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]