Rust 练习册 :Proverb与字符串处理

"Proverb"(谚语)练习源自一句经典的英文谚语:"For want of a nail the shoe was lost",表达了一个小小的缺失可能导致连锁反应,最终造成重大损失的道理。在 Exercism 的 "proverb" 练习中,我们需要根据给定的词汇列表生成这个谚语的完整形式。这不仅能帮助我们掌握字符串处理和迭代器操作技巧,还能深入学习Rust中的字符串拼接、模式匹配和函数式编程。

什么是 Proverb 谚语?

"For want of a nail" 是一个经典的英语谚语,用来说明小问题如何引发连锁反应导致大问题。完整的谚语形式如下:

复制代码
For want of a nail the shoe was lost.
For want of a shoe the horse was lost.
For want of a horse the rider was lost.
For want of a rider the battle was lost.
For want of a battle the kingdom was lost.
And all for the want of a nail.

这个谚语说明了因为缺少一个钉子,最终导致整个王国的沦陷,强调了细节的重要性。

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

rust 复制代码
pub fn build_proverb(list: &[&str]) -> String {
    if list.is_empty() {
        return String::new();
    }

    let res = list
        .windows(2)
        .map(|x| format!("For want of a {} the {} was lost.", x[0], x[1]))
        .chain(std::iter::once(format!(
            "And all for the want of a {}.",
            list[0]
        )))
        .collect::<Vec<_>>();

    res.join("\n")
}

