25_rust_迭代器

迭代器

什么是迭代器:迭代器模式是对一系列项执行某些任务,迭代器负责遍历每个项,确定序列(遍历)何时完成。
rust的迭代器:

  • layzy(懒惰的):除非调用消费迭代器的方法,否则迭代器本身没任何效果。
fn main() {
  let v1 = vec![1, 2, 3];
  let v1_iter = v1.iter(); // 获得迭代器,需要对其使用才有效
  for v in v1_iter {
    println!("{}", val);
  }
}

Iterator trait和next方法

Iterator trait

所有迭代器都实现了Iterator trait,定义于标注库,定义大致如下:

pub trait Iterator {
  type Item;
  fn next(&mut self) -> Option<Self::Item>;
}

type Item和Self::Item定义了与此该trait关联的类型。实现了Iterator trait需要定义一个Item类型,用于next方法的返回类型(迭代器的返回类型)。
Iterator trait仅要求实现一个方法:next;

next方法

  • 每次返回迭代器的一项
  • 返回结果包含在Some里
  • 迭代结束,返回None
    可直接在迭代器上调用next方法
#[cfg(test)]
mod tests {
    #[test]
    fn test1() {
        let v1 = vec![1, 2, 3];
        let mut v1_iter = v1.iter(); // 因为调用next方法相当于改变了指针位置,需要用mut关键字
        println!("{:?}{:?}{:?}", v1_iter.next(), v1_iter.next(), v1_iter.next());
        // 打印结果 Some(1)Some(2)Some(3)
        assert!(false);
    }
}

for循环不用加mut关键字的原因是执行for语句获得了所有权。

  • iter方法:在不可变引用上创建迭代器
  • into_iter方法:创建的迭代器会获得所有权
  • iter_mut方法:迭代可变的引用

消耗/产生迭代器

消耗迭代器的方法

在标准库中,Iterator trait有一些带默认实现的方法,其中有一些方法会调用next方法,所以实现Iterator trait时必须实现next方法。调用next的方法叫“消耗型适配器”,调用后会把迭代器耗尽,类似于指针一直往后移动,直到最后一个None。

比如sum方法就会耗尽迭代器,它会取得迭代器所有权,通过反复调用next,遍历所有元素,每次迭代,把当前元素添加到总和里,迭代结束,返回总和。

#[cfg(test)]
mod tests {
    #[test]
    fn test1() {
        let v1 = vec![1, 2, 3];
        let v1_iter = v1.iter();
        let total: i32 = v1_iter.sum();
        assert_eq!(total, 6);
        //let total: i32 = v1_iter.sum(); // 如果试图第二次调用,编译报错:use of moved value: `v1_iter`
    }
}

产生其它迭代器的方法

定义在Iterator trait上的另外一些方法叫“迭代器适配器”,用于吧迭代器转换为不同种类的迭代器,可通过链式调用使用多个迭代器适配器执行复杂操作。
如map,接收一个闭包,闭包可作用于每个元素,并产生一个新的迭代器。

#[cfg(test)]
mod tests {
    #[test]
    fn test1() {
        let v1 = vec![1, 2, 3];
        // 通过调用map方法对每个元素+1,但迭代器本身不会消耗,需要调collect方法让迭代器移动,
        // 即控制指针后移,下划线_的作用是让编译器自行推断类型
        let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
        assert_eq!(v2, vec![2, 3, 4]);
    }
}

collect方法:消耗型适配器,把结果收集到一个集合类型中。

使用闭包捕获环境

filter方法:

  • 接收一个闭包
  • 这个闭包在遍历迭代器的每个元素时,返回bool类型
  • 如果闭包返回true:当前元素将会包含在filter产生的迭代器中
  • 如果闭包返回false:当前元素将不会包含在filter产生的迭代器中
fn filter_data(vals: Vec<i32>, flag: i32) -> Vec<i32> {
    vals.into_iter().filter(|x| *x == flag).collect()
}
#[cfg(test)]
mod tests {
    use crate::*;
    #[test]
    fn test1() {
        let v1 = vec![1, 2, 3, 3, 5];
        let ret = filter_data(v1, 3);
        assert_eq!(ret, vec![3, 3]);
    }
}

创建自定义迭代器

实现next方法

struct Cnt {
    cnt: u32,
}
impl Cnt {
    fn new() -> Cnt {
        Cnt { cnt: 0 }
    }
}
impl Iterator for Cnt {
    type Item = u32; // 返回u32类型数据
    fn next(&mut self) -> Option<Self::Item> {
        if self.cnt < 3 {
            self.cnt += 1;
            Some(self.cnt)
        } else {
            None
        }
    }
}
#[cfg(test)]
mod tests {
    use crate::*;
    #[test]
    fn test1() {
        let mut cnt = Cnt::new();
        assert_eq!(cnt.next(), Some(1));
        assert_eq!(cnt.next(), Some(2));
        assert_eq!(cnt.next(), Some(3));
        assert_eq!(cnt.next(), None);
    }
}

进一步计算:

