Rust 迭代器 (Iterator)
核心概念:惰性求值 (Lazy Evaluation)
Rust 的迭代器是惰性的。当你调用 map 或 filter 时,并没有真正开始遍历数据,仅仅是创建了一个“计算计划”。
只有当你调用 消费者 (Consumer) 方法(如 collect, sum, for_each)时,计算才会真正触发。这使得 Rust 能够像 SQL 优化器一样优化你的逻辑。
一、标准库基础篇
1. 变换类 (Adapters)
这是最常用的中间操作,负责处理数据流。
fn main() {
let a = (0..10).collect::<Vec<i32>>();
let result = a.iter()
.filter_map(|x| if x % 2 == 0 { Some(x) } else { None }) // 过滤并变换
.collect::<Vec<_>>();
println!("{:#?}", result);
// 输出: [0, 2, 4, 6, 8]
}
2. 消费类 (Consumers)
这些方法会触发迭代器的执行。
collect*: 将迭代器重新“收集”回集合(Vec, HashMap 等)。
- 技巧:熟练掌握 Turbofish 语法 ::<Vec<_>> 来指定目标类型。
for_each*: 用于执行副作用(如打印日志、写入数据库),是一个不返回值的循环。
3. 结构与流程控制
处理索引、合并列表或切片时的必备工具。
结构辅助
enumerate*: 生成 (index, item),替代 C 风格的 for i in 0..len。
zip*: “拉链”操作,将两个迭代器合并为 (item_a, item_b)。
fn main() {
let a = vec![0, 1, 2];
let b = vec![10, 20]; // 注意:b 比 a 短
// 结论:zip 以最短的迭代器为准,多余元素会被丢弃
a.iter().zip(b.iter()).for_each(|(x, y)| {
println!("{}, {}", x, y);
});
// 输出:
// 0, 10
// 1, 20
}
- chain*: 首尾相连,将两个迭代器串联成一个。
数量控制
*take(n) / skip(n)**: 取前 n 个 / 跳过前 n 个。
take_while / skip_while*: 基于条件(闭包)来决定何时停止或开始获取元素。
二、标准库高阶操作
1. 降维与展开
flatten*: 降维打击。把 Vec<Vec
> 拍平成 Vec ,或者去除 Vec<Option > 中的 None。 flat_map*: map + flatten 的组合技。在解析嵌套结构(如解析 XML/JSON 节点)时是神技。
fn main() {
let a = vec![1, 2, 3];
// 场景1:展开 Option
let c: Vec<i32> = a.iter().flat_map(|&x| Some(x + 1)).collect();
// 场景2:展开嵌套数组 (每个元素变成 0..x 的序列)
let d: Vec<i32> = a.iter().flat_map(|&x| (0..x)).collect();
// d 的结果: [0, 0, 1, 0, 1, 2] (即 0..1, 0..2, 0..3 的拼接)
}
2. 归约 (Reduction)
- fold*: 这是核武器map, filter, sum 的底层都可以用 fold 实现。它带有初始值,用于将迭代器缩减为单一值(或某种集合)。
fn main() {
let nums = vec![1, 2, 3, 4, 5];
// 示例:将偶数转换为 i64 并存入新 Vec (Vec::new() 是初始值)
let f = nums.iter().fold(Vec::<i64>::new(), |mut acc, &num| {
if num % 2 == 0 {
acc.push(num as i64);
}
acc
});
println!("{:?}", f); // [2, 4]
}
reduce: 类似于 fold,但*没有初始值(使用第一个元素作为初始值)。因为迭代器可能为空,所以它返回 Option。
- 场景:求最大值、最小值、总和,且确定列表不为空时。
3. 逻辑与调试
any / all*: 是否 至少有一个 / 全部 满足条件?
find*: 找到第一个满足条件的元素并返回 Option(短路机制,找到即停)。
inspect*: 偷窥镜。它不改变数据,只允许你插入代码(如 println!)查看流经的数据。调试长链式调用时必备。
cloned / copied*: 将引用的迭代器转为值的迭代器(方便处理 &T -> T)。
三、超级进化:Itertools 库
GitHub: rust-itertools/itertools
itertools 被誉为 “Rust 标准库缺失的迭代器部分”。许多 Python 或 Haskell 中强大的功能都在这里。
1. 格式化与拼接 (The "Join" Confusion)
在标准库中,我经常感到困惑:
- Vec::join (Slice方法): 存在。可以写 vec![vec![1,2], vec![1,2]].join(&111)。
Iterator::join: 不存在*。你不能直接写 vec.iter().join(",")。
Itertools 补全了这一点:
join*: 让迭代器直接转字符串。
use itertools::Itertools; // 解决痛点:不再需要手动处理最后一个逗号 println!("{:?}", (0..3).join(", ")); // 输出 "0, 1, 2"format*: 更加灵活,支持对每个元素进行格式化后再拼接。
2. 相邻数据处理
tuple_windows*: 滑动窗口。
Std 做法zip + skip (繁琐且易错)。
Itertools:
// 自动生成 (1,2), (2,3), (3,4) vec![1, 2, 3, 4].iter().tuple_windows().for_each(|(a, b)| { println!("{} -> {}", a, b); });
collect_tuple*: 确定元素数量时的直接解构。
let (a, b, c) = vec![1, 2, 3].into_iter().collect_tuple().unwrap();
3. 多维处理 (笛卡尔积)
拒绝嵌套 for 循环,从这里开始。
cartesian_product*:
let it = (0..2).cartesian_product("ab".chars()); // 结果: (0, 'a'), (0, 'b'), (1, 'a'), (1, 'b')zip_eq*: 强校验版的 zip。如果两个迭代器长度不一致,直接 Panic。适用于数据完整性要求高的场景。
4. 数据清洗
标准库的流式处理很难做全局操作(如去重、排序),Itertools 封装了这些逻辑(注意:通常需要消耗内存)。
unique*: 去重(内部维护 HashSet)。
sorted*: 排序(内部收集为 Vec 排序后再迭代)。
// 链式调用中的排序 let v = vec![5, 1, 3].into_iter().sorted().map(|x| x * 2).collect::<Vec<_>>();
5. 高级分块 (Grouping)
chunks*: 简单的按数量分块(如每3个一组)。
group_by*: 按连续相同特征分组(类似 SQL GROUP BY 或 Linux uniq)。
坑点预警*:这是惰性的!如果不把当前组消费完,下一组不会开始。建议配合 &group.collect::<Vec<_>>() 使用。
6. 多流合并
*kmerge / kmerge_by (排序合并)**:
假设有两个已排序的数组,你想合并它们并保持有序*。它会比较两边的头部,谁小取谁。
*interleave (交替合并)**:
- 不管大小,你一个我一个(Round-robin)。
let a = vec![1, 3];
let b = vec![2, 4];
// kmerge (假设原序有序): [1, 2, 3, 4]
// interleave: [1, 2, 3, 4] (这里恰好一样,但逻辑不同)
// 如果 a=[1, 10], b=[2, 4] -> interleave 是 [1, 2, 10, 4]