这是一个使用Rust迭代器链的优雅实现,通过[windows](file:///Users/zacksleo/projects/github/zacksleo/exercism-rust/exercises/practice/docs/rust-exercise-62-proverb.md#struct.Windows)方法创建相邻元素对,然后使用[map](file:///Users/zacksleo/projects/github/zacksleo/exercism-rust/exercises/practice/docs/rust-exercise-62-proverb.md#impl-IoSlice.pub%20const%20fn%20new(buf:%20%27a%20[u8])%20->%20IoSlice%27a_)转换为谚语句子,最后添加总结句。

设计分析

1. 核心要求

  1. 字符串格式化:正确格式化谚语句子
  2. 相邻元素处理:处理列表中相邻的元素对
  3. 总结句生成:生成总结性的最后一句
  4. 边界处理:正确处理空列表和单元素列表

2. 技术要点

  1. 迭代器链:使用Rust强大的迭代器功能
  2. 字符串拼接:高效地拼接多行字符串
  3. 模式匹配:处理不同的输入情况
  4. 函数式编程:使用函数式方法处理数据转换

完整实现

1. 基础迭代器实现

rust 复制代码
pub fn build_proverb(list: &[&str]) -> String {
    if list.is_empty() {
        return String::new();
    }

    let res = list
        .windows(2)
        .map(|x| format!("For want of a {} the {} was lost.", x[0], x[1]))
        .chain(std::iter::once(format!(
            "And all for the want of a {}.",
            list[0]
        )))
        .collect::<Vec<_>>();

    res.join("\n")
}

2. 使用String.push_str的实现

rust 复制代码
pub fn build_proverb(list: &[&str]) -> String {
    if list.is_empty() {
        return String::new();
    }
    
    if list.len() == 1 {
        return format!("And all for the want of a {}.", list[0]);
    }
    
    let mut result = String::new();
    
    // 生成中间句子
    for i in 0..list.len() - 1 {
        if !result.is_empty() {
            result.push('\n');
        }
        result.push_str(&format!("For want of a {} the {} was lost.", list[i], list[i + 1]));
    }
    
    // 添加总结句
    result.push('\n');
    result.push_str(&format!("And all for the want of a {}.", list[0]));
    
    result
}

3. 使用fold的函数式实现

rust 复制代码
pub fn build_proverb(list: &[&str]) -> String {
    if list.is_empty() {
        return String::new();
    }
    
    if list.len() == 1 {
        return format!("And all for the want of a {}.", list[0]);
    }
    
    let main_lines: Vec<String> = list
        .windows(2)
        .map(|pair| format!("For want of a {} the {} was lost.", pair[0], pair[1]))
        .collect();
    
    let summary_line = format!("And all for the want of a {}.", list[0]);
    
    main_lines
        .iter()
        .chain(std::iter::once(&summary_line))
        .fold(String::new(), |acc, line| {
            if acc.is_empty() {
                line.clone()
            } else {
                acc + "\n" + line
            }
        })
}

测试用例分析

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

rust 复制代码
#[test]
fn test_two_pieces() {
    let input = vec!["nail", "shoe"];
    let expected = vec![
        "For want of a nail the shoe was lost.",
        "And all for the want of a nail.",
    ]
    .join("\n");
    assert_eq!(build_proverb(&input), expected);
}

两个元素的列表应该生成一行主句和一行总结句。

rust 复制代码
#[test]
fn test_three_pieces() {
    let input = vec!["nail", "shoe", "horse"];
    let expected = vec![
        "For want of a nail the shoe was lost.",
        "For want of a shoe the horse was lost.",
        "And all for the want of a nail.",
    ]
    .join("\n");
    assert_eq!(build_proverb(&input), expected);
}

三个元素的列表应该生成两行主句和一行总结句。

rust 复制代码
#[test]
fn test_one_piece() {
    let input = vec!["nail"];
    let expected = String::from("And all for the want of a nail.");
    assert_eq!(build_proverb(&input), expected);
}

单个元素的列表应该只生成总结句。

rust 复制代码
#[test]
fn test_zero_pieces() {
    let input: Vec<&str> = vec![];
    let expected = String::new();
    assert_eq!(build_proverb(&input), expected);
}

空列表应该返回空字符串。

rust 复制代码
#[test]
fn test_full() {
    let input = vec![
        "nail", "shoe", "horse", "rider", "message", "battle", "kingdom",
    ];
    let expected = vec![
        "For want of a nail the shoe was lost.",
        "For want of a shoe the horse was lost.",
        "For want of a horse the rider was lost.",
        "For want of a rider the message was lost.",
        "For want of a message the battle was lost.",
        "For want of a battle the kingdom was lost.",
        "And all for the want of a nail.",
    ]
    .join("\n");
    assert_eq!(build_proverb(&input), expected);
}

完整的谚语应该正确生成所有句子。

rust 复制代码
#[test]
fn test_three_pieces_modernized() {
    let input = vec!["pin", "gun", "soldier", "battle"];
    let expected = vec![
        "For want of a pin the gun was lost.",
        "For want of a gun the soldier was lost.",
        "For want of a soldier the battle was lost.",
        "And all for the want of a pin.",
    ]
    .join("\n");
    assert_eq!(build_proverb(&input), expected);
}

不同的词汇列表也应该正确处理。

性能优化版本

考虑性能的优化实现:

rust 复制代码
pub fn build_proverb(list: &[&str]) -> String {
    if list.is_empty() {
        return String::new();
    }
    
    if list.len() == 1 {
        return format!("And all for the want of a {}.", list[0]);
    }
    
    // 预估结果字符串的容量以减少重新分配
    let estimated_capacity = list.iter().map(|s| s.len()).sum::<usize>() * 10 + list.len() * 50;
    let mut result = String::with_capacity(estimated_capacity);
    
    // 生成主句
    for window in list.windows(2) {
        if !result.is_empty() {
            result.push('\n');
        }
        result.push_str("For want of a ");
        result.push_str(window[0]);
        result.push_str(" the ");
        result.push_str(window[1]);
        result.push_str(" was lost.");
    }
    
    // 添加总结句
    result.push('\n');
    result.push_str("And all for the want of a ");
    result.push_str(list[0]);
    result.push('.');
    
    result
}

// 使用格式化参数的版本
pub fn build_proverb_formatted(list: &[&str]) -> String {
    if list.is_empty() {
        return String::new();
    }
    
    if list.len() == 1 {
        return format!("And all for the want of a {}.", list[0]);
    }
    
    let mut lines: Vec<String> = Vec::with_capacity(list.len());
    
    // 生成主句
    for window in list.windows(2) {
        lines.push(format!("For want of a {} the {} was lost.", window[0], window[1]));
    }
    
    // 添加总结句
    lines.push(format!("And all for the want of a {}.", list[0]));
    
    lines.join("\n")
}

// 使用write!宏的版本
use std::fmt::Write;

pub fn build_proverb_write(list: &[&str]) -> String {
    if list.is_empty() {
        return String::new();
    }
    
    let mut result = String::new();
    
    // 生成主句
    for window in list.windows(2) {
        if !result.is_empty() {
            result.push('\n');
        }
        write!(result, "For want of a {} the {} was lost.", window[0], window[1])
            .expect("Failed to write to string");
    }
    
    // 添加总结句
    if !list.windows(2).count() > 0 {
        result.push('\n');
    }
    write!(result, "And all for the want of a {}.", list[0])
        .expect("Failed to write to string");
    
    result
}

错误处理和边界情况

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

rust 复制代码
pub fn build_proverb(list: &[&str]) -> String {
    // 处理空列表
    if list.is_empty() {
        return String::new();
    }
    
    // 处理只包含空字符串的列表
    if list.iter().all(|&s| s.is_empty()) {
        return String::new();
    }
    
    // 过滤掉空字符串
    let non_empty_list: Vec<&str> = list.iter().filter(|&&s| !s.is_empty()).copied().collect();
    
    if non_empty_list.is_empty() {
        return String::new();
    }
    
    if non_empty_list.len() == 1 {
        return format!("And all for the want of a {}.", non_empty_list[0]);
    }
    
    let mut result = String::new();
    
    // 生成主句
    for window in non_empty_list.windows(2) {
        if !result.is_empty() {
            result.push('\n');
        }
        result.push_str(&format!("For want of a {} the {} was lost.", window[0], window[1]));
    }
    
    // 添加总结句
    result.push('\n');
    result.push_str(&format!("And all for the want of a {}.", non_empty_list[0]));
    
    result
}

// 返回Result的版本
#[derive(Debug, PartialEq)]
pub enum ProverbError {
    EmptyList,
    EmptyWords,
}

impl std::fmt::Display for ProverbError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            ProverbError::EmptyList => write!(f, "列表为空"),
            ProverbError::EmptyWords => write!(f, "列表中包含空词汇"),
        }
    }
}

