Rust 迭代器适配器

207 阅读10分钟

map 和 filter

map(映射)

适配器能针对迭代器的各个条目用闭包来帮你转换迭代器

let text  = "    ponies \n giraffes\niguanas \
\n squid    ".to_string();
let v = text.lines().map(str::trim).collect::<Vec<&str>>();
println!("{:?}",v); // ["ponies", "giraffes", "iguanas", "squid"]
  • text.lines() 调用会返回一个生成字符串中各行的迭代器
  • 在该迭代器上调用 map 返回第第二个迭代器
  • 第二个迭代器会对每行调用 str.trim 并将结果作为自己的条目
  • 最后collect 会将这些条目收集到向量v中.

filter(过滤)

适配器能使用闭包帮你从迭代器中过滤某些条目,由闭包决定保留还是丢弃条目

/// 可以对map返回的迭代器进一步适配
let v = text.lines().map(str::trim).filter(|s|*s != "iguanas").collect::<Vec<&str>>();
println!("{:?}",v); // ["ponies", "giraffes", "iguanas", "squid"]
  • filter 会返回第三个迭代器,它只会从map迭代器的结果生成闭包|s|*s!="iguanas" 返回为true的条目
// map函数签名 
fn map<B,F>(self,f:F)->impl Iterator<Item=B> where Self:Sized,F:FnMut(Self::Item)->B;
// fitter 函数签名 
fn filter<P>(self,predicate:P)->impl Iterator<Item=self::Item> where Self:Sized,p:FnMut(&Self::Item)->bool;

/// 在标准库中 map 和 filter 实际上返回的上是 std::iter::Map 和 std::iter::Filter 的专用不透明struct 类型

大多数适配器会按值接受self,所以Self 必须是固定大小的(Sized)

map 迭代器会按值将每个条目传给闭包,然后将闭包结果的所有权转给消费者

filter 迭代器会通过共享引用将每个条目传给闭包,并保留所有权以便再把选定的条目传递给消费者

注意:

  • 1,单纯在迭代器上调用适配器并不会消耗任何条目,只会返回一个新的迭代器,
    • 新迭代器会根据需要从第一个迭代器提取条目,以生成自己的条目,
    • 在适配器的适配链中,实际完成任何工作(同时消耗条目)的唯一方法是在最终的迭代器上调用next,
    • 迭代器不会立即操作任何内容,只有调用next 的时候才会操作源数据
let mut l= ["eartch","warter","air","fire"].iter().map(|x|println!("{}",x)); // 不会有任何结果
l.next(); // 只有调用 next() 才会打印;
  • 2, 迭代器的适配器是一种零成本抽象,
    • 由于map,filter 和其它类似的适配器都是泛型,因此它们应用于迭代器就会专门针对所涉及的特定迭代器类型生成特化代码
    • 这意味着rust 会有足够的信息将每个迭代器的next 方法内联到它的消费者中,
    • 然后将这一组功能作为一个单元翻译成机器码,
    • 所以,之前的lines,map,filter迭代器的链式调用和手写循环的一样高效

filter_map 和 flat_map

每个传入条目都会生成传出条目的情况下,map 适配器很实用

如果想从迭代中删除而不是处理某些条目,或想用零个或多个条目替换单个条目时

可以使用 filter_map(过滤映射) 和 flat_map(展平映射)适配器

filter_map(过滤映射)

filter_map 适配器看作是 map 与 filter 的组合

前者允许其闭包将条目转换为新条目(map那样)或从迭代中丢弃该条目

// filter_map 函数签名
 fn filter_map<B,F>(self,f:F)->impl Iterator<Item=B> where Self:Sized,F:FnMut(Self::Item)->Option<B>;

这里的闭包会返回Option<B>,当返回None时,该条目就会被丢弃,当返回Some(b)时,b就是filter_ma 迭代器中生成的下一个条目.

use std::str::FromStr;
let text = "1\nfrond .25 289\n3.1415 estuary\n";
for number in text.split_whitespace().filter_map(|x1| f64::from_str(x1).ok()) {
        println!("{:4.2}",number)
}
// 传给filter_map 的闭包会尝试使用 f64::from_str 来解析每个以空格分割的切片
// 返回一个Result<f64,ParseFloatError> 的结果,在调用.ok() 转换成Option<f64>,
// 如果解析错误会转换成None,成功会转化成 Option<v>,filter_map 会丢弃所有None 值并为每个Some<v>生成值v;

