在编程中,FizzBuzz是一个经典的练习题目,用于测试基本的编程能力。然而,在Rust中实现一个通用且灵活的FizzBuzz解决方案涉及到了泛型编程和trait约束的高级概念。在 Exercism 的 “fizzy” 练习中,我们需要构建一个可扩展的FizzBuzz框架,支持自定义规则和多种数据类型。这不仅能帮助我们掌握泛型编程,还能深入学习Rust中的trait系统和迭代器链。

什么是Fizzy?

Fizzy是FizzBuzz问题的一个泛型实现,它允许我们:

  1. 定义匹配规则(Matcher)
  2. 组合多个规则形成FizzBuzz处理器(Fizzy)
  3. 将规则应用到任意迭代器上

经典的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. 核心组件

  1. Matcher - 匹配器,定义单个规则
  2. Fizzy - FizzBuzz处理器,组合多个规则
  3. 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在实际开发中有以下应用:

  1. 规则引擎:构建简单的业务规则处理器
  2. 数据验证:对数据流应用多种验证规则
  3. 文本处理:对文本应用多种替换规则
  4. 游戏开发:实现游戏中的条件触发系统
  5. 配置处理:根据配置规则处理数据
  6. 日志处理:对日志条目应用过滤和标记规则
  7. 数据转换:在ETL流程中应用转换规则

算法复杂度分析

  1. 时间复杂度

    • 规则应用:O(m×n),其中m是规则数量,n是元素数量
    • 单个元素处理:O(m)
  2. 空间复杂度

    • 存储规则: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 练习,我们学到了:

  1. 泛型编程:掌握了Rust中泛型的使用方法
  2. trait约束:理解了如何为泛型类型添加约束
  3. 闭包使用:学会了将函数作为数据存储和传递
  4. 迭代器链:熟练使用迭代器进行数据处理
  5. 动态分发:了解了trait对象的使用场景
  6. 组合模式:理解了如何构建可组合的系统

这些技能在实际开发中非常有用,特别是在构建可扩展的框架、规则引擎和数据处理管道时。Fizzy虽然是基于FizzBuzz这个简单问题的练习,但它涉及到了泛型编程、trait系统和函数式编程等许多核心概念,是学习Rust高级特性的良好起点。

通过这个练习,我们也看到了Rust在构建灵活且类型安全的API方面的强大能力,以及如何用安全且高效的方式实现复杂的系统。这种结合了安全性和性能的语言特性正是Rust的魅力所在。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