如何创建一个可以适用于多种整数类型的is_prime函数?

45

我刚开始学习Rust,并想创建一些通用的基本数学函数。我有以下is_prime函数:

fn is_prime(n: i64) -> bool {
    if n == 2 || n == 3 {
        return true;
    } else if n % 2 == 0 || n % 3 == 0 {
        return false;
    }

    let mut i = 5i64;
    let mut w = 2i64;
    while i*i <= n {
        if n % i == 0 {
            return false;
        }
        i += w;
        w = 6 - w;
    }
    true
}

我需要怎样才能将 isize, i64, usize等作为参数传递?我已经阅读了主页上的Rust指南,但不确定如何将特质的想法应用到我的目标中。

3个回答

46

通用数值类型可能会让人感到很烦,但一旦掌握了它们,它们通常不会太难,尽管可能会更冗长。这些方法的标准构建块是来自 crates.io 的 num crate 中的 traits,尤其是NumZeroOne,以及标准库的 std::cmp::PartialOrd

数字字面值不能泛型化到任何数字类型;必须使用 trait 方法调用; Zero::zero()One::one() 对于大多数情况已经足够 - 这里我们想要的数字是 0、1、2、3、5 和 6,使用这些构建块非常容易实现。您也可以创建自己的 trait 并为您喜欢的任何数字类型实现它,但只使用 Num 所保证的内容更好。

基本步骤是将通用类型参数指定为基于 Num(如果写一个值的不等式,例如 i * i <= n,则还需基于 PartialOrd),并将任何数字字面值替换为由零和一构造的数字,正如下面方法开头的半打 let 语句所示。那通常就足够了。

下面是针对这种特定方法的最终结果:

// You’ll also need the appropriate dependencies.num addition to Cargo.toml
extern crate num;

use num::Num;

fn is_prime<N: Num + PartialOrd + Copy>(n: N) -> bool {
    let _0 = N::zero();
    let _1 = N::one();
    let _2 = _1 + _1;
    let _3 = _2 + _1;
    let _5 = _2 + _3;
    let _6 = _3 + _3;
    if n == _2 || n == _3 {
        return true;
    } else if n % _2 == _0 || n % _3 == _0 {
        return false;
    }

    let mut i = _5;
    let mut w = _2;
    while i * i <= n {
        if n % i == _0 {
            return false;
        }
        i = i + w;
        w = _6 - w;
    }
    true
}

13
可以使用实际所需的基本特征作为约束条件,而不是使用Num trait。这些基本特征包括:N: PartialEq + PartialOrd + Add<N, N> + Sub<N, N> + Mul<N, N> + Rem<N, N> + One + ZeroNum只是一个方便的快捷方式。 - Francis Gagné

19
补充Chris Morgan的回答,您可以使用num :: NumCast :: from 将其转换为通用数字类型,在其中使用 Zero One 是不合适的。 在您的情况下:
use num::{Num, NumCast};

fn is_prime<N: Num + Ord + NumCast + Copy>(n: N) -> bool {
    let _0: N = NumCast::from(0usize).unwrap();
    let _1: N = NumCast::from(1usize).unwrap();
    let _2: N = NumCast::from(2usize).unwrap();
    let _3: N = NumCast::from(3usize).unwrap();
    let _4: N = NumCast::from(4usize).unwrap();
    let _5: N = NumCast::from(5usize).unwrap();
    let _6: N = NumCast::from(6usize).unwrap();

1

使用num包的另一个选项是使用ToPrimitive特性将传入的任何类型转换为i64。 使用您提供的相同函数,稍作修改以允许我们在无法将转换为i64时返回None:

use num::ToPrimitive;

fn is_prime<N: ToPrimitive>(q: N) -> Option<bool> {
    let n: i64 = q.to_i64()?;
    if n == 2 || n == 3 {
        return Some(true);
    } else if n % 2 == 0 || n % 3 == 0 {
        return Some(false);
    }

    let mut i = 5i64;
    let mut w = 2i64;
    while i * i <= n {
        if n % i == 0 {
            return Some(false);
        }
        i += w;
        w = 6 - w;
    }
    Some(true)
}

fn print_is_prime<N: ToPrimitive + std::fmt::Debug + Clone>(q: N) {
    println!("Is {:?} prime? {:?}", q, is_prime(q.clone()));
}

fn main() {
    let prime: usize = 7;
    print_is_prime(prime);

    let prime: isize = 7;
    print_is_prime(prime);

    let prime: i32 = 7;
    print_is_prime(prime);

    let prime: i64 = 7;
    print_is_prime(prime);
}

这将生成输出:

Is 7 prime? Some(true)
Is 7 prime? Some(true)
Is 7 prime? Some(true)
Is 7 prime? Some(true)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接