#[derive(Debug)]
struct Cnt {
    cnt: u32,
}
impl Cnt {
    fn new() -> Cnt {
        Cnt { cnt: 0 }
    }
}
impl Iterator for Cnt {
    type Item = u32; // 返回u32类型数据
    fn next(&mut self) -> Option<Self::Item> {
        if self.cnt < 3 {
            self.cnt += 1;
            Some(self.cnt)
        } else {
            None
        }
    }
}
#[cfg(test)]
mod tests {
    use crate::*;
    #[test]
    fn test1() {
        let mut cnt = Cnt::new();
        assert_eq!(cnt.next(), Some(1));
        assert_eq!(cnt.next(), Some(2));
        assert_eq!(cnt.next(), Some(3));
        assert_eq!(cnt.next(), None);
    }
    #[test]
    fn test2() {
        // let sum: Vec<_> = Cnt::new() // 使用collect方法可打印出内容
        // .zip(Cnt::new().skip(1)).collect(); // 第二个迭代器跳过第一个元素后,进行zip,最后剩两组, [(1, 2), (2, 3)]
        // .map(|(a, b)| a * b).collect(); // 对迭代器每个成员进行相乘,后结果迭代器数据为 [2, 6]
        // .filter(|x| x % 2 == 0).collect(); // 过滤2的倍数,依然是 [2, 6]
        // .sum();
        // println!("{:?}", sum);
        let sum: u32 = Cnt::new()
        .zip(Cnt::new().skip(1)) // [(1, 2), (2, 3)]
        .map(|(a, b)| a * b) // [2, 6]
        .filter(|x| x % 3 == 0) // 过滤后只剩6
        .sum();
        assert_eq!(6, sum);
    }
}

改进上一节的练习:
main.rs

use std::env;
use std::process;
use minigrep::Config;

fn main() {
    // args返回一迭代器,collect将迭代器转换为Vector,需指明类型
    // args函数无法处理非法Unicode字符,如果输入了非法unicode字符则panic
    // let args : Vec<String> = env::args().collect();
    // 如果要接收非法Unicode字符,需要使用env::args_os(), 这个返回的是OsString
    // 增加错误处理逻辑,使用unwrap_or_else函数,如果Ok则将结果赋值给cfg,如果失败则会调用一个传入的闭包
    let cfg = Config::new(env::args()).unwrap_or_else(|err| { // 闭包语法,匿名语法
        eprintln!("args parse err: {}", err);
        process::exit(1); // 程序退出,状态码1
    });
    if let Err(e) = minigrep::run(cfg) {
        eprintln!("run err: {}", e);
        process::exit(1);
    }
}

lib.rs

use std::fs; // 处理与文件相关的事务
use std::error::Error;
use std::env;

pub fn run(cfg: Config) -> Result<(), Box<dyn Error>> {
    println!("{}, {}", cfg.query, cfg.filename);
    // 原理的expect方法发生错误时会panic,使用?运算符,则发生错误时不会panic,而是会将错误返回给调用者处理
    let contents = fs::read_to_string(cfg.filename)?;
    let ret = if cfg.case_sensitive {
        search_txt(&cfg.query, &contents)
    } else {
        search_case_insensitive(&cfg.query, &contents)
    };
    for line in ret {
        println!("{}", line);
    }
    Ok(())
}
 
pub struct Config {
    pub query: String,
    pub filename: String,
    pub case_sensitive: bool, // 添加一个大小写敏感的字符串
}
 
impl Config {
    /**
     * @args: Vec切片,String类型元素
     * Return: Result,Ok时Config实例,携带待查关键字和文件名信息,
     *         Err时,返回一字符串字面指(静态生命周期,字符串切片)
     */
    pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
        if args.len() < 3 {
            // panic!("need 3 args"); // 一般程序本身错误时才用panic
            // 处理用户使用错误一般用Result,所以需设置返回值为Result类型
            return Err("need 3 args");
        }
        args.next(); // 第一个元素不要
        let query = match args.next() { // 无需再用clone方法
            Some(arg) => arg,
            None => return Err("can't get query string"),
        };
        let filename = match args.next() {
            Some(arg) => arg,
            None => return Err("can't get file name"),
        };
        // 获取环境变量,只要CASE_INSENS环境变量出现,就认为不区分大小写,
        // var函数返回一个Result,只要设置了就返回Ok里的值,未设置环境变量则返回Err,
        // 这里用is_err检测是否发生了错误即可。这里只关心有没出现,不关心具体值,所以不需要处理err。
        let case_sensitive = env::var("CASE_INSENS").is_err();
        Ok(Config { query, filename, case_sensitive })
    }
}

// 因为返回的字符串切片是contents里的内容,所以生命周期要一致
pub fn search_txt<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    // let mut ret = Vec::new();
    // for line in contents.lines() {
    //     if line.contains(query) {
    //         ret.push(line);
    //     }
    // }
    // ret
    contents.lines().filter(|line| line.contains(query)).collect() // 可避免并发问题
}

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut ret = Vec::new();
    let query = query.to_lowercase(); // 会创建一个新的字符串,拥有所有权
    for line in contents.lines() {
        if line.to_lowercase().contains(&query) {
            ret.push(line);
        }
    }
    ret
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_search() {
        let query = "test";
        let src_txt = "src multi txt:\nrust test search.\ntest txt.";
        assert_eq!(vec!["rust test search.", "test txt."], search_txt(query, src_txt));
    }

    #[test]
    fn test_case_sensitive() {
        let query = "test";
        let src_txt = "src multi txt:\nrust test search.\nTest txt.";
        assert_eq!(vec!["rust test search.",], search_txt(query, src_txt));
    }

    #[test]
    fn test_case_insensitive() {
        let query = "test";
        let src_txt = "src multi txt:\nrust TEST search.\nTest txt.";
        assert_eq!(vec!["rust TEST search.", "Test txt."], search_case_insensitive(query, src_txt));
    }
}

循环VS迭代器的性能

迭代器是rust里的高层次抽象,这就是

  • 零开销抽象(Zero-Cost Abstraction):使用抽象时不会引入额外的运行时开销。
posted @ 2023-11-09 00:27  00lab  阅读(17)  评论(0编辑  收藏  举报