详细介绍:Rust 练习册 :Nth Prime与素数算法

素数是数学中的基本概念,在密码学、计算机科学和数论中都有重要应用。在 Exercism 的 “nth-prime” 练习中,我们需要实现一个函数来找到第n个素数。这不仅能帮助我们掌握素数判断和生成算法,还能深入学习Rust中的迭代器、数学计算和性能优化技巧。

什么是素数?

素数是大于1的自然数,除了1和它本身之外没有其他正因数。例如:2, 3, 5, 7, 11, 13, 17, 19, 23, 29等都是素数。

在我们的练习中,需要实现一个函数来找到第n个素数(从0开始计数)。例如:

  • 第0个素数是2
  • 第1个素数是3
  • 第5个素数是13

素数在以下领域有重要应用:

  1. 密码学:RSA加密算法依赖于大素数
  2. 哈希表:使用素数作为哈希表大小可以减少冲突
  3. 随机数生成:某些随机数生成器使用素数
  4. 数论研究:素数是数论研究的核心对象

让我们先看看练习提供的实现:

pub fn nth(n: u32) -> u32 {
(2..).filter(|i| is_prime(*i)).nth(n as usize).unwrap_or(0)
}
fn is_prime(n: u32) -> bool {
!(2..n).any(|i| n % i == 0)
}

这是一个简洁但效率不高的实现。它使用迭代器从2开始生成所有数字,过滤出素数,然后取第n个。

设计分析

1. 核心要求

  1. 素数判断:判断一个数是否为素数
  2. 素数生成:生成素数序列
  3. 索引查找:找到第n个素数
  4. 性能考虑:优化算法以提高效率

2. 技术要点

  1. 迭代器使用:充分利用Rust的迭代器功能
  2. 数学优化:优化素数判断算法
  3. 边界处理:正确处理边界情况
  4. 类型选择:选择合适的数据类型

完整实现

1. 基础实现

pub fn nth(n: u32) -> u32 {
(2..).filter(|i| is_prime(*i)).nth(n as usize).unwrap_or(0)
}
fn is_prime(n: u32) -> bool {
!(2..n).any(|i| n % i == 0)
}

2. 优化的素数判断

pub fn nth(n: u32) -> u32 {
(2..).filter(|i| is_prime(*i)).nth(n as usize).unwrap_or(0)
}
fn is_prime(n: u32) -> bool {
if n < 2 {
return false;
}
if n == 2 {
return true;
}
if n % 2 == 0 {
return false;
}
let limit = (n as f64).sqrt() as u32;
!(3..=limit).step_by(2).any(|i| n % i == 0)
}

3. 埃拉托斯特尼筛法实现

pub fn nth(n: u32) -> u32 {
if n == 0 {
return 2;
}
// 估算第n个素数的上界(使用素数定理的近似)
let limit = if n < 6 {
12
} else {
(n as f64 * (n as f64).ln() + n as f64 * (n as f64).ln().ln()) as usize
};
let primes = sieve_of_eratosthenes(limit);
primes[n as usize]
}
fn sieve_of_eratosthenes(limit: usize) -> Vec<u32> {
  if limit < 2 {
  return vec![];
  }
  let mut is_prime = vec![true; limit + 1];
  is_prime[0] = false;
  if limit >= 1 {
  is_prime[1] = false;
  }
  let sqrt_limit = (limit as f64).sqrt() as usize;
  for i in 2..=sqrt_limit {
  if is_prime[i] {
  let mut j = i * i;
  while j <= limit {
  is_prime[j] = false;
  j += i;
  }
  }
  }
  (2..=limit)
  .filter(|&i| is_prime[i])
  .map(|i| i as u32)
  .collect()
  }

测试用例分析

通过查看测试用例,我们可以更好地理解需求:

#[test]
fn test_first_prime() {
assert_eq!(np::nth(0), 2);
}

第0个素数是2。