filter 和map 也可以做同样的事情

let text = "1\nfrond .25 289\n3.1415 estuary\n";
let list = text.split_whitespace().map(|x2| f64::from_str(x2)).filter(|x3|x3.is_ok()).map(|x4| x4.unwrap());

for number in list {
    println!("{:4.2}",number)
}

flat_map(展平映射)

flat_map 也是对map的功能延展;

这个闭包可以返回 像map那样一个条目,像filter_map 返回零个或一个条目,还可以发返回任意数量的条目序列

flat_map 迭代器会生成此闭包返回的序列串联后的结果.

fn flat_map<U,F>(self,f:F)->impl Iterator<Item=U::Item> where F:FnMut(Self::Item)->U,U:IntoIterator
 
// 传递给flat_map 的闭包必须返回一个可迭代者,但可以返回任意种类的可迭代者;
// 由于 Option 也是一个可迭代者,其行为类似与有零个或一个条目的序列
// 所以 iterator.filter_map(closure) 等效于 iterator.flat_map(closure),closure 返回一个Option<T>
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("The Netherlands",vec!["Amsterdam","Utrecht"]);

let countries = ["Japan","Brazil" ];


for &country in countries.iter().flat_map(|x5| &major_cities[x5]) {
    println!("{}",country);
}

flatten (展平) 适配器

flatten (展平) 适配器,会串联起迭代器的各个条目,这里假设每个条目都是可迭代者

/// flatten 函数签名
fn flatten(self)->Impl Iterator<Item=Self::Item::Item> where Self::Item:IntoIterator;
use std::collections::BTreeMap;

let mut parks = BTreeMap::new();

parks.insert("Portland",vec!["Mt,Tabor park","Forest Park"]);
parks.insert("Kyoto",vec!["Tadasu-no-Mori Forest","Maruyama Koen"]);
parks.insert("Nashville",vec!["Percy Warner Park","Dragon park"]);

let all_parks:Vec<_> = parks.values().flatten().cloned().collect();

println!("{:?}",all_parks);

类似于将一个二维结构展平为一维结构,

  • 这就要求地磁迭代器的条目必须自行实现IntoIterator 才能真正形成一个由序列才能组成的序列
  • 返回 flatten 方法会返回一个迭代器,该迭代器会生成这些序列的串联
  • 这都是惰性执行的,需要迭代完上一个序列才会从self中提取一个新序列

flatten 方法还有一些其它用法

从Vec<Option<...>> 中迭代出Some 的值

let v = vec![None,Some("day"),None,Some("one")];
let d = v.into_iter().flatten().collect::<Vec<_>>();
println!("{:?}",d);
  • 因为 Option 本身也实现了IntoIterator 表示由0个或1个元素组成的序列
  • None元素对迭代器没有任何贡献,而每个Some都会贡献一个值,
  • 同样可以使用 flatter 来迭代Option<Vec<...>> 值,其中None的行为等同于空向量
  • Result 也实现了 IntoIterator ,其中Err表示空序列,因此将flatten应用于Result值的迭代器有效的排除所有Err,并丢弃
  • 进而产生一个解包装过的由成功值组成的流
// 当可以随手使用flatten的时候,大多数需要使用的是 flat_map
//  flat_map 时 flatten 和 map 的组合
fn to_uppercase(s: &String)->String{
    s.chars().map(char::to_uppercase).flatten().collect()
}
// 优化直接使用 flat_map
fn to_uppercase1(s:&String)->String{
    s.chars().flat_map(char::to_uppercase).collect()
}

take 与 take_while

Iterator 特型的 take(取出) 适配器和 take_while (当...时候取出)适配器的作用是当条目达到一定数量或闭包决定终止时结束迭代

// take 函数签名
fn take(self,n:usize)->impl Iterator<Item=Self::Item> where Self:Sized;
// take_while 函数签名
fn take_while<P>(self,predicate:P)->impl Iterator<Item=Self::Item> where Self:Sized,P:FnMut(&Self::Item)->bool;
  • 两个都会接手某个迭代器的所有权并返回一个新的迭代器,
  • 新迭代器从第一个迭代器中传递条目,并可能提早终止序列,
  • take 迭代会在最多生成n个条目后返回None,
  • take_while 迭代器会针对每个条目调用predicate,并对 predicate闭包返回了一个false的首个条目以及其后的条目都返回None
