内容简介:本文介绍Iterator Adapters,即迭代器适配器。Iterator Adapter消耗一个迭代元素转换成另一个迭代元素。迭代器就好像是现实世界的水管,现实世界中,水管是标准化的接口,打开水管就可以出水。厨房用水可能需要加热,洗澡间用水可能需要冷热混合,淋浴花洒可能需要将水分割成若干个小水流。干这些事情的,我们称之为适配器。map很简单,将方法映射到迭代器产生的每一个元素,从而输出新的迭代元素。
前言
本文介绍Iterator Adapters,即迭代器适配器。Iterator Adapter消耗一个迭代元素转换成另一个迭代元素。
迭代器就好像是现实世界的水管,现实世界中,水管是标准化的接口,打开水管就可以出水。厨房用水可能需要加热,洗澡间用水可能需要冷热混合,淋浴花洒可能需要将水分割成若干个小水流。干这些事情的,我们称之为适配器。
map and filter
map很简单,将方法映射到迭代器产生的每一个元素,从而输出新的迭代元素。
fn main() { let text = " Panda \n giraffes\niguanas \nsquid".to_string(); let v:Vec<&str> = text.lines() .map(str::trim) .collect() ; assert_eq!(v, ["Panda", "giraffes", "iguanas", "squid"]); }
String和&str提供了lines方法,该方法也会产生一个迭代器,这是上一篇产生迭代器方法中没有提到的。类似的方法有很多:
- s.bytes()
- s.chars()
- s.split_whitespace()
- s.lines()
- s.split(‘/’)
- s.matches(char::is_numeric)
所以text.lines 方法返回的是一个迭代器,即以回车字符为界,每次迭代产生出一行的内容。
map将每一行的内容,执行str::trim方法,即将每一行头部和尾部的空白字符删除。
collect方法将迭代器转换成Vec,从而完成将一段文本,按行生成一个Vec,同时去除头部和尾部的无用空白字符。
map很容易理解,即对每一个元素都执行对应的方法,得到新的迭代元素,从而得到新的迭代器。filter,顾名思义,即过滤,即对每个迭代元素执行对应方法,得到bool型的结果,只有bool值为true的迭代元素才会保留下来,bool值为负的元素被滤出掉,从而形成新的迭代器。
fn main() { let text = " Panda \n giraffes\niguanas \nsquid".to_string(); let v:Vec<&str> = text.lines() .map(str::trim) .filter(|s| *s != "iguanas") .collect() ; assert_eq!(v, ["Panda", "giraffes", "squid"]); }
如上示范代码,滤除等于”iguanas”的迭代元素。
这两个适配器的方法签名如下:
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是共享引用传递。
- map之后新的迭代器的元素和原始迭代器产生的元素,未必是同一种类型
- filter之后,新的迭代器的元素和原始迭代器的元素是同一种类型
filter是共享引用,这也是为什么我们示例代码中,为 *s != “iguanas”的原因。
最后需要指出的是,迭代器适配器是惰性的,要有真正的消费行为,才会产生迭代,否则就只是个迭代器而已,无人调用。回到我们的范例,最后的collect方法调用,才真正产生了消费行为,才会不断调用迭代器的next方法。
我们给个反面的示范:
["earth", "water", "air", "fire"].iter() .map(|elt| println!("{}", elt));
上面方法看起来很好,但是打印每一个元素的值,但是实际上,map适配器是产生出新的迭代器,但是没有任何方法要求迭代器产生出新的元素,因此,Rust会发出警告。
enumerate
enumerate 适配器是一个看起来相当无聊,但在实际编程中,非常常用的适配器。我相信写过 python 代码的人,读到这一句,都会会心一笑,赞同我所言非虚。
比如一个迭代器产生出 A , B , C这种元素,那么经过enumerate适配器之后,新的迭代器产生出(0,A),(1,B), (2,C) 这种迭代元素。
fn main() { let a = ['a', 'b', 'c'] ; let iter = a.iter().enumerate(); for (idx, elem) in iter { println!("({}, {})", idx, elem) } }
输出结果如下:
(0, a) (1, b) (2, c)
zip
zip适配器是将两个迭代器的元素,捏合成一个迭代器。比如第一个迭代器的迭代元素是”a”, “b”, “c”,第二个迭代器的迭代元素’A’, ‘B’, ‘C’,那么iter1.zip(iter2),产生的新的迭代器的元素为(‘a’, ‘A’), (‘b’, ‘B’),(‘c’, ‘C’)。
从上面的讨论可以看出,enumerate适配器,本质是zip适配器的一种特例。
(0..).zip(iter) iter.enumerate()
chain
zip是将两个迭代器配对成一个新的迭代器,新迭代器中每个元素是两个迭代器同一位置的元素组成的元组。
chain 适配器是将两个迭代器连接在一起,即append的意思。
以 iter1.chain(iter2)为例,返回一个新的迭代器,新的迭代器的next方法,会先返回iter1迭代器中的元素,当iter1中元素耗尽,那么开始从iter2 迭代器中获取元素,知道iter2中的元素也耗尽。简单地说,就是把iter2 放到iter1的后面,连成一个新的迭代器。
fn main() { let a1 = [1, 2, 3] ; let a2 = [4, 5, 6] ; let iter = a1.iter().chain(a2.iter()); for elem in iter { println!("{}", elem) } }
上面的做法是严格的做法,实际上,
fn chain<U>(self, other: U) -> Chain<Self, <U as IntoIterator>::IntoIter> where U: IntoIterator<Item = Self::Item>,
chain 适配器使用IntoIterator,我们可以传给它任何可以转换成Iterator的变量,比如Vec本身,即上面的示范,也可以写成:
let iter = a1.iter().chain(a2)
因为slice(&[T]) 实现了IntoIterator,所以可以直接传给chain作为参数。
filter_map
filter_map 适配器是个非常有用的适配器,它有点是filter 和map的结合体的意味。我们有时候遍历Result类型的迭代器的时候,有时候,可能会有失败的情况,我们就用filter_map来忽略失败的项。
fn main() { let strings = vec!["tofu", "93", "18"]; let numbers: Vec<_> = strings .into_iter() .map(|s| s.parse::<i32>()) .filter_map(Result::ok) .collect(); println!("Results: {:?}", numbers); }
我们故意用了一个比较啰嗦的版本,我们来细细地分析:
-
map( s s.parse:: ())返回的是类型是Result类型 - Result::ok()方法,如果result是成功的,那么返回Some(success_value),如果执行失败,返回None
- filter_map适配器会对迭代器的每一个元素调用闭包,如果闭包返回Some(element),那么element元素返回,如果闭包返回None,那么它会忽略该元素,尝试对下一个迭代器元素调用闭包。
所以我们总结下filter_map的实质:
- filter_map适配器,会对迭代器中的每一元素执行闭包,体现了map的方面
- 执行闭包之后,正常的结果元素,保留,作为结果迭代器的元素,如果闭包的返回值是None,忽略,体现filter的方面。
上面的实例代码,我们可以进一步简化:
fn main() { let strings = vec!["tofu", "93", "18"]; let numbers: Vec<_> = strings .into_iter() .filter_map(|s| s.parse::<i32>().ok()) .collect(); println!("Results: {:?}", numbers); }
flat_map
flat是平坦的意思,flatten是拍平。有些时候,可能需要将多个序列的结果级联起来,变成一个序列。
比如说我们有个数据结构称为主要城市:
- 日本Japan的主要城市有Tokyo, Kyoto
- 中国China的主要城市有BeiJing,ShangHai,NanJing
- 美国USA的主要城市有Portland,Washington
如果我们需要得到一个中国 日本 美国的城市列表,我们就需要打破各自序列的界限,变成一个列表。
use std::collections::HashMap ; fn main() { let mut major_cities = HashMap::new() ; major_cities.insert("China", vec!["Beijing", "Shanghai", "Nanjing"]); major_cities.insert("Japan", vec!["Tokyo", "Kyoto"]); major_cities.insert("USA", vec!["Portland", "New York"]); let counties = ["China", "Japan", "USA"]; for &city in counties.iter().flat_map(|country| &major_cities[country]) { println!("{}", city); } }
输出如下:
Beijing Shanghai Nanjing Tokyo Kyoto Portland New York
我们细细的解析flat_map
- 首先是map,map之后,每个元素都是一个迭代器
- 然后将map之后的多个迭代器的界限打破,平坦化,把多个迭代器变成一个迭代器。
另一个类似的例子如下:
fn main() { let words = ["alpha", "beta", "gamma"]; // chars() returns an iterator let merged: String = words.iter() .flat_map(|s| s.chars()) .collect(); assert_eq!(merged, "alphabetagamma"); }
take和take_while
take适配器用来截取迭代器的前n个元素。
fn main() { let a = [1, 2, 3]; let mut iter = a.iter().take(2); assert_eq!(iter.next(), Some(&1)); assert_eq!(iter.next(), Some(&2)); assert_eq!(iter.next(), None); }
这个take没啥特别的,理解了take,可以接下来学习take_while适配器了。
take_while适配器和take一样,截取原始迭代器的部分元素,但是结束条件不一样:
- take(n):很明确,即选取前n个元素,余者不取
- take_while : 会有闭包函数,闭包函数返回bool型结果,如果bool型结果为false,即立刻停止。
fn main() { let message = "To: jimb\r\n\ From: superego <editor@oreilly.com>\r\n\ \r\n\ Did you get the 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) } }
message是一封邮件的内容,header和body之间,有一空行。解析程序通过判断空行位置,来打印header信息:
To: jimb From: superego <editor@oreilly.com>
#skip 和 skip_while
skip和take正好相反。skip是丢掉迭代器前n个元素。
我们做命令行解析的时候,第一个参数一般是可执行文件本身,需要被忽略掉。
for arg in std::env::args.skip(1) { ... }
skip_while的用法和take_while正好相反:
- drop 或skip掉 迭代器的前面的元素
- 直到闭包函数返回false
fn main() { let message = "To: jimb\r\n\ From: superego <editor@oreilly.com>\r\n\ \r\n\ Did you get the 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()){ println!("{}", body) } }
跳过前面的非空行,从第一个空行开始,打印后面所有的行:
Did you get the any writing done today when will you stop wasting time plotting fractals
以上所述就是小编给大家介绍的《迭代器适配器(Iterator Adapter)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。