#[test]
fn test_second_prime() {
assert_eq!(np::nth(1), 3);
}

第1个素数是3。

#[test]
fn test_sixth_prime() {
assert_eq!(np::nth(5), 13);
}

第5个素数是13。

#[test]
fn test_big_prime() {
assert_eq!(np::nth(10_000), 104_743);
}

第10,000个素数是104,743。

性能优化版本

考虑性能的优化实现:

pub fn nth(n: u32) -> u32 {
match n {
0 => 2,
1 => 3,
_ => {
let mut count = 2; // 已经计算了2和3
let mut candidate = 5; // 从5开始检查
loop {
if is_prime_optimized(candidate) {
if count == n {
return candidate;
}
count += 1;
}
candidate += 2; // 只检查奇数
}
}
}
}
fn is_prime_optimized(n: u32) -> bool {
if n < 2 {
return false;
}
if n == 2 || n == 3 {
return true;
}
if n % 2 == 0 || n % 3 == 0 {
return false;
}
// 所有素数都可以表示为6k±1的形式(除了2和3)
let limit = (n as f64).sqrt() as u32;
let mut i = 5;
while i <= limit {
if n % i == 0 || n % (i + 2) == 0 {
return false;
}
i += 6;
}
true
}
// 使用缓存的版本
pub struct PrimeGenerator {
primes: Vec<u32>,
  current: u32,
  }
  impl PrimeGenerator {
  pub fn new() -> Self {
  PrimeGenerator {
  primes: vec![2, 3],
  current: 5,
  }
  }
  pub fn nth(&mut self, n: u32) -> u32 {
  while self.primes.len() <= n as usize {
  if self.is_prime(self.current) {
  self.primes.push(self.current);
  }
  self.current += 2; // 只检查奇数
  }
  self.primes[n as usize]
  }
  fn is_prime(&self, n: u32) -> bool {
  let limit = (n as f64).sqrt() as u32;
  for &prime in &self.primes {
  if prime > limit {
  break;
  }
  if n % prime == 0 {
  return false;
  }
  }
  true
  }
  }
  // 全局缓存版本
  use std::sync::Mutex;
  use std::collections::HashMap;
  use once_cell::sync::Lazy;
  static PRIME_CACHE: Lazy<Mutex<HashMap<u32, u32>>> = Lazy::new(|| Mutex::new(HashMap::new()));
    pub fn nth_cached(n: u32) -> u32 {
    {
    let cache = PRIME_CACHE.lock().unwrap();
    if let Some(&prime) = cache.get(&n) {
    return prime;
    }
    }
    let prime = nth(n);
    let mut cache = PRIME_CACHE.lock().unwrap();
    cache.insert(n, prime);
    prime
    }

错误处理和边界情况

考虑更多边界情况的实现:

#[derive(Debug, PartialEq)]
pub enum PrimeError {
InvalidIndex,
Overflow,
}
pub fn nth_safe(n: u32) -> Result<u32, PrimeError> {
  if n > 100_000 {
  return Err(PrimeError::InvalidIndex); // 防止过长计算
  }
  match n {
  0 => Ok(2),
  1 => Ok(3),
  _ => {
  let mut count = 2;
  let mut candidate = 5;
  loop {
  if is_prime_optimized(candidate) {
  if count == n {
  return Ok(candidate);
  }
  count += 1;
  }
  candidate += 2;
  // 防止溢出
  if candidate < 5 {
  return Err(PrimeError::Overflow);
  }
  }
  }
  }
  }
  fn is_prime_optimized(n: u32) -> bool {
  if n < 2 {
  return false;
  }
  if n == 2 || n == 3 {
  return true;
  }
  if n % 2 == 0 || n % 3 == 0 {
  return false;
  }
  let limit = (n as f64).sqrt() as u32;
  let mut i = 5;
  while i <= limit {
  if n % i == 0 || n % (i + 2) == 0 {
  return false;
  }
  i += 6;
  }
  true
  }
  pub fn nth(n: u32) -> u32 {
  nth_safe(n).unwrap_or(0)
  }