let message = "To: jimb\r\n\
    From: superego <editor@oreilly.com>\r\n\
    \r\n\
    When will you stop wasting time plotting fractals?
    \r\n";

for line in message.lines().take_while(|l|!l.is_empty()) {
    println!("~ {}",line)
}

skip 与 skip_while

  • Iterator 特型的 skip(跳过) skip_while(当...时候跳过)
  • 是与 take 和 take_while 互补的方法
  • skip 从迭代开始时就丢弃一定数量的条目,skip_while 则一直丢弃条目直到闭包纵欲找到一个可以接手的条目为止
  • 然后将剩下的条目照原样传递出来`
// skip 函数签名
fn skip(self,n:usize)->impl Iterator<Item=Self::Item> where Self:Sized;
// skip_while 函数签名
fn skip_while<P>(self,predicate:P) ->impl Iterator<Item=Self::Item> where Slef:Sized,P:FnMut(&Self::Item)->bool;
// skip 适配器的常见用途之一是,在迭代程序的命令行参数时跳过命令本身的名称,
for arg in std::env::args().skip(1) {
    todo!()
}
// std::env::args 会返回一个迭代器,该迭代器会将程序的各个参数生成为一些String 型条目,首个条目时程序本身的名称,这并不是我们要在循环中处理的字符串;
// 对该迭代器调用skip(1),返回一个新的迭代器,新的迭代器在第一次调用时会丢弃程序名称,然后生成后续参数
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(|x6| !x6.is_empty()).skip(1) {
    println!("* {}",body)
}

// skip_while 闭包返回true 就丢弃跳过,false 就把剩余的条目(包含当前),迭代出来

peekable

peekable (可窥视) 迭代器,允许我们窥视即将生成的下一个条目,而无需实际消耗它

调用Iterator 特型的 peekable 方法可以将任何迭代器变成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时给出,

  • Peekable 迭代器上的所有Iterator 方法都知道这个缓存,

  • 比如: peekable 迭代器 iter 上的 iter.last() 就知道要在耗尽底层迭代器后检查此缓存

// 有时候,需要提前知道才能决定从迭代器中消耗多少个条目
use std::iter::Peekable;

fn parse_number<I>(tokens:&mut Peekable<I>)->u32 where I:Iterator<Item=char>{
    let mut n:u32 = 0;

    loop {

        match tokens.peek() {
            Some(r) if r.is_digit(10)=>{
                println!("* {}",r.to_digit(10).unwrap());

                n= n * 10 + r.to_digit(10).unwrap();
                println!("{}",n);
            }
            _=>return n
        }
        tokens.next();
    }
}

let mut chars = "226153980,1766319049".chars().peekable();

println!("{}",parse_number(&mut chars));
println!("{:?}",chars.next());
println!("{}",parse_number(&mut chars));
println!("{:?}",chars.next());

// parer_number 函数会使用peek来检查下一个字符,只有当它时数字时才消耗它,如果他不是数字或迭代器已消耗完(peek 返回None),返回已解析的数值,并将下一个字符留在迭代器中(非数字),以供使用;

fuse

Iterator 特型并没有规定一旦next 返回None 只会再次调用next方法时应该如何行动

大多数迭代只是再次返回None,但也有例外

fuse(保险丝)适配器能接受任何迭代器并生成一个确保在第一次返回 None 后继续返回 None 的迭代器

println!("----flaky-------");
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);

println!( "{:?}" ,flaky.next()); // Some("totally the last item")
println!( "{:?}" ,flaky.next()); // None
println!( "{:?}" ,flaky.next()); // Some("totally the last item")


let mut not_flaky = Flaky(true).fuse();
println!( "{:?}" ,not_flaky.next()); // Some("totally the last item")
println!( "{:?}" ,not_flaky.next()); // None
println!( "{:?}" ,not_flaky.next()); // None
  • fuse 适配器在需要使用不明来源的迭代器的泛型代码中非常有用,
  • 不要奢望处理的每个迭代器都表现良好,需要自己使用fuse更可靠

可逆迭代器 与 rev

... 未完待续