impl std::error::Error for ProverbError {}

pub fn build_proverb_safe(list: &[&str]) -> Result<String, ProverbError> {
    if list.is_empty() {
        return Err(ProverbError::EmptyList);
    }
    
    if list.iter().any(|&s| s.is_empty()) {
        return Err(ProverbError::EmptyWords);
    }
    
    Ok(build_proverb(list))
}

// 支持自定义格式的版本
pub struct ProverbBuilder {
    prefix: String,
    suffix: String,
    separator: String,
    summary_prefix: String,
    summary_suffix: String,
}

impl ProverbBuilder {
    pub fn new() -> Self {
        ProverbBuilder {
            prefix: "For want of a ".to_string(),
            suffix: " was lost.".to_string(),
            separator: " the ".to_string(),
            summary_prefix: "And all for the want of a ".to_string(),
            summary_suffix: ".".to_string(),
        }
    }
    
    pub fn with_prefix(mut self, prefix: &str) -> Self {
        self.prefix = prefix.to_string();
        self
    }
    
    pub fn with_suffix(mut self, suffix: &str) -> Self {
        self.suffix = suffix.to_string();
        self
    }
    
    pub fn with_separator(mut self, separator: &str) -> Self {
        self.separator = separator.to_string();
        self
    }
    
    pub fn with_summary_prefix(mut self, prefix: &str) -> Self {
        self.summary_prefix = prefix.to_string();
        self
    }
    
    pub fn with_summary_suffix(mut self, suffix: &str) -> Self {
        self.summary_suffix = suffix.to_string();
        self
    }
    
    pub fn build(&self, list: &[&str]) -> String {
        if list.is_empty() {
            return String::new();
        }
        
        if list.len() == 1 {
            return format!("{}{}{}", self.summary_prefix, list[0], self.summary_suffix);
        }
        
        let mut result = String::new();
        
        // 生成主句
        for window in list.windows(2) {
            if !result.is_empty() {
                result.push('\n');
            }
            result.push_str(&format!("{}{}{}{}{}", 
                                    self.prefix, 
                                    window[0], 
                                    self.separator, 
                                    window[1], 
                                    self.suffix));
        }
        
        // 添加总结句
        result.push('\n');
        result.push_str(&format!("{}{}{}", self.summary_prefix, list[0], self.summary_suffix));
        
        result
    }
}

扩展功能

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

rust 复制代码
pub struct Proverb {
    words: Vec<String>,
}

impl Proverb {
    pub fn new(words: Vec<&str>) -> Self {
        Proverb {
            words: words.into_iter().map(|s| s.to_string()).collect(),
        }
    }
    
    pub fn build(&self) -> String {
        build_proverb(&self.words.iter().map(|s| s.as_str()).collect::<Vec<_>>())
    }
    
    // 获取词汇列表
    pub fn words(&self) -> &[String] {
        &self.words
    }
    
    // 添加词汇
    pub fn add_word(&mut self, word: &str) {
        self.words.push(word.to_string());
    }
    
    // 获取谚语行数
    pub fn line_count(&self) -> usize {
        if self.words.is_empty() {
            0
        } else {
            self.words.len()
        }
    }
    