扩展功能

基于基础实现,我们可以添加更多功能:

pub struct PrimeUtils;
impl PrimeUtils {
// 生成前n个素数
pub fn first_n_primes(n: u32) -> Vec<u32> {
  (0..n).map(|i| nth(i)).collect()
  }
  // 检查一个数是否为素数
  pub fn is_prime(n: u32) -> bool {
  if n < 2 {
  return false;
  }
  if n == 2 || n == 3 {
  return true;
  }
  if n % 2 == 0 || n % 3 == 0 {
  return false;
  }
  let limit = (n as f64).sqrt() as u32;
  let mut i = 5;
  while i <= limit {
  if n % i == 0 || n % (i + 2) == 0 {
  return false;
  }
  i += 6;
  }
  true
  }
  // 获取小于n的所有素数
  pub fn primes_less_than(n: u32) -> Vec<u32> {
    if n <= 2 {
    return vec![];
    }
    let mut is_prime = vec![true; n as usize];
    is_prime[0] = false;
    is_prime[1] = false;
    let sqrt_limit = (n as f64).sqrt() as usize;
    for i in 2..=sqrt_limit {
    if is_prime[i] {
    let mut j = i * i;
    while j < n as usize {
    is_prime[j] = false;
    j += i;
    }
    }
    }
    (2..n as usize)
    .filter(|&i| is_prime[i])
    .map(|i| i as u32)
    .collect()
    }
    // 获取素数的近似位置
    pub fn approximate_index(prime: u32) -> u32 {
    if prime < 2 {
    return 0;
    }
    (prime as f64 / (prime as f64).ln()) as u32
    }
    // 验证第n个素数
    pub fn verify_nth_prime(n: u32, expected: u32) -> bool {
    nth(n) == expected && Self::is_prime(expected)
    }
    }
    // 素数生成器迭代器
    pub struct PrimeIterator {
    current_index: u32,
    }
    impl PrimeIterator {
    pub fn new() -> Self {
    PrimeIterator { current_index: 0 }
    }
    }
    impl Iterator for PrimeIterator {
    type Item = u32;
    fn next(&mut self) -> Option<Self::Item> {
      let prime = nth(self.current_index);
      self.current_index += 1;
      Some(prime)
      }
      }
      // 便利函数
      pub fn nth(n: u32) -> u32 {
      match n {
      0 => 2,
      1 => 3,
      _ => {
      let mut count = 2;
      let mut candidate = 5;
      loop {
      if PrimeUtils::is_prime(candidate) {
      if count == n {
      return candidate;
      }
      count += 1;
      }
      candidate += 2;
      }
      }
      }
      }

实际应用场景

素数算法在实际开发中有以下应用:

  1. 密码学:RSA加密、Diffie-Hellman密钥交换
  2. 哈希表:选择合适的表大小以减少冲突
  3. 随机数生成:线性同余生成器等算法
  4. 算法竞赛:数论相关问题
  5. 科学计算:数论研究和数学建模
  6. 游戏开发:生成伪随机数和哈希值
  7. 区块链:加密货币中的哈希算法
  8. 网络协议:安全通信协议中的密钥生成

算法复杂度分析

  1. 基础实现时间复杂度:O(n × p²)

    • 其中p是第n个素数,需要检查n个素数,每个素数的判断需要O§时间
  2. 优化实现时间复杂度:O(n × √p)

    • 优化了素数判断算法,只需要检查到√p
  3. 埃拉托斯特尼筛法时间复杂度:O(n × log(log(n)))

    • 对于生成大量素数更高效
  4. 空间复杂度

    • 基础实现:O(1)
    • 筛法实现:O(n)

与其他实现方式的比较

