09|集合与迭代器:建立文本处理流水线
目标:使用借用迭代器筛选文本,并把需要保留的结果收集进 Vec。
前面用 .count() 统计一次遍历。现在考虑“保留长度至少为四个字节的词”:需要逐个产生元素、按规则过滤,最后保存结果。迭代器描述逐项处理,Vec<T> 保存一个可增长的连续序列;只有需要稍后复用结果时,才有必要收集成容器。
filter 接收一个返回布尔值的闭包。闭包像小型匿名函数,|word| word.len() >= 4 中竖线包住参数。map 把元素转换为另一种值。它们是惰性的:只构造迭代器不会完成整个计算;collect、count、sum 或 for 才会推进遍历。
保存文本视图,计算长度总和
创建 examples/iterators.rs:
fn main() {
let text = String::from("rust go rust cargo");
let words: Vec<&str> = text.split_whitespace().collect();
let selected: Vec<&str> = words
.iter()
.copied()
.filter(|word| word.len() >= 4)
.collect();
let total: usize = selected.iter().map(|word| word.len()).sum();
println!("selected={}", selected.join(","));
println!("selected_bytes={total} all_words={}", words.len());
}
Vec<&str> 保存引用,没有复制原文本的内容。words.iter() 借用容器并产生 &&str;.copied() 复制其中的引用,得到 &str,不复制字符串内容。filter 会借用每一项以决定保留与否,方法调用会自动处理这些引用层级。join 用分隔符把字符串片段拼成新的 String,便于稳定展示。
运行前先预测:过滤是否会从 words 里删除短词?
cargo run --quiet --example iterators
预期输出:
selected=rust,rust,cargo
selected_bytes=13 all_words=4
原容器仍然有 4 项,筛选结果有 3 项。iter() 只借用;如果对拥有的 Vec 调用 into_iter(),通常会消耗容器并移出元素。iter_mut() 则允许逐个可变借用已有元素。这三个入口应按“只读、接管、修改”的意图选择。
改变筛选规则
- 将阈值从 4 改为 5。
- 再运行,应保留
cargo,总字节数为 5,原容器仍有 4 项。 - 恢复阈值。不要通过删改输入来假装筛选规则生效。
独立练习:在收集到 selected 后去除重复词,按字典顺序输出 cargo,rust。可以把 selected 声明为可变,调用 sort_unstable() 排序,再调用 dedup() 删除相邻重复项。这两个方法在 Vec 上原地操作,不返回新 Vec。
完整参考实现
fn main() {
let text = String::from("rust go rust cargo");
let mut selected: Vec<&str> = text
.split_whitespace()
.filter(|word| word.len() >= 4)
.collect();
selected.sort_unstable();
selected.dedup();
assert_eq!(selected, vec!["cargo", "rust"]);
println!("{}", selected.join(","));
}
完整替换实验文件,运行同一命令,预期 cargo,rust。vec! 创建向量,供断言比较。必须先排序再去掉相邻重复项;否则不相邻的两个 rust 可能不会一起被删除。这里所有重复字符串内容相等,不需要保留它们之间的原顺序,所以可使用不稳定排序。
如果 collect() 报无法推断类型,在绑定上写清 Vec<&str>;同一个迭代器可以收集成多种容器,编译器需要目标类型。如果提示文本活得不够久,检查容器是否保存了指向已经释放的 String 的切片。想让结果独立存活时才将元素变为 String。需要按键查找次数时可以进一步学习 HashMap,但本次统计不需要维护映射表。
本课新增 examples/iterators.rs。最终项目直接使用 count(),因为只要数量,不需要保存全部词。