【问题标题】:How to set upper bound in array size in nth prime using Eratoshenes Sieve?如何使用 Eratosthenes Sieve 在第 n 个素数中设置数组大小的上限?
【发布时间】:2019-10-10 22:01:39
【问题描述】:

以 n 作为输入,我试图输出第 n 个素数。 Eratosthenes Sieve 似乎是一个很好的方法,但我对要筛分的数组的大小有疑问。

我使用一个数组,其中每个成员都是 1,并且代表一个数字。如果筛子过滤掉数字,则值变为0,这意味着该数字不是素数。一旦到达值为 1 的第 n 个成员,则返回索引值。

我正在尝试为任何给定的 n 设置一个合理的数组大小。但是我有两个问题。

  1. 因为我似乎需要将数组的大小设置为一个常数值,所以我找不到使用 n 的大小来近似所需数组大小的方法。这意味着我总是使用 10e6 大小的数组,即使 n 很小。

  2. 这种方法依赖于拥有一个大数组,因为它使用早期值来更改后面的值。这意味着对于 n > 10e7,我的数组会破坏堆栈。有没有办法在不进入堆的情况下解决这个问题?

我尝试使用const 来解决第一个问题,如下所示:

pub fn nth(n: u32) -> u32 {
    const ARRAY_SIZE : usize = f(n) // where f(n) is some approximation of required array size
    let mut primes: [usize; ARRAY_SIZE] = [1; ARRAY_SIZE];
    ...
}

但是,它并没有绕过固定数组大小的要求。

有什么建议吗?此外,我对 rust 非常陌生,欢迎提出任何使它更像生锈的建议。

以下是我的尝试,它有一个固定大小的数组,适用于 n

pub fn nth(n: u32) -> u32 {
    // Eratosthene's Sieve Algorithm
    let mut output: u32 = 0;
    let mut primes: [usize; 104_827] = [1; 104_827];
    primes[0] = 0;
    primes[1] = 0;

    let mut prime_count = 0;

    for i in 2..(primes.len() as usize) {
        if primes[i] == 1 {
            if prime_count == n as usize { output = i as u32; }
            prime_count += 1;

            let mut j: usize = 2;
            while i * j <= primes.len() as usize {
                primes[i * j] = 0;
                j += 1;
            }
        }
    }
    output
}

编辑: 感谢您的精彩回答!我已更改函数以使用 (n * (n * n.ln()).ln()) 估计,将 usize 数组替换为布尔向量。遗憾的是,我似乎无法仅在堆栈上使用较低的值,因为当数组大小接近近似值时,基于数组的方法比基于向量的方法更快。

我尝试使用提前返回并省去output,但不知道如何使其工作。我在此编辑的末尾有我的代码,其中注释掉了我试图放置早期返回和unreachable!() 的行,但它总是让我感到恐慌。如果有人能指出我的错误,那就太好了。

我打算按照 NieDzejkob 的建议将最里面的 while 循环更改为 for 循环,但是当我运行 whilefor 的速度数字时,我发现了 while循环更快。有谁知道为什么会这样?

此外,我在 0-9、11 和 12 质数方面也遇到了一些问题,所以我只是将近似值设置为高于这个范围。

第 100 个素数 while_func = 547, for_func = 547
素数 while_func 152.431µs 与 for_func 216.043µs
第 1000 个素数 while_func = 7927, for_func = 7927
素数 while_func 2.099213ms 与 for_func 3.362854ms
第 10000 个素数 while_func = 104743, for_func = 104743
素数 while_func 28.162967ms 与 for_func 44.967197ms
第 100000 个素数 while_func = 1299721, for_func = 1299721
素数 while_func 339.324756ms 与 for_func 559.755934ms
第 1000000 个素数 while_func = 15485867,for_func = 15485867
素数 while_func 4.151047728s 与 for_func 6.950281943s

pub fn nth(n: u32) -> u32 {
    // Eratosthene's Sieve Algorithm
    let estimate = estimate_size(n);
    let mut primes: Vec<bool> = vec![true; estimate + 1];

    primes[0] = false;
    primes[1] = false;

    let mut output: u32 = 0; // remove if unreachable!() works
    let mut prime_count: u32 = 0;

    for i in 2..(estimate) {
        if primes[i] == true {
            if prime_count == n { output = i as u32; }
//            if prime_count == n { return i as u32; }
            prime_count += 1;

            let mut j: usize = 2;
            while i * j <= estimate {
                primes[i * j] = false;
                j += 1;
            }
//            for j in (2*i ..= estimate).step_by(i) {
//                primes[j] = false;
//            }
        }
    }
    output
//  unreachable!()
}

fn estimate_size(n: u32) -> usize {
    if n < 14 {
        44 as usize
    } else {
        let n = n as f64;
        (n * (n * n.ln()).ln()).ceil() as usize
    }
}

【问题讨论】:

    标签: arrays rust sieve-of-eratosthenes


    【解决方案1】:

    使用可变数组大小

    不幸的是,栈上不可能有一个变长数组。您可以使用向量在堆上分配它:

    let mut primes: Vec<bool> = vec![true; estimate_size(n)];
    

    估计所需大小

    这个问题has been described here before,虽然不在 Rust 中。这个想法是对第 n 个素数使用上限公式:

    pn

    对于u32,您可以使用浮点数来计算:

    // I'm making it a separate function for demonstration purposes. You might want to not do that.
    fn estimate_size(n: u32) -> usize {
        let n = n as f64;
        (n * (n * n.ln()).ln()).ceil() as usize
    }
    

    Rust 代码风格

    就改进代码而言,我不明白您为什么使用usizes,因为您只存储零和一。这是bool 的完美用例,它应该可以减少八倍的内存使用。

    output 变量并不是必需的,最好使用return 提前退出。然后,您可以使用 unreachable!() 标记函数的结尾。

    另外,最里面的循环也可以这样写,我觉得这样更好:

    for j in (2*i ..= primes.len()).step_by(i) {
        primes[j] = false;
    }
    

    【讨论】:

    • 您好,感谢您的所有建议!我将您提到的一些更改合并到上面的编辑中。我很想听听你对他们的看法。
    【解决方案2】:

    筛子尺寸的标准公式是 ln (n ln n),n ≥ 6。请参阅Wikipedia

    如果您使用上限函数上限(ln (n ln(n)),则该公式适用于 n >= 3。但是,它不适用于 1 和 2。对于 n=1,您会得到在第二次 ln() 调用中取 ln(0) 时出错,但它应该是 2。对于 n=2,筛子需要是 3。

    所以,如下所示检查 n

    fn get_sieve_size(n: u32) -> usize {
        if n < 3 {
            (n + 1) as usize
        } else {
            let n = n as f64;
            (n * (n * n.ln()).ln()).ceil() as usize
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-06
      • 2018-08-10
      • 2015-12-02
      • 2021-12-21
      相关资源
      最近更新 更多