Rust 练习册 :Raindrops与FizzBuzz变体
Raindrops 是经典的 FizzBuzz 问题的一个有趣变体。在这个练习中,我们需要根据数字的因子来返回特定的字符串,而不是简单的数字。这不仅能帮助我们掌握条件判断和字符串拼接技巧,还能深入学习Rust中的模运算、字符串操作和控制流。
什么是 Raindrops?
Raindrops 是 FizzBuzz 问题的一个变体,规则如下:
- 如果数字能被 3 整除,返回 “Pling”
- 如果数字能被 5 整除,返回 “Plang”
- 如果数字能被 7 整除,返回 “Plong”
- 如果数字能被多个数整除,则按顺序拼接对应的字符串
- 如果数字不能被 3、5 或 7 整除,则返回数字本身
例如:
- 1 → “1”
- 3 → “Pling”
- 5 → “Plang”
- 7 → “Plong”
- 15 → “PlingPlang”(能被3和5整除)
- 105 → “PlingPlangPlong”(能被3、5和7整除)
让我们先看看练习提供的函数实现:
pub fn raindrops(n: u32) -> String {let mut res = String::new();if n % 3 == 0 {res.push_str("Pling");}if n % 5 == 0 {res.push_str("Plang");}if n % 7 == 0 {res.push_str("Plong");}if res.is_empty() {res.push_str(&n.to_string());}res
}
这是一个直接而清晰的实现,通过模运算检查数字是否能被特定数整除,并相应地构建结果字符串。
设计分析
1. 核心要求
- 模运算:正确使用模运算判断整除性
- 条件判断:根据不同的条件返回不同的字符串
- 字符串拼接:按顺序拼接多个字符串
- 数字转换:在不满足条件时将数字转换为字符串
2. 技术要点
- 控制流:使用 if 语句进行条件判断
- 字符串操作:使用 String 类型进行字符串构建
- 类型转换:将数字转换为字符串
- 逻辑组合:处理多个条件的组合情况
完整实现
1. 基础实现
pub fn raindrops(n: u32) -> String {let mut res = String::new();if n % 3 == 0 {res.push_str("Pling");}if n % 5 == 0 {res.push_str("Plang");}if n % 7 == 0 {res.push_str("Plong");}if res.is_empty() {res.push_str(&n.to_string());}res
}
2. 使用 match 的实现
pub fn raindrops(n: u32) -> String {let pling = if n % 3 == 0 { "Pling" } else { "" };let plang = if n % 5 == 0 { "Plang" } else { "" };let plong = if n % 7 == 0 { "Plong" } else { "" };let result = format!("{}{}{}", pling, plang, plong);if result.is_empty() {n.to_string()} else {result}
}
3. 使用迭代器的函数式实现
pub fn raindrops(n: u32) -> String {let factors = [(3, "Pling"), (5, "Plang"), (7, "Plong")];let result: String = factors.iter().filter(|&&(divisor, _)| n % divisor == 0).map(|&(_, sound)| sound).collect();if result.is_empty() {n.to_string()} else {result}
}
测试用例分析
通过查看测试用例,我们可以更好地理解需求:
#[test]
fn test_1() {assert_eq!("1", raindrops::raindrops(1));
}
数字1不能被3、5或7整除,所以返回"1"。
#[test]
fn test_3() {assert_eq!("Pling", raindrops::raindrops(3));
}
数字3能被3整除,所以返回"Pling"。
#[test]
fn test_5() {assert_eq!("Plang", raindrops::raindrops(5));
}
数字5能被5整除,所以返回"Plang"。
#[test]
fn test_7() {assert_eq!("Plong", raindrops::raindrops(7));
}
数字7能被7整除,所以返回"Plong"。
#[test]
fn test_6() {assert_eq!("Pling", raindrops::raindrops(6));
}
数字6能被3整除,所以返回"Pling"。
#[test]
fn test_8() {assert_eq!("8", raindrops::raindrops(8));
}
数字8不能被3、5或7整除,所以返回"8"。
#[test]
fn test_9() {assert_eq!("Pling", raindrops::raindrops(9));
}
数字9能被3整除,所以返回"Pling"。
#[test]
fn test_10() {assert_eq!("Plang", raindrops::raindrops(10));
}
数字10能被5整除,所以返回"Plang"。
#[test]
fn test_14() {assert_eq!("Plong", raindrops::raindrops(14));
}
数字14能被7整除,所以返回"Plong"。
#[test]
fn test_15() {assert_eq!("PlingPlang", raindrops::raindrops(15));
}
数字15能被3和5整除,所以返回"PlingPlang"。
#[test]
fn test_21() {assert_eq!("PlingPlong", raindrops::raindrops(21));
}
数字21能被3和7整除,所以返回"PlingPlong"。
#[test]
fn test_25() {assert_eq!("Plang", raindrops::raindrops(25));
}
数字25能被5整除,所以返回"Plang"。
#[test]
fn test_27() {assert_eq!("Pling", raindrops::raindrops(27));
}
数字27能被3整除,所以返回"Pling"。
#[test]
fn test_35() {assert_eq!("PlangPlong", raindrops::raindrops(35));
}
数字35能被5和7整除,所以返回"PlangPlong"。
#[test]
fn test_49() {assert_eq!("Plong", raindrops::raindrops(49));
}
数字49能被7整除,所以返回"Plong"。
#[test]
fn test_52() {assert_eq!("52", raindrops::raindrops(52));
}
数字52不能被3、5或7整除,所以返回"52"。
#[test]
fn test_105() {assert_eq!("PlingPlangPlong", raindrops::raindrops(105));
}
数字105能被3、5和7整除,所以返回"PlingPlangPlong"。
#[test]
fn test_3125() {assert_eq!("Plang", raindrops::raindrops(3125));
}
数字3125能被5整除,所以返回"Plang"。
#[test]
fn test_12121() {assert_eq!("12121", raindrops::raindrops(12_121));
}
数字12121不能被3、5或7整除,所以返回"12121"。
性能优化版本
考虑性能的优化实现:
pub fn raindrops(n: u32) -> String {// 预分配字符串以避免多次重新分配let mut res = String::with_capacity(16);if n % 3 == 0 {res.push_str("Pling");}if n % 5 == 0 {res.push_str("Plang");}if n % 7 == 0 {res.push_str("Plong");}if res.is_empty() {// 使用更高效的数字到字符串转换itoa::Buffer::new().format(n).to_string()} else {res}
}// 使用位运算的版本
pub fn raindrops_bitwise(n: u32) -> String {let mut flags = 0u8;if n % 3 == 0 { flags |= 1; }if n % 5 == 0 { flags |= 2; }if n % 7 == 0 { flags |= 4; }match flags {0 => n.to_string(),1 => "Pling".to_string(),2 => "Plang".to_string(),3 => "PlingPlang".to_string(),4 => "Plong".to_string(),5 => "PlingPlong".to_string(),6 => "PlangPlong".to_string(),7 => "PlingPlangPlong".to_string(),_ => unreachable!(),}
}// 使用查找表的版本
static SOUNDS: [&str; 8] = ["", // 0: none"Pling", // 1: 3"Plang", // 2: 5"PlingPlang", // 3: 3,5"Plong", // 4: 7"PlingPlong", // 5: 3,7"PlangPlong", // 6: 5,7"PlingPlangPlong", // 7: 3,5,7
];pub fn raindrops_lookup(n: u32) -> String {let mut index = 0;if n % 3 == 0 { index |= 1; }if n % 5 == 0 { index |= 2; }if n % 7 == 0 { index |= 4; }if index == 0 {n.to_string()} else {SOUNDS[index].to_string()}
}
错误处理和边界情况
考虑更多边界情况的实现:
#[derive(Debug, PartialEq)]
pub enum RaindropError {ZeroInput,
}impl std::fmt::Display for RaindropError {fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {match self {RaindropError::ZeroInput => write!(f, "输入不能为0"),}}
}impl std::error::Error for RaindropError {}pub fn raindrops(n: u32) -> String {// 处理0的特殊情况if n == 0 {return "0".to_string();}let mut res = String::new();if n % 3 == 0 {res.push_str("Pling");}if n % 5 == 0 {res.push_str("Plang");}if n % 7 == 0 {res.push_str("Plong");}if res.is_empty() {n.to_string()} else {res}
}// 返回Result的版本
pub fn raindrops_safe(n: u32) -> Result<String, RaindropError> {if n == 0 {Err(RaindropError::ZeroInput)} else {Ok(raindrops(n))}
}// 支持自定义因子的版本
pub struct RaindropConverter {factors: Vec<(u32, String)>,
}impl RaindropConverter {pub fn new() -> Self {RaindropConverter {factors: vec![(3, "Pling".to_string()),(5, "Plang".to_string()),(7, "Plong".to_string()),],}}pub fn with_factors(factors: Vec<(u32, String)>) -> Self {RaindropConverter { factors }}pub fn convert(&self, n: u32) -> String {let result: String = self.factors.iter().filter(|&&(divisor, _)| n % divisor == 0).map(|&(_, ref sound)| sound.as_str()).collect();if result.is_empty() {n.to_string()} else {result}}
}
扩展功能
基于基础实现,我们可以添加更多功能:
pub struct RaindropGame {score: u32,streak: u32,
}impl RaindropGame {pub fn new() -> Self {RaindropGame {score: 0,streak: 0,}}pub fn play_round(&mut self, number: u32, player_answer: &str) -> bool {let correct_answer = raindrops(number);let is_correct = correct_answer == player_answer;if is_correct {self.score += 1;self.streak += 1;} else {self.streak = 0;}is_correct}pub fn score(&self) -> u32 {self.score}pub fn streak(&self) -> u32 {self.streak}pub fn reset(&mut self) {self.score = 0;self.streak = 0;}
}// Raindrop分析器
pub struct RaindropAnalyzer;impl RaindropAnalyzer {pub fn new() -> Self {RaindropAnalyzer}pub fn analyze(&self, n: u32) -> RaindropAnalysis {let result = raindrops(n);let factors = self.get_factors(n);let is_prime = self.is_prime(n);RaindropAnalysis {number: n,result,factors,is_prime,}}fn get_factors(&self, n: u32) -> Vec<u32> {let mut factors = Vec::new();for i in 1..=n {if n % i == 0 {factors.push(i);}}factors}fn is_prime(&self, n: u32) -> bool {if n <= 1 {return false;}if n <= 3 {return true;}if n % 2 == 0 || n % 3 == 0 {return false;}let mut i = 5;while i * i <= n {if n % i == 0 || n % (i + 2) == 0 {return false;}i += 6;}true}
}pub struct RaindropAnalysis {pub number: u32,pub result: String,pub factors: Vec<u32>,pub is_prime: bool,
}// 批量处理版本
pub fn raindrops_batch(numbers: &[u32]) -> Vec<String> {numbers.iter().map(|&n| raindrops(n)).collect()
}// 生成Raindrop序列
pub fn raindrop_sequence(start: u32, count: usize) -> Vec<String> {(start..start + count as u32).map(raindrops).collect()
}// 便利函数
pub fn raindrops(n: u32) -> String {let mut res = String::new();if n % 3 == 0 {res.push_str("Pling");}if n % 5 == 0 {res.push_str("Plang");}if n % 7 == 0 {res.push_str("Plong");}if res.is_empty() {n.to_string()} else {res}
}pub fn format_raindrop_table(start: u32, end: u32) -> String {(start..=end).map(|n| format!("{}: {}", n, raindrops(n))).collect::<Vec<_>>().join("\n")
}
实际应用场景
Raindrops 在实际开发中有以下应用:
- 教育软件:编程入门教学和练习工具
- 游戏开发:益智游戏和儿童教育游戏
- 面试题目:技术面试中的经典编程题
- 算法练习:条件判断和控制流练习
- 测试框架:单元测试和功能测试示例
- 代码示例:教学和演示中的简单示例
- 逻辑训练:编程逻辑思维训练
- 儿童应用:儿童编程启蒙应用
算法复杂度分析
-
时间复杂度:O(1)
- 只需要进行固定次数的模运算和字符串操作
-
空间复杂度:O(1)
- 只需要常数级别的额外空间存储结果
与其他实现方式的比较
// 使用递归的实现
pub fn raindrops_recursive(n: u32) -> String {fn convert(n: u32, divisors: &[(u32, &str)]) -> String {if divisors.is_empty() {return if n.to_string().is_empty() { n.to_string() } else { String::new() };}let (divisor, sound) = divisors[0];let mut result = if n % divisor == 0 { sound.to_string() } else { String::new() };result.push_str(&convert(n, &divisors[1..]));result}let divisors = [(3, "Pling"), (5, "Plang"), (7, "Plong")];let result = convert(n, &divisors);if result.is_empty() {n.to_string()} else {result}
}// 使用宏的实现
macro_rules! raindrops {($n:expr) => {{let mut res = String::new();if $n % 3 == 0 {res.push_str("Pling");}if $n % 5 == 0 {res.push_str("Plang");}if $n % 7 == 0 {res.push_str("Plong");}if res.is_empty() {res.push_str(&format!("{}", $n));}res}};
}// 使用第三方库的实现
// [dependencies]
// num = "0.4"use num::Integer;pub fn raindrops_with_num(n: u32) -> String {let mut res = String::new();if n.is_multiple_of(&3) {res.push_str("Pling");}if n.is_multiple_of(&5) {res.push_str("Plang");}if n.is_multiple_of(&7) {res.push_str("Plong");}if res.is_empty() {n.to_string()} else {res}
}// 使用函数式组合的实现
pub fn raindrops_functional(n: u32) -> String {[(3, "Pling"), (5, "Plang"), (7, "Plong")].iter().filter_map(|&(divisor, sound)| {if n % divisor == 0 {Some(sound)} else {None}}).collect::<String>().or_else(|| n.to_string())
}
总结
通过 raindrops 练习,我们学到了:
- 条件判断:掌握了使用 if 语句进行多条件判断
- 模运算:深入理解了模运算在整除性判断中的应用
- 字符串操作:学会了构建和拼接字符串
- 控制流:理解了不同控制流结构的使用场景
- 性能优化:学会了预分配内存和使用高效算法等优化技巧
- 扩展设计:理解了如何设计可扩展的系统架构
这些技能在实际开发中非常有用,特别是在教育软件、游戏开发、算法练习等场景中。Raindrops虽然是一个简单的编程练习,但它涉及到了条件判断、模运算、字符串操作、控制流等许多核心概念,是学习Rust入门编程的良好起点。
通过这个练习,我们也看到了Rust在简单算法实现方面的简洁性和高效性,以及如何用安全且高效的方式实现经典的编程问题。这种结合了安全性和性能的语言特性正是Rust的魅力所在。