    // 获取特定行
    pub fn line(&self, index: usize) -> Option<String> {
        if self.words.is_empty() {
            return None;
        }
        
        if index >= self.words.len() {
            return None;
        }
        
        if self.words.len() == 1 {
            return Some(format!("And all for the want of a {}.", self.words[0]));
        }
        
        if index == self.words.len() - 1 {
            // 最后一行是总结句
            Some(format!("And all for the want of a {}.", self.words[0]))
        } else if index < self.words.len() - 1 {
            // 中间行是主句
            Some(format!("For want of a {} the {} was lost.", 
                        self.words[index], 
                        self.words[index + 1]))
        } else {
            None
        }
    }
    
    // 反向构建谚语(从后往前)
    pub fn build_reverse(&self) -> String {
        if self.words.is_empty() {
            return String::new();
        }
        
        if self.words.len() == 1 {
            return format!("And all for the want of a {}.", self.words[0]);
        }
        
        let mut lines: Vec<String> = Vec::with_capacity(self.words.len());
        
        // 生成主句(反向)
        for i in (0..self.words.len() - 1).rev() {
            lines.push(format!("For want of a {} the {} was lost.", 
                              self.words[i + 1], 
                              self.words[i]));
        }
        
        // 添加总结句
        lines.push(format!("And all for the want of a {}.", self.words[self.words.len() - 1]));
        
        lines.join("\n")
    }
}

// 谚语分析器
pub struct ProverbAnalysis {
    pub word_count: usize,
    pub line_count: usize,
    pub character_count: usize,
    pub first_word: Option<String>,
    pub last_word: Option<String>,
}

impl Proverb {
    pub fn analyze(&self) -> ProverbAnalysis {
        let word_count = self.words.len();
        let line_count = self.line_count();
        let character_count = self.build().chars().count();
        let first_word = self.words.first().cloned();
        let last_word = self.words.last().cloned();
        
        ProverbAnalysis {
            word_count,
            line_count,
            character_count,
            first_word,
            last_word,
        }
    }
}

// 多语言支持
pub struct MultiLanguageProverbBuilder {
    translations: std::collections::HashMap<&'static str, ProverbTemplate>,
}

pub struct ProverbTemplate {
    line_template: String,          // "For want of a {} the {} was lost."
    summary_template: String,       // "And all for the want of a {}."
}

impl MultiLanguageProverbBuilder {
    pub fn new() -> Self {
        let mut builder = MultiLanguageProverbBuilder {
            translations: std::collections::HashMap::new(),
        };
        
        // 添加英语模板
        builder.translations.insert("en", ProverbTemplate {
            line_template: "For want of a {} the {} was lost.".to_string(),
            summary_template: "And all for the want of a {}.".to_string(),
        });
        
        // 添加中文模板
        builder.translations.insert("zh", ProverbTemplate {
            line_template: "缺了{},{}就丢了。".to_string(),
            summary_template: "所有这些都因为缺了{}。".to_string(),
        });
        
        builder
    }
    
    pub fn build(&self, language: &str, words: &[&str]) -> Option<String> {
        let template = self.translations.get(language)?;
        
        if words.is_empty() {
            return Some(String::new());
        }
        
        if words.len() == 1 {
            return Some(template.summary_template.replace("{}", words[0]));
        }
        
        let mut lines: Vec<String> = Vec::with_capacity(words.len());
        
        // 生成主句
        for window in words.windows(2) {
            lines.push(template.line_template.replace("{}", window[0]).replace("{}", window[1]));
        }
        
        // 添加总结句
        lines.push(template.summary_template.replace("{}", words[0]));
        
        Some(lines.join("\n"))
    }
}

// 便利函数
pub fn build_proverb(list: &[&str]) -> String {
    if list.is_empty() {
        return String::new();
    }

    let res = list
        .windows(2)
        .map(|x| format!("For want of a {} the {} was lost.", x[0], x[1]))
        .chain(std::iter::once(format!(
            "And all for the want of a {}.",
            list[0]
        )))
        .collect::<Vec<_>>();

    res.join("\n")
}

pub fn format_proverb_with_numbering(list: &[&str]) -> String {
    let proverb = build_proverb(list);
    let lines: Vec<&str> = proverb.lines().collect();
    
    lines
        .iter()
        .enumerate()
        .map(|(i, line)| format!("{}. {}", i + 1, line))
        .collect::<Vec<_>>()
        .join("\n")
}

实际应用场景

Proverb 谚语在实际开发中有以下应用:

  1. 教育软件:语言学习和文学教学工具
  2. 文本生成:自动生成有哲理的文本内容
  3. 游戏开发:益智游戏和文字游戏
  4. 演示工具:编程教学和示例演示
  5. 文化应用:谚语和格言展示应用
  6. 写作辅助:创意写作和内容生成工具
  7. 心理应用:正念练习和哲理思考工具
  8. 社交媒体:生成有深度的社交媒体内容

