Rust 练习册 :Fizzy与泛型约束
在编程中,FizzBuzz是一个经典的练习题目,用于测试基本的编程能力。然而,在Rust中实现一个通用且灵活的FizzBuzz解决方案涉及到了泛型编程和trait约束的高级概念。在 Exercism 的 “fizzy” 练习中,我们需要构建一个可扩展的FizzBuzz框架,支持自定义规则和多种数据类型。这不仅能帮助我们掌握泛型编程,还能深入学习Rust中的trait系统和迭代器链。
什么是Fizzy?
Fizzy是FizzBuzz问题的一个泛型实现,它允许我们:
- 定义匹配规则(Matcher)
- 组合多个规则形成FizzBuzz处理器(Fizzy)
- 将规则应用到任意迭代器上
经典的FizzBuzz规则是:
- 能被3整除的数替换为"Fizz"
- 能被5整除的数替换为"Buzz"
- 能被3和5同时整除的数替换为"FizzBuzz"
- 其他数保持原样
让我们先看看练习提供的结构和函数签名:
// the PhantomData instances in this file are just to stop compiler complaints
// about missing generics; feel free to remove them
/// A Matcher is a single rule of fizzbuzz: given a function on T, should
/// a word be substituted in? If yes, which word?
pub struct Matcher<T>(std::marker::PhantomData<T>);
impl<T> Matcher<T> {
pub fn new<F, S>(_matcher: F, _subs: S) -> Matcher<T> {
unimplemented!()
}
}
/// A Fizzy is a set of matchers, which may be applied to an iterator.
///
/// Strictly speaking, it's usually more idiomatic to use `iter.map()` than to
/// consume an iterator with an `apply` method. Given a Fizzy instance, it's
/// pretty straightforward to construct a closure which applies it to all
/// elements of the iterator. However, we're using the `apply` pattern
/// here because it's a simpler interface for students to implement.
///
/// Also, it's a good excuse to try out using impl trait.
pub struct Fizzy<T>(std::marker::PhantomData<T>);
impl<T> Fizzy<T> {
pub fn new() -> Self {
unimplemented!()
}
// feel free to change the signature to `mut self` if you like
pub fn add_matcher(self, _matcher: Matcher<T>) -> Self {
unimplemented!()
}
/// map this fizzy onto every element of an iterator, returning a new iterator
pub fn apply<I>(self, _iter: I) -> impl Iterator<Item = String> {
// unimplemented!() doesn't actually work, here; () is not an Iterator
// that said, this is probably not the actual implementation you desire
Vec::new().into_iter()
}
}
/// convenience function: return a Fizzy which applies the standard fizz-buzz rules
pub fn fizz_buzz<T>() -> Fizzy<T> {
unimplemented!()
}
我们需要实现一个完整的泛型FizzBuzz框架,支持自定义规则和多种数据类型。
设计分析
1. 核心组件
- Matcher - 匹配器,定义单个规则
- Fizzy - FizzBuzz处理器,组合多个规则
- fizz_buzz() - 便利函数,创建标准FizzBuzz规则
2. 泛型约束
为了支持不同数据类型,我们需要适当的trait约束:
- Display trait:用于将数字转换为字符串
- Rem trait:用于取模运算
- PartialEq trait:用于比较结果
- From trait:用于创建常量值
完整实现
1. Matcher实现
use std::fmt::Display;
use std::ops::Rem;
/// A Matcher is a single rule of fizzbuzz: given a function on T, should
/// a word be substituted in? If yes, which word?
pub struct Matcher<T> {
matcher: Box<dyn Fn(T) -> bool>,
subs: String,
}
impl<T> Matcher<T> {
pub fn new<F, S>(matcher: F, subs: S) -> Matcher<T>
where
F: Fn(T) -> bool + 'static,
S: Display,
{
Matcher {
matcher: Box::new(matcher),
subs: subs.to_string(),
}
}
}
2. Fizzy实现
/// A Fizzy is a set of matchers, which may be applied to an iterator.
pub struct Fizzy<T> {
matchers: Vec<Matcher<T>>,
}
impl<T> Fizzy<T> {
pub fn new() -> Self {
Fizzy {
matchers: Vec::new(),
}
}
// feel free to change the signature to `mut self` if you like
pub fn add_matcher(mut self, matcher: Matcher<T>) -> Self {
self.matchers.push(matcher);
self
}
/// map this fizzy onto every element of an iterator, returning a new iterator
pub fn apply<I>(self, iter: I) -> impl Iterator<Item = String>
where
I: Iterator<Item = T>,
T: Copy + Display,
{
iter.map(move |item| {
let mut result = String::new();
for matcher in &self.matchers {
if (matcher.matcher)(item) {
result.push_str(&matcher.subs);
}
}
if result.is_empty() {
item.to_string()
} else {
result
}
})
}
}
/// convenience function: return a Fizzy which applies the standard fizz-buzz rules
pub fn fizz_buzz<T>() -> Fizzy<T>
where
T: Copy + Display + PartialEq + From<u8> + Rem<Output = T>,
{
let three: T = 3.into();
let five: T = 5.into();
let zero: T = 0.into();
Fizzy::new()
.add_matcher(Matcher::new(
move |n: T| n % three == zero,
"fizz",
))
.add_matcher(Matcher::new(
move |n: T| n % five == zero,
"buzz",
))
}
测试用例分析
通过查看测试用例,我们可以更好地理解需求:
#[test]
fn test_simple() {
let got = fizz_buzz::<i32>().apply(1..=16).collect::<Vec<_>>();
assert_eq!(expect!(), got);
}
标准FizzBuzz规则应该正确应用到整数序列。
#[test]
fn test_u8() {
let got = fizz_buzz::<u8>().apply(1_u8..=16).collect::<Vec<_>>();
assert_eq!(expect!(), got);
}
应该支持u8类型。
#[test]
fn test_u64() {
let got = fizz_buzz::<u64>().apply(1_u64..=16).collect::<Vec<_>>();
assert_eq!(expect!(), got);
}
应该支持u64类型。
#[test]
fn test_nonsequential() {
let collatz_12 = &[12, 6, 3, 10, 5, 16, 8, 4, 2, 1];
let expect = vec![
"fizz", "fizz", "fizz", "buzz", "buzz", "16", "8", "4", "2", "1",
];
let got = fizz_buzz::<i32>()
.apply(collatz_12.iter().cloned())
.collect::<Vec<_>>();
assert_eq!(expect, got);
}
应该能处理非连续序列。
#[test]
fn test_custom() {
let expect = vec![
"1", "2", "Fizz", "4", "Buzz", "Fizz", "Bam", "8", "Fizz", "Buzz", "11", "Fizz", "13",
"Bam", "BuzzFizz", "16",
];
let fizzer: Fizzy<i32> = Fizzy::new()
.add_matcher(Matcher::new(|n: i32| n % 5 == 0, "Buzz"))
.add_matcher(Matcher::new(|n: i32| n % 3 == 0, "Fizz"))
.add_matcher(Matcher::new(|n: i32| n % 7 == 0, "Bam"));
let got = fizzer.apply(1..=16).collect::<Vec<_>>();
assert_eq!(expect, got);
}
应该支持自定义规则。
#[test]
fn test_f64() {
let got = fizz_buzz::<f64>()
.apply(std::iter::successors(Some(1.0), |prev| Some(prev + 1.0)))
.take(16)
.collect::<Vec<_>>();
assert_eq!(expect!(), got);
}
应该支持浮点数类型。
#[test]
fn test_minimal_generic_bounds() {
use std::fmt;
use std::ops::{Add, Rem};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
struct Fizzable(u8);
impl From<u8> for Fizzable {
fn from(i: u8) -> Fizzable {
Fizzable(i)
}
}
impl fmt::Display for Fizzable {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let Fizzable(ref n) = self;
write!(f, "{}", n)
}
}
impl Add for Fizzable {
type Output = Fizzable;
fn add(self, rhs: Fizzable) -> Fizzable {
let Fizzable(n1) = self;
let Fizzable(n2) = rhs;
Fizzable(n1 + n2)
}
}
impl Rem for Fizzable {
type Output = Fizzable;
fn rem(self, rhs: Fizzable) -> Fizzable {
let Fizzable(n1) = self;
let Fizzable(n2) = rhs;
Fizzable(n1 % n2)
}
}
let got = fizz_buzz::<Fizzable>()
.apply(std::iter::successors(Some(Fizzable(1)), |prev| {
Some(*prev + 1.into())
}))
.take(16)
.collect::<Vec<_>>();
assert_eq!(expect!(), got);
}
应该支持自定义类型,只要满足必要的trait约束。
性能优化版本
考虑性能的优化实现:
use std::fmt::Display;
use std::ops::Rem;
/// A Matcher is a single rule of fizzbuzz: given a function on T, should
/// a word be substituted in? If yes, which word?
pub struct Matcher<T> {
matcher: Box<dyn Fn(T) -> bool + Send + Sync>,
subs: String,
}
impl<T> Matcher<T> {
pub fn new<F, S>(matcher: F, subs: S) -> Matcher<T>
where
F: Fn(T) -> bool + Send + Sync + 'static,
S: Display,
{
Matcher {
matcher: Box::new(matcher),
subs: subs.to_string(),
}
}
}
/// A Fizzy is a set of matchers, which may be applied to an iterator.
pub struct Fizzy<T> {
matchers: Vec<Matcher<T>>,
}
impl<T> Fizzy<T> {
pub fn new() -> Self {
Fizzy {
matchers: Vec::new(),
}
}
pub fn add_matcher(mut self, matcher: Matcher<T>) -> Self {
self.matchers.push(matcher);
self
}
/// map this fizzy onto every element of an iterator, returning a new iterator
pub fn apply<I>(self, iter: I) -> impl Iterator<Item = String>
where
I: Iterator<Item = T>,
T: Copy + Display,
{
iter.map(move |item| {
let mut result = String::with_capacity(32); // 预分配容量
for matcher in &self.matchers {
if (matcher.matcher)(item) {
result.push_str(&matcher.subs);
}
}
if result.is_empty() {
item.to_string()
} else {
result
}
})
}
}
/// convenience function: return a Fizzy which applies the standard fizz-buzz rules
pub fn fizz_buzz<T>() -> Fizzy<T>
where
T: Copy + Display + PartialEq + From<u8> + Rem<Output = T>,
{
let three: T = 3.into();
let five: T = 5.into();
let zero: T = 0.into();
Fizzy::new()
.add_matcher(Matcher::new(
move |n: T| n % three == zero,
"fizz",
))
.add_matcher(Matcher::new(
move |n: T| n % five == zero,
"buzz",
))
}
错误处理和边界情况
考虑更多边界情况的实现:
use std::fmt::Display;
use std::ops::Rem;
#[derive(Debug, PartialEq)]
pub enum FizzyError {
NoRules,
InvalidInput,
}
/// A Matcher is a single rule of fizzbuzz: given a function on T, should
/// a word be substituted in? If yes, which word?
pub struct Matcher<T> {
matcher: Box<dyn Fn(T) -> bool + Send + Sync>,
subs: String,
}
impl<T> Matcher<T> {
pub fn new<F, S>(matcher: F, subs: S) -> Matcher<T>
where
F: Fn(T) -> bool + Send + Sync + 'static,
S: Display,
{
Matcher {
matcher: Box::new(matcher),
subs: subs.to_string(),
}
}
}
/// A Fizzy is a set of matchers, which may be applied to an iterator.
pub struct Fizzy<T> {
matchers: Vec<Matcher<T>>,
}
impl<T> Fizzy<T> {
pub fn new() -> Self {
Fizzy {
matchers: Vec::new(),
}
}
pub fn add_matcher(mut self, matcher: Matcher<T>) -> Self {
self.matchers.push(matcher);
self
}
/// Check if the Fizzy has any rules
pub fn is_empty(&self) -> bool {
self.matchers.is_empty()
}
/// Get the number of rules
pub fn len(&self) -> usize {
self.matchers.len()
}
/// map this fizzy onto every element of an iterator, returning a new iterator
pub fn apply<I>(self, iter: I) -> impl Iterator<Item = String>
where
I: Iterator<Item = T>,
T: Copy + Display,
{
iter.map(move |item| {
let mut result = String::new();
for matcher in &self.matchers {
if (matcher.matcher)(item) {
result.push_str(&matcher.subs);
}
}
if result.is_empty() {
item.to_string()
} else {
result
}
})
}
}
/// convenience function: return a Fizzy which applies the standard fizz-buzz rules
pub fn fizz_buzz<T>() -> Fizzy<T>
where
T: Copy + Display + PartialEq + From<u8> + Rem<Output = T>,
{
let three: T = 3.into();
let five: T = 5.into();
let zero: T = 0.into();
Fizzy::new()
.add_matcher(Matcher::new(
move |n: T| n % three == zero,
"fizz",
))
.add_matcher(Matcher::new(
move |n: T| n % five == zero,
"buzz",
))
}
// 带错误处理的版本
impl<T> Fizzy<T> {
pub fn apply_safe<I, E>(self, iter: I) -> Result<impl Iterator<Item = String>, FizzyError>
where
I: Iterator<Item = Result<T, E>>,
T: Copy + Display,
{
if self.matchers.is_empty() {
return Err(FizzyError::NoRules);
}
Ok(iter.map(move |item_result| {
match item_result {
Ok(item) => {
let mut result = String::new();
for matcher in &self.matchers {
if (matcher.matcher)(item) {
result.push_str(&matcher.subs);
}
}
if result.is_empty() {
item.to_string()
} else {
result
}
}
Err(_) => "error".to_string(),
}
}))
}
}
扩展功能
基于基础实现,我们可以添加更多功能:
use std::fmt::Display;
use std::ops::Rem;
/// A Matcher is a single rule of fizzbuzz: given a function on T, should
/// a word be substituted in? If yes, which word?
pub struct Matcher<T> {
matcher: Box<dyn Fn(T) -> bool + Send + Sync>,
subs: String,
}
impl<T> Matcher<T> {
pub fn new<F, S>(matcher: F, subs: S) -> Matcher<T>
where
F: Fn(T) -> bool + Send + Sync + 'static,
S: Display,
{
Matcher {
matcher: Box::new(matcher),
subs: subs.to_string(),
}
}
}
/// A Fizzy is a set of matchers, which may be applied to an iterator.
pub struct Fizzy<T> {
matchers: Vec<Matcher<T>>,
}
impl<T> Fizzy<T> {
pub fn new() -> Self {
Fizzy {
matchers: Vec::new(),
}
}
pub fn add_matcher(mut self, matcher: Matcher<T>) -> Self {
self.matchers.push(matcher);
self
}
pub fn add_matchers(mut self, matchers: Vec<Matcher<T>>) -> Self {
self.matchers.extend(matchers);
self
}
/// map this fizzy onto every element of an iterator, returning a new iterator
pub fn apply<I>(self, iter: I) -> impl Iterator<Item = String>
where
I: Iterator<Item = T>,
T: Copy + Display,
{
iter.map(move |item| {
let mut result = String::new();
for matcher in &self.matchers {
if (matcher.matcher)(item) {
result.push_str(&matcher.subs);
}
}
if result.is_empty() {
item.to_string()
} else {
result
}
})
}
// 创建FizzBuzz规则的工厂方法
pub fn fizz_buzz() -> Self
where
T: Copy + Display + PartialEq + From<u8> + Rem<Output = T>,
{
let three: T = 3.into();
let five: T = 5.into();
let zero: T = 0.into();
Fizzy::new()
.add_matcher(Matcher::new(
move |n: T| n % three == zero,
"fizz",
))
.add_matcher(Matcher::new(
move |n: T| n % five == zero,
"buzz",
))
}
// 创建自定义规则的工厂方法
pub fn custom() -> Self {
Fizzy::new()
}
// 获取规则数量
pub fn len(&self) -> usize {
self.matchers.len()
}
// 检查是否有规则
pub fn is_empty(&self) -> bool {
self.matchers.is_empty()
}
// 清空所有规则
pub fn clear(self) -> Self {
Fizzy {
matchers: Vec::new(),
}
}
}
/// convenience function: return a Fizzy which applies the standard fizz-buzz rules
pub fn fizz_buzz<T>() -> Fizzy<T>
where
T: Copy + Display + PartialEq + From<u8> + Rem<Output = T>,
{
Fizzy::<T>::fizz_buzz()
}
// 支持链式调用的扩展
pub struct FizzyBuilder<T> {
fizzy: Fizzy<T>,
}
impl<T> FizzyBuilder<T> {
pub fn new() -> Self {
FizzyBuilder {
fizzy: Fizzy::new(),
}
}
pub fn with_fizz_buzz_rules(mut self) -> Self
where
T: Copy + Display + PartialEq + From<u8> + Rem<Output = T>,
{
self.fizzy = fizz_buzz::<T>();
self
}
pub fn with_matcher(mut self, matcher: Matcher<T>) -> Self {
self.fizzy = self.fizzy.add_matcher(matcher);
self
}
pub fn build(self) -> Fizzy<T> {
self.fizzy
}
}
// 支持收集到不同容器的扩展
impl<T> Fizzy<T> {
pub fn apply_and_collect<I, C>(self, iter: I) -> C
where
I: Iterator<Item = T>,
T: Copy + Display,
C: FromIterator<String>,
{
self.apply(iter).collect()
}
}
实际应用场景
Fizzy在实际开发中有以下应用:
- 规则引擎:构建简单的业务规则处理器
- 数据验证:对数据流应用多种验证规则
- 文本处理:对文本应用多种替换规则
- 游戏开发:实现游戏中的条件触发系统
- 配置处理:根据配置规则处理数据
- 日志处理:对日志条目应用过滤和标记规则
- 数据转换:在ETL流程中应用转换规则
算法复杂度分析
-
时间复杂度:
- 规则应用:O(m×n),其中m是规则数量,n是元素数量
- 单个元素处理:O(m)
-
空间复杂度:
- 存储规则:O(m)
- 输出结果:O(n×s),其中s是平均字符串长度
与其他实现方式的比较
// 使用宏实现的FizzBuzz
macro_rules! fizz_buzz_macro {
($n:expr) => {
if $n % 15 == 0 {
"fizzbuzz".to_string()
} else if $n % 3 == 0 {
"fizz".to_string()
} else if $n % 5 == 0 {
"buzz".to_string()
} else {
$n.to_string()
}
};
}
// 使用枚举的简单实现
#[derive(Debug, Clone)]
enum FizzBuzzResult {
Number(i32),
Fizz,
Buzz,
FizzBuzz,
}
impl std::fmt::Display for FizzBuzzResult {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
FizzBuzzResult::Number(n) => write!(f, "{}", n),
FizzBuzzResult::Fizz => write!(f, "fizz"),
FizzBuzzResult::Buzz => write!(f, "buzz"),
FizzBuzzResult::FizzBuzz => write!(f, "fizzbuzz"),
}
}
}
fn fizz_buzz_simple(n: i32) -> FizzBuzzResult {
match (n % 3, n % 5) {
(0, 0) => FizzBuzzResult::FizzBuzz,
(0, _) => FizzBuzzResult::Fizz,
(_, 0) => FizzBuzzResult::Buzz,
_ => FizzBuzzResult::Number(n),
}
}
// 使用函数式组合的实现
use std::ops::Rem;
fn create_fizz_buzz_rule<T>() -> Box<dyn Fn(T) -> String>
where
T: Copy + std::fmt::Display + PartialEq + From<u8> + Rem<Output = T>,
{
let three: T = 3.into();
let five: T = 5.into();
let zero: T = 0.into();
Box::new(move |n: T| {
let mut result = String::new();
if n % three == zero {
result.push_str("fizz");
}
if n % five == zero {
result.push_str("buzz");
}
if result.is_empty() {
n.to_string()
} else {
result
}
})
}
总结
通过 fizzy 练习,我们学到了:
- 泛型编程:掌握了Rust中泛型的使用方法
- trait约束:理解了如何为泛型类型添加约束
- 闭包使用:学会了将函数作为数据存储和传递
- 迭代器链:熟练使用迭代器进行数据处理
- 动态分发:了解了trait对象的使用场景
- 组合模式:理解了如何构建可组合的系统
这些技能在实际开发中非常有用,特别是在构建可扩展的框架、规则引擎和数据处理管道时。Fizzy虽然是基于FizzBuzz这个简单问题的练习,但它涉及到了泛型编程、trait系统和函数式编程等许多核心概念,是学习Rust高级特性的良好起点。
通过这个练习,我们也看到了Rust在构建灵活且类型安全的API方面的强大能力,以及如何用安全且高效的方式实现复杂的系统。这种结合了安全性和性能的语言特性正是Rust的魅力所在。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)