// 使用递归的实现
pub fn nth_recursive(n: u32) -> u32 {
fn find_nth_prime(index: u32, current: u32, count: u32) -> u32 {
if count == index {
return current;
}
let next = if current == 2 { 3 } else { current + 2 };
if is_prime(next) {
find_nth_prime(index, next, count + 1)
} else {
find_nth_prime(index, next, count)
}
}
if n == 0 {
2
} else {
find_nth_prime(n, 1, 0)
}
}
fn is_prime(n: u32) -> bool {
if n < 2 {
return false;
}
if n == 2 || n == 3 {
return true;
}
if n % 2 == 0 || n % 3 == 0 {
return false;
}
let limit = (n as f64).sqrt() as u32;
let mut i = 5;
while i <= limit {
if n % i == 0 || n % (i + 2) == 0 {
return false;
}
i += 6;
}
true
}
// 使用生成器模式的实现
pub struct SieveOfEratosthenes {
current: usize,
sieve: Vec<bool>,
  }
  impl SieveOfEratosthenes {
  pub fn new(limit: usize) -> Self {
  let mut sieve = vec![true; limit];
  if limit > 0 {
  sieve[0] = false;
  }
  if limit > 1 {
  sieve[1] = false;
  }
  let sqrt_limit = (limit as f64).sqrt() as usize;
  for i in 2..=sqrt_limit {
  if sieve[i] {
  let mut j = i * i;
  while j < limit {
  sieve[j] = false;
  j += i;
  }
  }
  }
  SieveOfEratosthenes { current: 2, sieve }
  }
  }
  impl Iterator for SieveOfEratosthenes {
  type Item = usize;
  fn next(&mut self) -> Option<Self::Item> {
    while self.current < self.sieve.len() {
    if self.sieve[self.current] {
    let prime = self.current;
    self.current += 1;
    return Some(prime);
    }
    self.current += 1;
    }
    None
    }
    }
    // 使用第三方库的实现
    // [dependencies]
    // primal = "0.3"
    pub fn nth_primal(n: u32) -> u32 {
    primal::nth_prime(n as usize) as u32
    }
    // 使用并行计算的实现
    use rayon::prelude::*;
    pub fn nth_parallel(n: u32) -> u32 {
    if n == 0 {
    return 2;
    }
    let limit = (n * 15) as usize; // 估算上界
    let candidates: Vec<usize> = (2..limit).collect();
      let primes: Vec<usize> = candidates
        .par_iter()
        .filter(|&&i| is_prime_parallel(i as u32))
        .collect();
        primes[n as usize] as u32
        }
        fn is_prime_parallel(n: u32) -> bool {
        if n < 2 {
        return false;
        }
        if n == 2 || n == 3 {
        return true;
        }
        if n % 2 == 0 || n % 3 == 0 {
        return false;
        }
        let limit = (n as f64).sqrt() as u32;
        !(5..=limit).step_by(6).any(|i| n % i == 0 || n % (i + 2) == 0)
        }

总结

通过 nth-prime 练习,我们学到了:

  1. 素数算法:掌握了素数判断和生成的基本算法
  2. 迭代器使用:学会了使用Rust的迭代器功能
  3. 数学优化:理解了如何优化数学算法
  4. 性能优化:了解了不同实现方式的性能特点
  5. 缓存技术:学会了使用缓存提高重复计算的效率
  6. 边界处理:深入理解了边界情况的处理

这些技能在实际开发中非常有用,特别是在密码学、算法竞赛、科学计算等场景中。素数计算虽然是一个经典的数学问题,但它涉及到了算法优化、数学计算、性能优化等许多核心概念,是学习Rust实用编程的良好起点。

通过这个练习,我们也看到了Rust在数学计算和算法实现方面的强大能力,以及如何用安全且高效的方式实现经典算法。这种结合了安全性和性能的语言特性正是Rust的魅力所在。

posted @ 2025-12-12 20:56  gccbuaa  阅读(1)  评论(0)    收藏  举报