Как установить верхнюю границу размера массива в n-м простом, используя Eratoshenes Sieve? - PullRequest
2 голосов
/ 11 октября 2019

При вводе n я пытаюсь вывести n-е простое число. Сито Эратосфена казалось хорошим способом сделать это, но у меня возникли проблемы с размером массива, который необходимо просеять.

Я использую массив, в котором каждый член равен 1 и представляет число. Если сито отфильтровывает число, значение изменяется на 0, что означает, что число не простое. Как только он достигает n-го члена, который имеет значение 1, возвращается значение индекса.

Я пытаюсь установить разумный размер массива для любого данного 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];
    ...
}

Однако, это не обходит требование иметь фиксированный размер массива.

Есть предложения? Также я очень новичок в ржавчине, и любые предложения, чтобы сделать его более ржавым, приветствуются.

Ниже моя попытка, которая имеет массив фиксированного размера и работает для значений n <= 10,000: </p>

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
}

edit: Спасибо за отличные ответы! Я изменил функцию, чтобы использовать оценку (n * (n * n.ln()).ln()), заменил массив usize вектором bools. Жаль, что я не могу заставить более низкие значения этой работы только на стеке, потому что метод на основе массива быстрее, чем метод на основе вектора, когда размер массива близок к приближению.

Я попытался использовать ранний возврат и отказаться от output, но не мог понять, как заставить это работать. У меня есть код в конце этого редактирования с закомментированными строками, где я пытался поставить ранний возврат и unreachable!(), но он всегда паниковал. Если бы кто-то мог указать на мою ошибку, это было бы здорово.

Я собирался изменить самый внутренний цикл while в цикл for, как предложено NieDzejkob, но когда я запустил числа для скоростиwhile против for, я обнаружил, что цикл while быстрее. Кто-нибудь знает, почему это может быть?

Также у меня были некоторые проблемы с 0-9-м, 11-м и 12-м простыми числами, поэтому я просто установил приближение для работы выше этого диапазона.

100-е простое while_func = 547, for_func = 547
простое while_func 152.431 мкс против for_func 216.043 мкс
1000-е простое while_func = 7927, for_func = 7927
простое while_func 2.099213ms против for_func 3.362854th
1049= 104743, for_func = 104743
простое время while_func 28,166767 мс против for_func 44,967197мс
100000-е простое while_func = 1299721, for_func = 1299721
простое время while_func 339,324756 мс против for_func 559,755_586 = 5,66 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 = 5,600 для финн. = 15485867
prime 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
    }
}

Ответы [ 2 ]

2 голосов
/ 11 октября 2019

Использование переменного размера массива

К сожалению, невозможно иметь массив переменной длины в стеке. Вы можете использовать вектор, чтобы выделить его в куче:

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

Оценка необходимого размера

Эта проблема была описана здесь ранее , но не в Rust. Идея состоит в том, чтобы использовать формулу верхней границы для n-го простого числа:

p n

Для 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

Что касается улучшения вашего кода, я не понимаю, почему вы используете usize s, так как выХраните только нули и единицы. Это идеальный вариант использования для bool, что должно привести к восьмикратному сокращению использования памяти.

Переменная output на самом деле не нужна, было бы лучше использовать return, чтобы выйти рано. Затем вы можете пометить конец функции с помощью unreachable!().

Кроме того, самый внутренний цикл также может быть записан таким образом, и я думаю, что это лучше:

for j in (2*i ..= primes.len()).step_by(i) {
    primes[j] = false;
}
1 голос
/ 11 октября 2019

Стандартная формула для размера сита: ln (n ln n) для n ≥ 6. См. Википедия .

Если вы используете функцию потолка, потолок (ln (n ln (n))), то формула работает для n> = 3. Однако она не работает для 1 и 2. Для n = 1,вы получаете ошибку для принятия ln (0) во втором вызове ln (), но оно должно быть 2. Для n = 2 сито должно быть 3.

Итак, проверьте для n <3 какпоказано ниже. </p>

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
    }
}
...