了解了关于迭代器的两个核心特征,以及一些基本语法后,你才只是窥见了关于 Rust 迭代器的冰山一角!下面我会介绍一系列迭代器的具体操作用法。
Iterator 提供的方法Iterator 除了 next 需要我们自己编写,其他方法都提供了默认实现。它们提供很多针对迭代器进行的便捷操作。
| 方法 | 简介 |
|---|---|
collect |
转换为集合 |
cmp 与 partial_cmp |
元素比较 |
eq 与 ne |
元素比较 |
sum |
元素求和 |
count |
元素计数 |
last |
获取末尾元素 |
nth |
获取指定索引元素 |
fold |
元素折叠为值 |
接下来我们一个一个展开:
collect |
|---|
| 将当前迭代器转换为一个集合 |
消耗当前迭代器所有权,返回一个实现了 FromIterator 的集合。
因此该方法总是出现在调用链的末尾。
collect 方法极为全能的一点是,它能将迭代器转换为你选定的任何集合类型(甚至不是集合类型都可以)。但是强大的背后总有代价:你必须为它手动标注转换的类型,要么使用涡轮鱼语法,要么在结果一侧添加类型注解。
let a = [1, 2, 3];
// 在结果一侧添加类型注解 `Vec<i32>`
let doubled: Vec<i32> = a.iter().map(|x| x * 2).collect();
assert_eq!(vec![2, 4, 6], doubled);
// 使用涡轮鱼语法标注类型为 `Vec<i32>`
let doubled = a.iter().map(|x| x * 2).collect::<Vec<i32>>();
assert_eq!(vec![2, 4, 6], doubled);
// `collect` 只关心你想要生成什么类型
// 所以有时它会允许类型的一部分用 `_` 代替
let doubled = a.iter().map(|x| x * 2).collect::<Vec<_>>();
assert_eq!(vec![2, 4, 6], doubled);
cmp 与 partial_cmp |
|---|
| 将当前迭代器中的每个元素与另一个给定的迭代器中的元素进行比较 |
cmp 要求两个迭代器所迭代的元素类型相同且实现了 Ord 特征。
use std::cmp::Ordering;
assert_eq!([1].iter().cmp([1].iter()), Ordering::Equal);
assert_eq!([1].iter().cmp([1, 2].iter()), Ordering::Less);
assert_eq!([1, 2].iter().cmp([1].iter()), Ordering::Greater);
partial_cmp 则是把要求放宽到实现 PartialOrd 就行。但是遇到无法比较的情况则会返回 None。
use std::cmp::Ordering;
assert_eq!([1.0, f64::NAN].iter().partial_cmp([2.0, f64::NAN].iter()), Some(Ordering::Less));
assert_eq!([2.0, f64::NAN].iter().partial_cmp([1.0, f64::NAN].iter()), Some(Ordering::Greater));
// 浮点类型的 NaN 无法比较
assert_eq!([f64::NAN, 1.0].iter().partial_cmp([f64::NAN, 2.0].iter()), None);
eq 与 ne |
|---|
| 将当前迭代器中每个元素与另一给定的迭代器中的元素比较是否相同 |
要求两个迭代器所迭代的元素类型互相之间实现了 PartialEq 特征。
assert_eq!([1].iter().eq([1].iter()), true);
assert_eq!([1].iter().eq([1, 2].iter()), false);
assert_eq!([1, 2].iter().eq([1, 2].iter()), true);
assert_eq!([1].iter().ne([1].iter()), false);
assert_eq!([1].iter().ne([1, 2].iter()), true);
sum |
|---|
| 对迭代器的所有元素求和 |
要求迭代的元素实现 Sum 特征,也就是可求和。在这一点上,甚至元素是 Option 和 Result 都可以求和。
let a = [1, 2, 3];
let sum: i32 = a.iter().sum();
assert_eq!(sum, 6);
let words = vec!["have", "a", "great", "day"];
// `.find` 会在字符串中寻找给定字符
// 若找到,就返回该字符在字符串中出现的索引位置
// 若没找到就返回 `None`
let total: Option<usize> = words.iter().map(|w| w.find('a')).sum();
assert_eq!(total, Some(5));
count |
|---|
| 消耗迭代器返回迭代总次数(即元素总数) |
此方法通过反复调用 next 来对迭代的元素进行计数,当遇到 None 时停止计数,并返回 usize 值。
由于迭代器元素总数可能超过 usize 可表示范围,所以当发生溢出时(超出 usize::MAX)程序会报错或直接崩溃。
let a = [1, 2, 3, 4, 5];
assert_eq!(a.iter().count(), 5);
last |
|---|
| 消耗迭代器返回最后一个迭代的元素 |
此方法通过持续追踪当前迭代的元素,来最终返回它观察到的最后一个元素。若元素无限多,方法会崩溃。
let a = [1, 2, 3, 4, 5];
assert_eq!(a.into_iter().last(), Some(5));
nth |
|---|
返回索引为 n 的元素 |
n 是零索引的,因此 0 代表迭代的首个元素。如果 n 超出可用索引会返回 None。
要注意的是,该方法从前迭代到第 n 个元素后,除了返回第 n 个元素,还会丢弃前面的所有元素。这意味着对同一迭代器多次调用 nth 时要特别小心。
let a = [1, 2, 3];
let mut iter = a.into_iter();
// 第一次调用导致第 1 个元素被返回
// 而第 0 个元素被丢弃
assert_eq!(iter.nth(1), Some(2));
// 所以此时迭代器只剩下 1 个元素了
assert_eq!(iter.nth(1), None);
fold |
|---|
| 将迭代元素折叠进一个不断更新的值 |
该方法的签名为 fold<B, F>(self, init: B, f: F) -> B。它接收一个 B 类型的初始值 init,还有一个 FnMut(B, Self::Item) -> B闭包。
闭包的第一个参数是“累加器”,初始值 init 就是累加器的初始值。闭包第二个参数是传入的当前迭代的元素。而闭包返回的是累加器在下一次迭代时应具有的值。fold 方法返回最后一次调用闭包后返回的值。
换言之,累加器是一个在每次迭代过程中不断更新的状态值。迭代器每迭代一次,累加器和当前元素就一起被传入闭包,并用返回的值更新累加器。迭代结束后,fold 方法会返回最终的状态。
let a = [1, 2, 3];
// 计算数组所有元素的和
// 那么用 `0` 来初始化
let sum = a.iter().fold(0, |acc, x| acc + x);
assert_eq!(sum, 6);
Note
fold 具有“左结合”(left-associative)的特性。整个折叠过程始终从左到右进行,即初始值先与第一个元素结合,得到的结果再与第二个元素结合,依此类推。
相对比,DoubleEndedIterator::rfold 则是“右结合”(right-associative)。整个折叠过程始终从右到左进行。
let numbers = [1, 2, 3, 4, 5];
let zero = "0".to_string();
let l_result = numbers.iter().fold(zero, |acc, &x| {
format!("({acc} + {x})")
});
assert_eq!(l_result, "(((((0 + 1) + 2) + 3) + 4) + 5)");
let zero = "0".to_string();
let r_result = numbers.iter().rfold(zero, |acc, &x| {
format!("({acc} + {x})")
});
assert_eq!(r_result, "(((((0 + 5) + 4) + 3) + 2) + 1)");
接受一个迭代器作为参数并返回另一个迭代器的函数通常被称为迭代器适配器(iterator adapters)。Iterator 提供的方法当中就有不少是迭代器适配器。
Tip
前面我们并没有谈到的一点是,迭代器是可组合的,通常会将它们链式调用来执行一连串更复杂的处理。而这恰恰是适配器的功劳。
下面会介绍以下常见的迭代器适配器:
| 适配器 | 简介 |
|---|---|
copied 与 cloned |
复制迭代器 |
rev |
反转迭代器 |
cycle |
使迭代无限循环 |
take |
取出前面的元素 |
skip |
取出后面的元素 |
chain |
拼接两个迭代器 |
zip |
合并两个迭代器 |
map |
对迭代元素进行函数映射 |
filter |
过滤迭代元素 |
filter_map |
同时进行过滤和映射 |
enumerate |
为元素附加计数器 |
让我们一个个把它们讲清楚。
copied 与 cloned |
|---|
| 将当前迭代器中的所有元素拷贝到一个新迭代器中并返回新迭代器 |
copied 要求原迭代器的元素是实现了 Copy 的引用类型。之所以要求元素是引用是因为很多情景下,你拥有的迭代器是引用形式的(迭代的元素是引用类型),且想从一个迭代 &T 元素的迭代器生成一个迭代 T 元素的迭代器。此方法就是为应对这种需求而生的。
let a = [1, 2, 3];
// `.iter` 使得迭代器的元素变成了 `&i32`
let v_copied: Vec<_> = a.iter().copied().collect();
// `copied` 与 `.map(|&x| x)` 效果相同
let v_map: Vec<_> = a.iter().map(|&x| x).collect();
assert_eq!(v_copied, [1, 2, 3]);
assert_eq!(v_map, [1, 2, 3]);
cloned 与 copied 几乎一样,最大的不同在于对元素的限制从实现 Copy 放宽到了实现 Clone。
let a = [1, 2, 3];
let v_cloned: Vec<_> = a.iter().cloned().collect();
// 等价于
let v_map: Vec<_> = a.iter().map(|&x| x).collect();
assert_eq!(v_cloned, [1, 2, 3]);
assert_eq!(v_map, [1, 2, 3]);
Tip
为获得更佳的性能,最好把 cloned 放在适配器调用链的后面。
rev |
|---|
| 逆转元素的迭代方向 |
该方法要求原迭代器是 DoubleEndedIterator,也就是其元素是有尽头的。只有可穷尽的迭代器才能从末尾开始向起点遍历。
let a = [1, 2, 3];
let mut iter = a.into_iter().rev();
assert_eq!(iter.next(), Some(3));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), None);
cycle |
|---|
| 创建一个无限循环的迭代器,遍历完一遍再从头开始 |
生成的迭代器永远不会返回 None(除非原始迭代器为空)。
要求原迭代器实现 Clone。因为重新从原始迭代器的开头开始需要克隆整个迭代器。
let a = [1, 2, 3];
let mut iter = a.into_iter().cycle();
loop {
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), Some(3));
assert_eq!(iter.next(), Some(1));
break;
}
take |
|---|
取出指定的最多 n 个元素组成一个新迭代器 |
take(n) 会持续从原迭代器取出元素,直到已取出 n 个元素或迭代器到达末尾。如果原始迭代器至少包含 n 个元素,则返回的迭代器是长度为 n 的前缀;否则返回原始迭代器中的所有(少于 n 个)元素。
let a = [1, 2, 3];
let mut iter = a.into_iter().take(2);
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), None);
由于 take 的此种特性,它常常与元素数无限的迭代器或无限循环的迭代器搭配使用,这样能让它们变为有限的迭代器。
// `0..` 拥有无限多个元素
let mut iter = (0..).take(3);
assert_eq!(iter.next(), Some(0));
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), None);
let v = [1, 2];
// 使用 `cycle` 使其无限循环
let mut iter = v.into_iter().cycle().take(3);
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), None);
take 能与 by_ref 搭配使用,这样的话前 n 个元素被取出后,原迭代器还能正常使用。
let mut words = ["hello", "world", "of", "Rust"].into_iter();
// 取出前 2 个元素
let hello_world: Vec<_> = words.by_ref().take(2).collect();
assert_eq!(hello_world, vec!["hello", "world"]);
// 将剩余元素转换为集合
let of_rust: Vec<_> = words.collect();
assert_eq!(of_rust, vec!["of", "Rust"]);
skip |
|---|
跳过前 n 个元素,取出剩余元素组成一个新迭代器 |
与 take 方法类似,它会持续从原迭代器跳过元素,直到已跳过 n 个元素或迭代器到达末尾。如果原始迭代器至少包含 n+1 个元素,则返回所有剩余元素的迭代器;否则返回 None。
let a = [1, 2, 3];
let mut iter = a.into_iter().skip(2);
assert_eq!(iter.next(), Some(3));
assert_eq!(iter.next(), None);
chain |
|---|
| 将两个迭代器拼接成一个新的迭代器 |
消耗两个迭代器,将它们首尾相接(self 在前,传入的迭代器在后)合成新迭代器。方法要求两个迭代器的元素类型相同。
由于该方法接收的是 IntoIterator,所以任何可转为迭代器的类型(例如 [T])都支持进行拼接。
let s1 = "ab".chars();
let s2 = "def".chars();
let mut iter = s1.chain(s2);
assert_eq!(iter.next(), Some('a'));
assert_eq!(iter.next(), Some('b'));
assert_eq!(iter.next(), Some('d'));
assert_eq!(iter.next(), Some('e'));
assert_eq!(iter.next(), Some('f'));
assert_eq!(iter.next(), None);
let a1 = [1, 2];
let a2 = [4, 5, 6];
let mut iter = a1.into_iter().chain(a2);
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), Some(4));
assert_eq!(iter.next(), Some(5));
assert_eq!(iter.next(), Some(6));
assert_eq!(iter.next(), None);
zip |
|---|
| 将两个迭代器合并,对应元素拼接成元组 |
消耗两个迭代器,将它们合并“压缩”,产生一个新迭代器。此迭代器中每个元素都是一个元组:第一个元素来自 self,第二个元素来自传入此方法的迭代器。
如果其中一个迭代器提前结束了迭代,那么合并过程也会到此戛然而止。
由于该方法接收的是 IntoIterator,所以任何可转为迭代器的类型(例如 [T])都支持进行合并“压缩”。
let s1 = "abc".chars();
let s2 = "defg".chars();
let mut iter = s1.zip(s2);
assert_eq!(iter.next(), Some(('a', 'd')));
assert_eq!(iter.next(), Some(('b', 'e')));
assert_eq!(iter.next(), Some(('c', 'f')));
// 由于 s1 提前结束了迭代
// 因此合并后的迭代器也会在此结束迭代
// `s2` 后面剩余元素不再参与合并
assert_eq!(iter.next(), None);
map |
|---|
| 使用闭包将原迭代器每个元素映射到新迭代器中 |
该方法接收一个 FnMut(Self::Item) -> B 的闭包,然后方法会依次将原迭代器每个元素传入闭包,并将闭包产生的值依次放入新迭代器。
let a = [1, 2, 3];
let mut iter = a.iter().map(|x| 2 * x);
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), Some(4));
assert_eq!(iter.next(), Some(6));
assert_eq!(iter.next(), None);
Tip
我们使用 map 是想要将一个迭代器转为另一迭代器,并在此新迭代器上继续其他操作。
如果你所传入的闭包不会使其转为另一迭代器,那么更好的选择是使用 for 循环。
// 不要写成
// (0..5).map(|x| println!("{x}"));
// 此语句甚至不会执行,因为 `map` 是惰性的
// 更好的选择是用 `for` 循环:
for x in 0..5 {
println!("{x}");
}
filter |
|---|
| 使用闭包筛选原迭代器每个元素,符合条件者进入新迭代器 |
该方法接收一个 FnMut(&Self::Item) -> bool 的闭包,然后方法会依次将原迭代器每个元素传入闭包。如果闭包返回 true 就将原迭代器对应元素放入新迭代器;如果闭包返回 false 就忽略它。最终返回新迭代器。
let a = [0i32, 1, 2];
// 筛选出所有正值
let mut iter = a.into_iter().filter(|x| x.is_positive());
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), None);
Important
注意到,闭包接收的是引用类型参数。如果原先的迭代器就是对引用类型迭代的话,可能会出现某些令人困惑不解的双重引用。
此时对闭包的处理需要非常小心。
// 对下面这个集合直接调用 `iter`
// 产生的迭代器元素类型为 `&i32`
let s = &[0, 1, 2];
// 由于 `filter` 接收 `&Self::Item` 参数
// 而 `Self::Item` 为 `&i32` 类型
// 所以闭包的参数实际上是 `&&i32`
// `x` 需要解引用两次才能得到 `i32`
let mut iter = s.iter().filter(|x| **x > 1);
// 等价于
// `x` 需要解引用才能得到 `i32`,因为
// 闭包参数 `&x` 对应的类型是 `&&i32`
// 故 `x` 对应 `&i32`,只需解引用一次
let mut iter = s.iter().filter(|&x| *x > 1);
// 等价于
// 直接在参数中使用双重引用,这样就不需要解引用了
let mut iter = s.iter().filter(|&&x| x > 1);
assert_eq!(iter.next(), Some(&2));
assert_eq!(iter.next(), None);
filter_map |
|---|
| 使用闭包对原迭代器每个元素同时进行筛选和映射 |
该方法接收一个 FnMut(Self::Item) -> Option<B> 的闭包,然后方法会依次将原迭代器每个元素传入闭包。如果闭包返回 Some(value) 就将 value 放入新迭代器;如果闭包返回 None 就忽略它。最终返回新迭代器。
执行一步 filter_map 相当于执行了多步 map 和 filter。
let a = ["1", "two", "NaN", "four", "5"];
let mut iter = a.iter().filter_map(|s| s.parse().ok());
// 等价于
// let mut iter = a.iter()
// .map(|s| s.parse())
// .filter(|s| s.is_ok())
// .map(|s| s.unwrap());
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(5));
assert_eq!(iter.next(), None);
enumerate |
|---|
| 为原迭代器的元素附加计数器 |
该方法返回一个新迭代器,其中每个元素是一个元组 (i, val),i 为当前迭代的索引,val 为对应的原迭代器的元素。
和 count 类似,该方法由于涉及索引,可能出现 usize 的溢出问题。一旦迭代元素个数超出 usize::MAX 程序就会报错或崩溃。
let a = ['a', 'b', 'c'];
let mut iter = a.into_iter().enumerate();
assert_eq!(iter.next(), Some((0, 'a')));
assert_eq!(iter.next(), Some((1, 'b')));
assert_eq!(iter.next(), Some((2, 'c')));
assert_eq!(iter.next(), None);