Iterator 特型可以为迭代器提供大量可供选择的适配器方法,或简称适配器(adapter)。map 和 filtermap 适配器:为迭代器的每个迭代项都应用一个闭包。
filter 适配器:通过迭代器来过滤某些迭代项,使用闭包来决定保留或消除哪个迭代项。
标准库 str::trim 方法可以清除 &str 开头和末尾的空格,返回一个新的修整后的借用原始值 &str。
可以通过 map 适配器给迭代器的每一行应用 str::trim:
let text = " ponies \n giraffes\niguanas \nsquid".to_string();
let v: Vec<&str> = text.lines()
.map(str::trim)
.collect();
assert_eq!(v, ["ponies", "giraffes", "iguanas", "squid"]);
调用 map 返回的迭代器本身仍然可以继续使用适配器。如下所示,继续排除 "iguanas":
let text = " ponies \n giraffes\niguanas \nsquid".to_string();
let v: Vec<&str> = text.lines()
.map(str::trim) // 先调用适配器map
.filter(|s| *s != "iguanas") // 再调用适配器filter
.collect();
assert_eq!(v, ["ponies", "giraffes", "iguanas", "squid"]);
map 和 filter 适配器的签名:
fn map<B, F>(self, f: F) -> some Iterator<Item=B>
where Self: Sized, F: FnMut(Self:: Item) -> B;
fn filter<P>(self, predicate: P) -> some Iterator<Item=Self::Item>
where Self: Sized, P: FnMut(&Self:: Item) -> bool;
map 与 filter 适配器的区别:
map 适配器将迭代器每一项按值传给其闭包,进而将闭包返回结果的所有权传递给消费者。filter 适配器将迭代器每一项的共享引用传给其闭包,在将选中项传给其消费者时,保留该项的所有权。对于迭代器适配器有以下两个特点:
简单地在一个迭代器上调用适配器不会消费任何项,只会返回一个新迭代器,并可以按需从第一个迭代器排取以产生自己的项。在适配器链中,唯一可以真正让操作落地的是在最终的迭代器上调用 next。
["earth", "water", "air", "fire"]
.iter().map(|elt| println!("{}", elt));
对于上述代码,编译时 Rust 会给出警告提示:其中 lazy 指的是一种将计算推迟到真正需要时的机制。
warning: unused result which must b used:
iterator adaptors are lazy and do nothing unless consumed
|
|
387 | / ["earth", "water", "air", "fire"]
388 | | .iter().map(|elt| println!("{}", elt));
| |_________________________________________^
|
= note: #[warn(unused_must_use)] on by default
迭代器适配器属于零开销抽象。因为 map、filter 及其同类方法是泛型的,所以把它们应用给迭代器会针对涉及的特定迭代器类型特化它们的代码。
// 上述代码等效于:
for line in text.lines() {
let line = line.trim();
if line != "iguanas" {
v.push(line);
}
}
filter_map 和 flat_mapfilter_map 适配器:允许闭包在迭代过程中转换项(类似 map),也支持删除项。类似 filter 与 map 的组合。
fn filter_map<B, F>(self, f: F) -> some Iterator<Item=B>
where Self: Sized, F: FnMut(Self:: Item) -> Option<B>;
适配器中的闭包返回的是 Option<B>;
如果适配器返回 None,表示从迭代中清除项;
如果适配器返回 Some(b),则 b 就是 filter_map 迭代器的下一项。
use std::str::FromStr;
let text = "1\nfrond .25 289\n3.1415 estuary\n";
for number in text.split_whitespace()
.filter_map(|w| f64::from_str(w).ok()) {
println!("{:4.2}", number.sqrt());
}
上述代码输出结果为:
1.00
0.50
17.00
1.77
在迭代中决定是否包含某一项的最好方式,就是尝试实际去处理它。
flat_map 适配器:传给它的闭包必须返回一个可迭代类型,任何可迭代类型都可以。
fn flat_map<U, F>(self, f: F) -> some Iterator<Item=U::Item>
where F: FnMut(Self::Item) -> U, U: IntoIterator;
只有 for 循环调用 flat_map 迭代器的 next 方法时,才会实际发生迭代。
拼接完整序列的操作不会在内存中发生。
对于下述例子:对每个国家,先取得其城市的向量,然后把所有向量拼接为一个序列,再把它打印出来。
use std::collections::HashMap;
let mut major_cities = HashMap::new();
major_cities.insert("Japan", vec!["Tokyo", "Kyoto"]);
major_cities.insert("The United States", vec!["Portland", "Nashville"]);
major_cities.insert("Brazil", vec!["Sao Paulo", "Brasilia"]);
major_cities.insert("Kenya", vec!["Nairobi", "Mombasa"]);
major_cities.insert("The Netherlands", vec!["Amsterdam", "Utrecht"]);
let countries = ["Japan", "Brazil", "Kenya"];
for &city in countries.iter().flat_map(|country| &major_cities[country]) {
println!("{}", city);
}
结果输出为:
Tokyo
Kyoto
Sao Paulo
Brasilia
Nairobi
Mombasa
scanscan 适配器:
map,区别是它会传给闭包一个可修改的值,而且可以选择提前终止迭代。Option,scan 迭代器将其作为自己的下一项。如下所示例子:一个迭代器链会对另一个迭代器的项求平方,并在和超过 10 时终止迭代:
let iter = (0..10).scan(0, |sum, item| {
*sum += item;
if *sum > 10 {
None
} else {
Some(item * item)
}
});
assert_eq!(iter.collection::<Vec<i32>>(), vec![0, 1, 4, 9, 16]);
sum 参数是一个迭代器私有变量的可修改引用,在 scan 的第一个参数中初始化,即 0。*sum,检查它是否超过了限制,然后返回迭代器的下一个结果。take 和 take_whileIterator 特型的 take 和 take_while 适配器用于在取得一定项数之后或闭包决定中断时终止迭代。
fn take(self, n: usize) -> some Iterator<Item=Self:: Item>
where Self: Sized;
fn take_while<P>(self, predicate: P) -> some Iterator<Item=Self:: Item>
where Self: Sized, P: FnMut(&Self:: Item) -> bool;
take 适配器在产生最多 n 项后,返回 None。take_while 适配器对每一项应用 predicate,在遇到第一个 predicate 返回 false 项时,适配器返回 None,后续每次调用 next 也都返回 None。如下所示:使用 take_while 迭代邮件的标题行。邮件的内容以空格分隔标题行和正文。
let message = "To: jimb\r\n\
From: superego <editor@oreilly.com>\r\n\
\r\n
Did you get any writing done today?\r\n\
When will you stop wasting time plotting fractals?\r\n";
for header in message.lines().take_while(|l| !l.is_empty) {
println!("{}", header);
}
当一行字符串以 \ 斜杠结尾时,Rust 不会在字符串中包含下一行的缩进,因此字符串中每一行前面都不会有空格。
此时,第三行是空的。take_while 适配器碰到这个空行就会终止迭代,因此上述代码只会打印以下信息:
To: jimb
From: superego <editor@oreilly.com>
skip 和 skip_whileIterator 特型的 skip 和 skip_while 方法是对 take 和 take_while 的补充。它们从迭代开始清除一定数量的项,或者一直清除到闭包发现一个可以接受的项,然后将剩余项原封不动返回。
fn skip(self, n: usize) -> some Iterator<Item=Self:: Item>
where Self: Sized;
fn skip_while<P>(self, predicate: P) -> some Iterator<Item=Self:: Item>
where Self: Sized, P: FnMut(&Self:: Item) -> bool;
skip 适配器的应用场景:在迭代程序的命令行参数时跳过命令名。
for arg in std::env::args().skip(1) {
...
}
std::env::args 函数返回一个迭代器,该迭代器会产生 String 类型的程序参数,其中第一项是程序本身的名字。skip(1) 会清除程序名,然后产生后面所有的参数。skip_while 适配器使用闭包来决定清除序列开头的多少项。
let message = "To: jimb\r\n\
From: superego <editor@oreilly.com>\r\n\
\r\n
Did you get any writing done today?\r\n\
When will you stop wasting time plotting fractals?\r\n";
for body in message.lines()
.skip_while(|l| !l.is_empty())
.skip(1) {
println!("{}", body);
}
skip_while 会跳过非空的行,但此时迭代器还会产生空行,因为这个闭包碰到空行会返回 false
所以,又使用 skip 方法跳过了这个空行,这样得到的迭代器第一项就是邮件正文的第一行。
输出结果为:
Did you get any writing done today?
When will you stop wasting time plotting fractals?
peekableIterator 特型的 peekable 方法可以让代码在不消费下一项的情况下探测下一项。调用这个方法可以将几乎任何迭代器转换为可探测的迭代器:
fn peekable(self) -> std::iter::Peekable<Self>
where Self: Sized;
Peekable<Self> 是一个实现了 Iterator<Item=Self::Item> 的结构体。Self 是底层迭代器的类型。Peekable 迭代器有一个 peek 方法,该方法返回 Option<&Item>:如果底层迭代器终止就返回 None,否则返回 Some(r),其中 r 是下一项的共享引用。peek 会尝试从底层迭代器取出下一项,如果取到了,就将其缓存到下一次调用 next。举例:从一个字符流中解析数值,在发现其后面第一个非数值字符之前无法确定数值是否结束:
use std::iter::Peekable;
fn parse_number<I>(tokens: &mut Peekable<I>) -> u32
where I: Iterator<Item=char>
{
let mut n = 0;
loop {
match tookens.peek() {
Some(r) if r.is_digit(10) => {
n = n * 10 + r.to_digit(10).unwrap();
}
_ => return n
}
tokens.next();
}
}
let mut chars = "226153980,1766319049".chars().peekable();
assert_eq!(parse_number(&mut chars), 226153980);
assert_eq!(chars.next(), Some(','));
assert_eq!(parse_number(&mut chars), 1766319049);
assert_eq!(chars.next(), None);
parse_number 函数使用 peek 检查下一个字符,只有该字符是数值时才消费它。peek 返回 None),则返回已解析的数值,并把下一个字符留在迭代器中供后面消费。fuseIterator 特型的 fuse 适配器,可以将任何适配器转换为第一次返回 None 之后始终继续返回 None 迭代器。
None 的情况下,再调用 next 的场景。struct Flaky(bool);
impl Iterator for Flaky {
type Item = &'static str;
fn next(&mut self) -> Option<Self::Item> {
if self.0 {
self.0 = false;
Some("totally the last item")
} else {
self.0 = true;
None
}
}
}
let mut flaky = Flaky(true);
assert_eq!(flaky.next(), Some("totally the last item"));
assert_eq!(flaky.next(), None);
assert_eq!(flaky.next(), Some("totally the last item"));
let mut not_flaky = Flaky(true).fuse();
assert_eq!(not_flaky.next(), Some("totally the last item"));
assert_eq!(not_flaky.next(), None);
assert_eq!(not_flaky.next(), None);
rev可逆迭代器:可以从序列两端取得项。
一个迭代向量的迭代器可以转换为从向量末尾开始取值。即可以实现 std::iter::DoubleEndedIterator 特型,该特型扩展了 Iterator。
trait DoubleEndedIterator: Iterator {
fn next_back(&mut self) -> Option<Self::Item>;
}
调用这个特型:
use std::iter::DoubleEndedIterator;
let bee_parts = ["head", "thorax", "abdomen"];
let mut iter = bee_parts.iter();
assert_eq!(iter.next(), Some(&"head"));
assert_eq!(iter.next_back(), Some(&"abdomen"));
assert_eq!(iter.next(), Some(&"thorax"));
assert_eq!(iter.next(), None);
assert_eq!(iter.next_back(), None);
这样的迭代器相当于一个指针,分别指向尚未产生元素的头和尾。
并不是所有迭代器都可以实现两端迭代。如基于另一个线程返回给 Receiver 值的迭代器无法预算接收到的最后一个值是什么。此时,需要查询标准库文档来确定一个迭代器是否实现了 DoubleEndedIterator 特型。
rev 适配器:对于实现了 DoubleEndedIterator 特型的迭代器,可以使用 rev 适配器将其反转。
fn rev(self) -> some Iterator<Item=Self>
where Self: Sized + DoubleEndedIterator;
返回的迭代器同样支持两端取值,其 next 和 next_back 方法知识简单互换。
let meals = ["breakfast", "lunch", "dinner"];
let mut iter = meals.iter().rev();
assert_eq!(iter.next(), Some(&"dinner"));
assert_eq!(iter.next(), Some(&"lunch"));
assert_eq!(iter.next(), Some(&"breakfast"));
assert_eq!(iter.next(), None);
在应用给可逆迭代器后,大多数迭代器适配器会返回另一个可逆迭代器。如 map 和 filter 都具有可逆性。
inspectinspect 适配器:
举例:把字符串转换为大写会改变其长度:
let upper_case: String = "grosse".chars()
.inspect(|c| println!("before: {:?}", c))
.flat_map(|c| c.to_uppercase())
.inspect(|c| println!(" after: {:?}", c))
.collect();
assert_eq!(upper_case, "GROSSE");
chainchain 适配器:将一个迭代器添加到另一个适配器后面。
i1.chain(i2) 会返回一个迭代器,该迭代器会先从 i1 中提取项,取完后再继续从 i2 中提取项。
chain 适配器的签名如下:
fn chain<U>(self, other: U) -> some Iterator<Item=Self:: Item>
where Self: Sized, U: IntoIterator<Item=Self:: Item>;
可以将迭代器与任何产生相同项类型的可迭代类型连缀在一起:
let v: Vec<i32> = (1..4).chain(vec![20, 30, 40]).collect();
assert_eq!(v, [1, 2, 3, 20, 30, 40]);
如果连缀的两个迭代器都是可逆的,则 chain 返回的迭代器也是可逆的:
lev v: Vec<i32> = (1..4).chain(vec![20, 30, 40]).rev().collect();
assert_eq!(v, [40, 30, 20, 3, 2, 1]);
chain 迭代器会跟踪底层的迭代器是否返回 None,根据情况调用某个底层迭代器的 next 和 next_back。
enumerateIterator 特型的 enumerate 适配器,可以向序列中添加连续的索引。
A, B, C... 的迭代器,其返回的迭代器则产生 (0, A), (1, B), (2, C)...。用法举例:
// 创建一个矩形的像素缓冲区
let mut pixels = vec![0; columns * rows];
// 使用chunks_mut 将图像切分成水平长条,每个线程分派一个。
let threads = 8;
let band_rows = rows / threads + 1;
let bands: Vec<&mut [u8]> = pixels.chunks_mut(band_rows * columns).collect();
// 迭代这些长条,为每个长条启动一个线程。
for (i, band) in bands.into_iter().enumerate() {
let top = band_rows * i; // 启动一个线程渲染top..top + band_rows
}
(i, band),其中 band 是线程应该渲染的像素缓冲的 &mut [u8] 切片;i 是相应长条在整个图像中的索引。这个索引是 enumerate 适配器提供的。zipzip 适配器:将两个适配器组合为一个适配器,产生之前两个迭代器项的项对。(如同拉链把分开的两边拼在一起)
举例:将半开范围 0.. 与其他迭代器组合在一起,得到与使用 enumerate 适配器同样的效果。
let v: Vec<_> = (0..).zip("ABCD".chars()).collect();
assert_eq!(v, vec![0, 'A'], [1, 'B'], (2, 'C'), (3, 'D'));
zip 相当于通用化的 enumerate:
enumerate 只能给其他序列添加索引;zip 可以添加任何迭代项。传给 zip 的参数不一定是迭代器本身,也可以是任何可迭代类型:
use std::iter::repeat;
let endings = vec!["once", "twice", "chicken soup with rice"];
let rhyme: Vec<_> = repeat("going")
.zip(endings)
.collect();
assert_eq!(rhyme, vec![
("going", "once"),
("going", "twice"),
("going", "chicken soup with rice")
]);
by_ref迭代器的 by_ref 方法可以借用迭代器的一个可修改引用,以便把适配器应用给这个引用。
by_ref 方法在通过适配器消费完迭代器的项后,借用结束,可以恢复对原始迭代器的访问。by_ref 的定义:
impl<'a, I: Iterator + ?Sized> Iterator for &'a mut I {
type Item = I:: Item;
fn next(&mut self) -> Option<I: Item> {
(**self).next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
(**self).size_hint()
}
}
I 是某种迭代器类型,那么 &mut I 也是迭代器,其 next 和 size_hint 方法会解引用到它的引用值。举例:
let message = "
To: jimb\r\n\
From: id\r\n\
\r\n
Oooooh, donuts!!\r\n
";
let mut lines = message.lines()
println!("Headers:");
for header in lines.by_ref().take_while(|l| !l.is_empty()) {
println!("{}", header);
}
println!("\nBody:");
for body in lines {
println!("{}", body)
}
lines.by_ref() 调用从迭代器借用了一个可修改引用,而 take_while 迭代器只是取得了这个引用的所有权。for 循环结束后,take_while 迭代器离开作用域,借用关系随之终止。for 循环中,可以继续使用 lines。clonedcloned 适配器可以将一个产生引用的迭代器转换为产生基于引用克隆的值的迭代器。引用值的类型必须实现 Clone。
let a = ['1', '2', '3', '4'];
assert_eq!(a.iter().next(), Some(&'1'));
assert_eq!(a.iter().cloned().next(), Some('1'));
cyclecycle 适配器返回一个无休止重复底层迭代器的迭代器。
底层迭代器必须实现 std::clone::Clone;
以便 cycle 可以保存其初始状态并在每次循环开始时重用。
let dirs = ["North", "East", "South", "West"];
let mut spin = dirs.iter().cycle();
assert_eq(spin.next(), Some(&"North"));
assert_eq(spin.next(), Some(&"East"));
assert_eq(spin.next(), Some(&"South"));
assert_eq(spin.next(), Some(&"West"));
assert_eq(spin.next(), Some(&"North"));
assert_eq(spin.next(), Some(&"East"));
典型计算题:用 fizz 替换可以被 3 整除的数,用 buzz 替换可以被 5 整除的数。可以同时被 3 和 5 整除的数就会得到 fizzbuzz。
use std::iter::{once, repeat};
let fizzes = repeat("").take(2).chain(once("fizz")).cycle();
let buzzes = repeat("").take(4).chain(once("buzz")).cycle();
let fizzes_buzzes = fizzes.zip(buzzes);
let fizz_buzz = (1..100).zip(fizzes_buzzes)
.map(
|tuple| match tuple {
(i, ("", "")) => i.to_string(),
(_, (fizz, buzz)) => format!("{}--{}{}", i.to_string(), fizz, buzz)
}
);
for line in fizz_buzz {
println!("{}", line);
}
详见《Rust 程序设计》(吉姆 - 布兰迪、贾森 - 奥伦多夫著,李松峰译)第十五章
原文地址