算法复杂度分析

  1. 时间复杂度:O(n×m)

    • 其中n是词汇列表长度,m是平均词汇长度
    • 需要遍历列表并生成每个句子
  2. 空间复杂度:O(n×m)

    • 需要存储生成的谚语字符串

与其他实现方式的比较

rust 复制代码
// 使用递归的实现
pub fn build_proverb_recursive(list: &[&str]) -> String {
    fn build_lines(words: &[&str], first_word: &str) -> Vec<String> {
        if words.len() < 2 {
            return vec![format!("And all for the want of a {}.", first_word)];
        }
        
        let mut lines = vec![format!("For want of a {} the {} was lost.", words[0], words[1])];
        lines.extend(build_lines(&words[1..], first_word));
        lines
    }
    
    if list.is_empty() {
        return String::new();
    }
    
    if list.len() == 1 {
        return format!("And all for the want of a {}.", list[0]);
    }
    
    build_lines(list, list[0]).join("\n")
}

// 使用第三方库的实现
// [dependencies]
// itertools = "0.10"

use itertools::Itertools;

pub fn build_proverb_itertools(list: &[&str]) -> String {
    if list.is_empty() {
        return String::new();
    }
    
    let main_lines: Vec<String> = list
        .iter()
        .tuple_windows()
        .map(|(a, b)| format!("For want of a {} the {} was lost.", a, b))
        .collect();
    
    let summary_line = format!("And all for the want of a {}.", list[0]);
    
    main_lines
        .into_iter()
        .chain(std::iter::once(summary_line))
        .join("\n")
}

// 使用宏的实现
macro_rules! proverb {
    () => {
        String::new()
    };
    ($first:expr) => {
        format!("And all for the want of a {}.", $first)
    };
    ($first:expr, $($rest:expr),+) => {
        {
            let words = vec![$first, $($rest),+];
            build_proverb(&words)
        }
    };
}

// 使用状态机的实现
#[derive(Debug, Clone)]
enum ProverbState {
    Start,
    Building { lines: Vec<String>, current_index: usize },
    Done { result: String },
}

pub fn build_proverb_state_machine(list: &[&str]) -> String {
    if list.is_empty() {
        return String::new();
    }
    
    if list.len() == 1 {
        return format!("And all for the want of a {}.", list[0]);
    }
    
    let mut lines = Vec::new();
    
    // 生成主句
    for window in list.windows(2) {
        lines.push(format!("For want of a {} the {} was lost.", window[0], window[1]));
    }
    
    // 添加总结句
    lines.push(format!("And all for the want of a {}.", list[0]));
    
    lines.join("\n")
}

总结

通过 proverb 练习,我们学到了:

  1. 字符串处理:掌握了Rust中字符串处理和格式化技巧
  2. 迭代器使用:深入理解了Rust迭代器链的强大功能
  3. 函数式编程:学会了使用函数式方法处理数据转换
  4. 边界处理:理解了如何处理各种边界情况
  5. 性能优化:学会了预分配内存和使用高效算法等优化技巧
  6. 设计模式:理解了构建者模式和模板方法的应用

这些技能在实际开发中非常有用,特别是在文本处理、内容生成、教育软件等场景中。Proverb练习虽然是一个简单的文本生成问题,但它涉及到了字符串处理、迭代器使用、函数式编程、边界处理等许多核心概念,是学习Rust实用编程的良好起点。

通过这个练习,我们也看到了Rust在文本处理和字符串操作方面的强大能力,以及如何用安全且高效的方式实现文本生成算法。这种结合了安全性和性能的语言特性正是Rust的魅力所在。

相关推荐
Source.Liu1 小时前
【ISO8601库】Serde 集成模块详解(serde.rs文件)
rust·time·iso8601
工会主席-阿冰2 小时前
数据索引是无序时,直接用这个数据去画图的话,显示的图是错误的
开发语言·python·数据挖掘
麦麦鸡腿堡2 小时前
Java_TreeSet与TreeMap源码解读
java·开发语言
gladiator+2 小时前
Java中的设计模式------策略设计模式
java·开发语言·设计模式
Lucifer__hell2 小时前
【python+tkinter】图形界面简易计算器的实现
开发语言·python·tkinter
2301_812914872 小时前
py day34 装饰器
开发语言·python
ZZHHWW3 小时前
RocketMQ vs Kafka01 - 存储架构深度对比
后端
卡提西亚3 小时前
C++笔记-24-文件读写操作
开发语言·c++·笔记
snakecy3 小时前
树莓派学习资料共享
大数据·开发语言·学习·系统架构