"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. 核心要求
- 字符串格式化:正确格式化谚语句子
- 相邻元素处理:处理列表中相邻的元素对
- 总结句生成:生成总结性的最后一句
- 边界处理:正确处理空列表和单元素列表
2. 技术要点
- 迭代器链:使用Rust强大的迭代器功能
- 字符串拼接:高效地拼接多行字符串
- 模式匹配:处理不同的输入情况
- 函数式编程:使用函数式方法处理数据转换
完整实现
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 谚语在实际开发中有以下应用:
- 教育软件:语言学习和文学教学工具
- 文本生成:自动生成有哲理的文本内容
- 游戏开发:益智游戏和文字游戏
- 演示工具:编程教学和示例演示
- 文化应用:谚语和格言展示应用
- 写作辅助:创意写作和内容生成工具
- 心理应用:正念练习和哲理思考工具
- 社交媒体:生成有深度的社交媒体内容
算法复杂度分析
-
时间复杂度:O(n×m)
- 其中n是词汇列表长度,m是平均词汇长度
- 需要遍历列表并生成每个句子
-
空间复杂度: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 练习,我们学到了:
- 字符串处理:掌握了Rust中字符串处理和格式化技巧
- 迭代器使用:深入理解了Rust迭代器链的强大功能
- 函数式编程:学会了使用函数式方法处理数据转换
- 边界处理:理解了如何处理各种边界情况
- 性能优化:学会了预分配内存和使用高效算法等优化技巧
- 设计模式:理解了构建者模式和模板方法的应用
这些技能在实际开发中非常有用,特别是在文本处理、内容生成、教育软件等场景中。Proverb练习虽然是一个简单的文本生成问题,但它涉及到了字符串处理、迭代器使用、函数式编程、边界处理等许多核心概念,是学习Rust实用编程的良好起点。
通过这个练习,我们也看到了Rust在文本处理和字符串操作方面的强大能力,以及如何用安全且高效的方式实现文本生成算法。这种结合了安全性和性能的语言特性正是Rust的魅力所在。