Rust 练习册 :Pythagorean Triplet与数学算法
毕达哥拉斯三元组(Pythagorean Triplet)是满足毕达哥拉斯定理 a² + b² = c² 的三个正整数 (a, b, c)。在 Exercism 的 “pythagorean-triplet” 练习中,我们需要找到所有满足 a + b + c = sum 条件的毕达哥拉斯三元组。这不仅能帮助我们掌握数学算法和优化技巧,还能深入学习Rust中的集合操作、迭代器使用和性能优化。
什么是毕达哥拉斯三元组?
毕达哥拉斯三元组是满足毕达哥拉斯定理 a² + b² = c² 的三个正整数 (a, b, c)。其中最著名的例子是 (3, 4, 5),因为 3² + 4² = 9 + 16 = 25 = 5²。
在本练习中,我们需要找到所有满足以下条件的三元组:
- a² + b² = c²(毕达哥拉斯定理)
- a + b + c = sum(给定的和)
- a < b < c(按升序排列)
让我们先看看练习提供的函数签名:
use std::collections::HashSet;pub fn find(sum: u32) -> HashSet<[u32; 3]> {unimplemented!("Given the sum {}, return all possible Pythagorean triplets, which produce the said sum, or an empty HashSet if there are no such triplets. Note that you are expected to return triplets in [a, b, c] order, where a < b < c", sum);
}
我们需要实现 find 函数,根据给定的和找到所有符合条件的毕达哥拉斯三元组。
设计分析
1. 核心要求
- 数学计算:正确实现毕达哥拉斯定理的验证
- 条件筛选:筛选满足和条件的三元组
- 排序约束:确保返回的三元组满足 a < b < c
- 去重处理:使用 HashSet 避免重复的三元组
2. 技术要点
- 暴力搜索:遍历所有可能的组合
- 数学优化:利用数学性质减少搜索空间
- 集合操作:使用 HashSet 存储和去重结果
- 性能优化:优化算法以处理大数计算
完整实现
1. 基础暴力搜索实现
use std::collections::HashSet;pub fn find(sum: u32) -> HashSet<[u32; 3]> {let mut triplets = HashSet::new();// 遍历所有可能的 a 和 b 值for a in 1..sum / 3 {for b in (a + 1)..sum / 2 {let c = sum - a - b;// 确保 c > b 以满足 a < b < c 的条件if c > b {// 验证毕达哥拉斯定理if a * a + b * b == c * c {triplets.insert([a, b, c]);}}}}triplets
}
2. 优化的数学实现
use std::collections::HashSet;pub fn find(sum: u32) -> HashSet<[u32; 3]> {let mut triplets = HashSet::new();// 根据数学推导优化搜索范围// 从 a = 1 开始,到 a < sum / 3 结束for a in 1..sum / 3 {// 利用 a + b + c = sum 和 a² + b² = c² 推导出:// b = (sum² - 2*sum*a) / (2*(sum - a))let numerator = (sum as u64) * (sum as u64) - 2 * (sum as u64) * (a as u64);let denominator = 2 * ((sum as u64) - (a as u64));// 检查是否能整除if denominator != 0 && numerator % denominator == 0 {let b = (numerator / denominator) as u32;// 确保 b > a 且 c > bif b > a {let c = sum - a - b;if c > b {// 验证毕达哥拉斯定理if a * a + b * b == c * c {triplets.insert([a, b, c]);}}}}}triplets
}
3. 使用Euclid公式的实现
use std::collections::HashSet;pub fn find(sum: u32) -> HashSet<[u32; 3]> {let mut triplets = HashSet::new();// 使用Euclid公式生成原始毕达哥拉斯三元组// 对于互质的 m > n > 0,且 m,n 不同时为奇数:// a = m² - n²// b = 2mn// c = m² + n²let limit = ((sum as f64).sqrt() as u32) + 1;for m in 2..limit {for n in 1..m {// 确保 m 和 n 不同时为奇数if (m - n) % 2 == 1 {let a = m * m - n * n;let b = 2 * m * n;let c = m * m + n * n;// 确保 a < b(如果不满足则交换)let (a, b) = if a < b { (a, b) } else { (b, a) };let triplet_sum = a + b + c;// 如果基本三元组的和能整除目标和,则存在解if sum % triplet_sum == 0 {let k = sum / triplet_sum;triplets.insert([k * a, k * b, k * c]);}}}}triplets
}
测试用例分析
通过查看测试用例,我们可以更好地理解需求:
#[test]
fn test_triplets_whose_sum_is_12() {process_tripletswithsum_case(12, &[[3, 4, 5]]);
}
和为12的毕达哥拉斯三元组只有(3, 4, 5)。
#[test]
fn test_triplets_whose_sum_is_108() {process_tripletswithsum_case(108, &[[27, 36, 45]]);
}
和为108的毕达哥拉斯三元组只有(27, 36, 45)。
#[test]
fn test_triplets_whose_sum_is_1000() {process_tripletswithsum_case(1000, &[[200, 375, 425]]);
}
和为1000的毕达哥拉斯三元组只有(200, 375, 425)。
#[test]
fn test_no_matching_triplets_for_1001() {process_tripletswithsum_case(1001, &[]);
}
和为1001时没有符合条件的毕达哥拉斯三元组。
#[test]
fn test_returns_all_matching_triplets() {process_tripletswithsum_case(90, &[[9, 40, 41], [15, 36, 39]]);
}
和为90时有两个符合条件的毕达哥拉斯三元组。
#[test]
fn test_several_matching_triplets() {process_tripletswithsum_case(840,&[[40, 399, 401],[56, 390, 394],[105, 360, 375],[120, 350, 370],[140, 336, 364],[168, 315, 357],[210, 280, 350],[240, 252, 348],],);
}
和为840时有8个符合条件的毕达哥拉斯三元组。
#[test]
fn test_triplets_for_large_number() {process_tripletswithsum_case(30_000,&[[1200, 14_375, 14_425],[1875, 14_000, 14_125],[5000, 12_000, 13_000],[6000, 11_250, 12_750],[7500, 10_000, 12_500],],);
}
和为30000时有5个符合条件的毕达哥拉斯三元组。
性能优化版本
考虑性能的优化实现:
use std::collections::HashSet;pub fn find(sum: u32) -> HashSet<[u32; 3]> {let mut triplets = HashSet::new();// 边界情况处理if sum < 12 {return triplets;}// 优化搜索范围let max_a = sum / 3;for a in 1..=max_a {// 使用数学公式直接计算 b// 从 a + b + c = sum 和 a² + b² = c² 可得:// b = (sum² - 2*sum*a) / (2*(sum - a))let sum_sq = (sum as u64) * (sum as u64);let numerator = sum_sq - 2 * (sum as u64) * (a as u64);let denominator = 2 * ((sum as u64) - (a as u64));if denominator > 0 && numerator % denominator == 0 {let b = (numerator / denominator) as u32;// 确保 b > a 且满足三元组条件if b > a {let c = sum - a - b;if c > b {// 验证毕达哥拉斯定理(二次验证)if (a as u64) * (a as u64) + (b as u64) * (b as u64) == (c as u64) * (c as u64) {triplets.insert([a, b, c]);}}}}}triplets
}// 使用预分配的版本
pub fn find_with_capacity(sum: u32) -> HashSet<[u32; 3]> {// 预估容量以减少重新分配let estimated_capacity = (sum as f64).sqrt() as usize;let mut triplets = HashSet::with_capacity(estimated_capacity);if sum < 12 {return triplets;}let max_a = sum / 3;for a in 1..=max_a {let sum_sq = (sum as u64) * (sum as u64);let numerator = sum_sq - 2 * (sum as u64) * (a as u64);let denominator = 2 * ((sum as u64) - (a as u64));if denominator > 0 && numerator % denominator == 0 {let b = (numerator / denominator) as u32;if b > a {let c = sum - a - b;if c > b {if (a as u64) * (a as u64) + (b as u64) * (b as u64) == (c as u64) * (c as u64) {triplets.insert([a, b, c]);}}}}}triplets
}// 使用Vec代替HashSet的版本(如果不需要去重)
pub fn find_vec(sum: u32) -> Vec<[u32; 3]> {let mut triplets = Vec::new();if sum < 12 {return triplets;}let max_a = sum / 3;for a in 1..=max_a {let sum_sq = (sum as u64) * (sum as u64);let numerator = sum_sq - 2 * (sum as u64) * (a as u64);let denominator = 2 * ((sum as u64) - (a as u64));if denominator > 0 && numerator % denominator == 0 {let b = (numerator / denominator) as u32;if b > a {let c = sum - a - b;if c > b {if (a as u64) * (a as u64) + (b as u64) * (b as u64) == (c as u64) * (c as u64) {triplets.push([a, b, c]);}}}}}triplets
}
错误处理和边界情况
考虑更多边界情况的实现:
use std::collections::HashSet;#[derive(Debug, PartialEq)]
pub enum TripletError {InvalidSum,NoTripletsFound,
}impl std::fmt::Display for TripletError {fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {match self {TripletError::InvalidSum => write!(f, "无效的和值"),TripletError::NoTripletsFound => write!(f, "未找到符合条件的三元组"),}}
}impl std::error::Error for TripletError {}pub fn find(sum: u32) -> HashSet<[u32; 3]> {// 处理边界情况if sum < 12 {return HashSet::new();}let mut triplets = HashSet::new();let max_a = sum / 3;for a in 1..=max_a {let sum_sq = (sum as u64) * (sum as u64);let numerator = sum_sq - 2 * (sum as u64) * (a as u64);let denominator = 2 * ((sum as u64) - (a as u64));if denominator > 0 && numerator % denominator == 0 {let b = (numerator / denominator) as u32;if b > a {let c = sum - a - b;if c > b {if (a as u64) * (a as u64) + (b as u64) * (b as u64) == (c as u64) * (c as u64) {triplets.insert([a, b, c]);}}}}}triplets
}// 返回Result的版本
pub fn find_safe(sum: u32) -> Result<HashSet<[u32; 3]>, TripletError> {if sum < 12 {return Err(TripletError::InvalidSum);}let triplets = find(sum);if triplets.is_empty() {Err(TripletError::NoTripletsFound)} else {Ok(triplets)}
}// 支持更大整数类型的版本
use std::collections::HashSet as StdHashSet;pub fn find_u64(sum: u64) -> StdHashSet<[u64; 3]> {let mut triplets = StdHashSet::new();if sum < 12 {return triplets;}let max_a = sum / 3;for a in 1..=max_a {let sum_sq = sum * sum;let numerator = sum_sq - 2 * sum * a;let denominator = 2 * (sum - a);if denominator > 0 && numerator % denominator == 0 {let b = numerator / denominator;if b > a {let c = sum - a - b;if c > b {if a * a + b * b == c * c {triplets.insert([a, b, c]);}}}}}triplets
}
扩展功能
基于基础实现,我们可以添加更多功能:
use std::collections::HashSet;pub struct PythagoreanTripletFinder;impl PythagoreanTripletFinder {pub fn new() -> Self {PythagoreanTripletFinder}pub fn find(&self, sum: u32) -> HashSet<[u32; 3]> {find(sum)}// 验证三元组是否为毕达哥拉斯三元组pub fn is_pythagorean_triplet(&self, triplet: &[u32; 3]) -> bool {let [a, b, c] = triplet;a * a + b * b == c * c}// 生成原始毕达哥拉斯三元组(互质的三元组)pub fn generate_primitive_triplets(&self, limit: u32) -> HashSet<[u32; 3]> {let mut triplets = HashSet::new();let m_limit = ((limit as f64).sqrt() as u32) + 1;for m in 2..m_limit {for n in 1..m {if (m - n) % 2 == 1 && gcd(m, n) == 1 {let a = m * m - n * n;let b = 2 * m * n;let c = m * m + n * n;let (a, b) = if a < b { (a, b) } else { (b, a) };if a + b + c <= limit {triplets.insert([a, b, c]);}}}}triplets}// 从原始三元组生成所有三元组pub fn generate_all_triplets_from_primitive(&self, primitive: &[u32; 3], limit: u32) -> Vec<[u32; 3]> {let mut triplets = Vec::new();let [a, b, c] = primitive;let sum = a + b + c;let mut k = 1;while k * sum <= limit {triplets.push([k * a, k * b, k * c]);k += 1;}triplets}// 查找特定范围内的所有毕达哥拉斯三元组pub fn find_in_range(&self, min_sum: u32, max_sum: u32) -> Vec<(u32, HashSet<[u32; 3]>)> {let mut results = Vec::new();for sum in min_sum..=max_sum {let triplets = self.find(sum);if !triplets.is_empty() {results.push((sum, triplets));}}results}// 统计特定和值的三元组数量pub fn count_triplets(&self, sum: u32) -> usize {self.find(sum).len()}
}// 计算最大公约数
fn gcd(mut a: u32, mut b: u32) -> u32 {while b != 0 {let temp = b;b = a % b;a = temp;}a
}pub fn find(sum: u32) -> HashSet<[u32; 3]> {let mut triplets = HashSet::new();if sum < 12 {return triplets;}let max_a = sum / 3;for a in 1..=max_a {let sum_sq = (sum as u64) * (sum as u64);let numerator = sum_sq - 2 * (sum as u64) * (a as u64);let denominator = 2 * ((sum as u64) - (a as u64));if denominator > 0 && numerator % denominator == 0 {let b = (numerator / denominator) as u32;if b > a {let c = sum - a - b;if c > b {if (a as u64) * (a as u64) + (b as u64) * (b as u64) == (c as u64) * (c as u64) {triplets.insert([a, b, c]);}}}}}triplets
}// 毕达哥拉斯三元组分析
pub struct TripletAnalysis {pub sum: u32,pub triplets: HashSet<[u32; 3]>,pub count: usize,pub smallest_triplet: Option<[u32; 3]>,pub largest_triplet: Option<[u32; 3]>,
}impl PythagoreanTripletFinder {pub fn analyze(&self, sum: u32) -> TripletAnalysis {let triplets = self.find(sum);let count = triplets.len();let smallest_triplet = triplets.iter().min().copied();let largest_triplet = triplets.iter().max().copied();TripletAnalysis {sum,triplets,count,smallest_triplet,largest_triplet,}}
}// 便利函数
pub fn is_pythagorean_triplet(triplet: &[u32; 3]) -> bool {let [a, b, c] = triplet;a * a + b * b == c * c
}pub fn format_triplets(triplets: &HashSet<[u32; 3]>) -> String {let mut formatted = Vec::new();for triplet in triplets {formatted.push(format!("({}, {}, {})", triplet[0], triplet[1], triplet[2]));}formatted.sort();formatted.join(", ")
}
实际应用场景
毕达哥拉斯三元组在实际开发中有以下应用:
- 数学软件:数学计算和教育工具
- 游戏开发:几何游戏和益智游戏
- 图形学:计算机图形学中的几何计算
- 密码学:数论相关算法
- 教育工具:数学教学演示
- 算法竞赛:数学算法问题解决
- 工程计算:三角形相关计算
- 科学研究:数论研究工具
算法复杂度分析
-
时间复杂度:
- 暴力搜索:O(n²)
- 优化实现:O(n)
- Euclid公式:O(√n)
-
空间复杂度:O(k)
- 其中k是符合条件的三元组数量
与其他实现方式的比较
// 使用递归的实现
pub fn find_recursive(sum: u32) -> HashSet<[u32; 3]> {fn find_helper(sum: u32, a: u32, b: u32) -> HashSet<[u32; 3]> {if a >= sum / 3 {return HashSet::new();}if b >= sum / 2 {return find_helper(sum, a + 1, a + 2);}let c = sum - a - b;let mut triplets = if c > b && a * a + b * b == c * c {let mut set = HashSet::new();set.insert([a, b, c]);set} else {HashSet::new()};triplets.extend(find_helper(sum, a, b + 1));triplets}find_helper(sum, 1, 2)
}// 使用第三方库的实现
// [dependencies]
// num = "0.4"use num::integer::gcd;pub fn find_with_num_library(sum: u32) -> HashSet<[u32; 3]> {let mut triplets = HashSet::new();if sum < 12 {return triplets;}let max_a = sum / 3;for a in 1..=max_a {let sum_sq = (sum as u64) * (sum as u64);let numerator = sum_sq - 2 * (sum as u64) * (a as u64);let denominator = 2 * ((sum as u64) - (a as u64));if denominator > 0 && numerator % denominator == 0 {let b = (numerator / denominator) as u32;if b > a {let c = sum - a - b;if c > b {if (a as u64) * (a as u64) + (b as u64) * (b as u64) == (c as u64) * (c as u64) {// 使用第三方库的gcd函数验证是否为原始三元组if gcd(gcd(a, b), c) == 1 {// 这是一个原始三元组}triplets.insert([a, b, c]);}}}}}triplets
}// 使用并行计算的实现
// [dependencies]
// rayon = "1.5"use rayon::prelude::*;pub fn find_parallel(sum: u32) -> HashSet<[u32; 3]> {if sum < 12 {return HashSet::new();}let max_a = sum / 3;(1..=max_a).into_par_iter().filter_map(|a| {let sum_sq = (sum as u64) * (sum as u64);let numerator = sum_sq - 2 * (sum as u64) * (a as u64);let denominator = 2 * ((sum as u64) - (a as u64));if denominator > 0 && numerator % denominator == 0 {let b = (numerator / denominator) as u32;if b > a {let c = sum - a - b;if c > b {if (a as u64) * (a as u64) + (b as u64) * (b as u64) == (c as u64) * (c as u64) {Some([a, b, c])} else {None}} else {None}} else {None}} else {None}}).collect()
}// 使用生成器模式的实现
pub struct TripletGenerator {sum: u32,a: u32,b: u32,
}impl TripletGenerator {pub fn new(sum: u32) -> Self {TripletGenerator { sum, a: 1, b: 2 }}
}impl Iterator for TripletGenerator {type Item = [u32; 3];fn next(&mut self) -> Option<Self::Item> {while self.a < self.sum / 3 {while self.b < self.sum / 2 {let c = self.sum - self.a - self.b;if c > self.b {if self.a * self.a + self.b * self.b == c * c {let triplet = [self.a, self.b, c];self.b += 1;return Some(triplet);}}self.b += 1;}self.a += 1;self.b = self.a + 1;}None}
}
总结
通过 pythagorean-triplet 练习,我们学到了:
- 数学算法:掌握了毕达哥拉斯三元组的数学性质和计算方法
- 优化技巧:学会了使用数学公式优化搜索算法
- 集合操作:深入理解了HashSet的使用和去重机制
- 边界处理:理解了如何处理各种边界情况
- 性能优化:学会了算法复杂度分析和优化技巧
- 设计模式:理解了生成器模式和工厂模式的应用
这些技能在实际开发中非常有用,特别是在数学计算、算法设计、游戏开发等场景中。毕达哥拉斯三元组虽然是一个数学问题,但它涉及到了算法优化、数学计算、集合操作、性能优化等许多核心概念,是学习Rust实用编程的良好起点。
通过这个练习,我们也看到了Rust在数学计算和算法实现方面的强大能力,以及如何用安全且高效的方式实现经典数学算法。这种结合了安全性和性能的语言特性正是Rust的魅力所在。
