Rust的迭代器

我爱海鲸 2024-03-01 17:56:03 rust学习

简介iterator trait、next方法

1、什么是迭代器

迭代器模式:对一系列项执行某些任务

迭代器负责:

   遍历每个项

   确定序列(遍历)何时完成

Rust的迭代器:

   懒惰的:除非调用消费迭代器的方法,否则迭代器本身没有任何效果。

fn main() {
    let v1 = vec![1,2,3];
    let v1_iter = v1.iter();

    for val in v1_iter {
        println!("Got {}",val)
    }
}

2、lterator trait

所有迭代器都实现了lterator trait

lterator trait定义于标准库,定义大致如下:

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::ltem>;
    // methods with default implementations elided
}

type ltem和 Self::ltem定义了与此该trait 关联的类型。

实现lterator trait需要你定义一个ltem类型,它用于 next方法的返回类型(迭代器的返回类型)。

lterator trait仅要求实现一个方法: next

next:

   每次返回迭代器中的一项

   返回结果包裹在Some里

   迭代结束,返回None

可直接在迭代器上调用next方法

#[cfg(test)]
mod tests {

    #[test]
    fn iterator_demonstration() {
        let v1 = vec![1,2,3];
        let mut v1_iter = v1.iter();

        assert_eq!(v1_iter.next(),Some(&1));
        assert_eq!(v1_iter.next(),Some(&2));
        assert_eq!(v1_iter.next(),Some(&3));
    }
}

几个迭代方法

iter方法:在不可变引用上创建迭代器

into_iter方法:创建的迭代器会获得所有权

iter_mut方法:迭代可变的引用

3、消耗/产生迭代器

消耗迭代器的方法

在标准库中,lterator trait有一些带默认实现的方法

其中有一些方法会调用next方法

实现lterator trait时必须实现next 方法的原因之一

调用next的方法叫做“消耗型适配器”

因为调用它们会把迭代器消耗尽

sum方法(就会耗尽迭代器)

取得迭代器的所有权

通过反复调用next,遍历所有元素

每次迭代,把当前元素添加到一个总和里,迭代结束,返回总和

#[cfg(test)]
mod tests {

    #[test]
    fn iterator_sum() {
        let v1 = vec![1,2,3];
        let v1_iter = v1.iter();
        let total:i32 = v1_iter.sum();

        assert_eq!(total,6);
    }
}

产生其它迭代器的方法

定义在 lterator trait上的另外一些方法叫做“迭代器适配器”

―把迭代器转换为不同种类的迭代器

可以通过链式调用使用多个迭代器适配器来执行复杂的操作,这种调用可读性较高。

例如: map 

一接收一个闭包,闭包作用于每个元素

-产生一个新的迭代器

#[cfg(test)]
mod tests {

    #[test]
    fn iterator_sum() {
        let v1 = vec![1,2,3];
        let v2:Vec<_> = v1.iter().map(|x|x+1).collect();

        assert_eq!(v2,vec![2,3,4])
    }
}

这个Vec<_>就表示让编译器自己推断集合的值

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

4、使用闭包捕获环境

filter方法:

一接收一个闭包

一这个闭包在遍历迭代器的每个元素时,返回bool类型

一如果闭包返回true:当前元素将会包含在filter产生的迭代器中

一如果闭包返回false:当前元素将不会包含在 filter 产生的迭代器中

#[derive(PartialEq,Debug)]
struct Shoe {
    size:u32,
    style:String,
}

fn shoes_in_my_size(shoes:Vec<Shoe>,shoe_size:u32)->Vec<Shoe> {
    shoes.into_iter().filter(|x|x.size == shoe_size).collect()
}

#[test]
fn filiter_by_size() {
    let shoes = vec! [
        Shoe {
            size:10,
            style:String::from("sneaker"),
        },
        Shoe {
            size:13,
            style:String::from("sandal"),
        },
        Shoe {
            size:10,
            style:String::from("bool"),
        },
    ];
    let in_my_size = shoes_in_my_size(shoes, 10);

    assert_eq!(
        in_my_size,
        vec![
            Shoe {
                size:10,
                style:String::from("sneaker"),
            },
            Shoe {
                size:10,
                style:String::from("bool"),
            },
        ]
    );
}

5、创建自定义迭代器

使用lterator trait来创建自定义迭代器

实现next方法

struct Counter {
    count:u32,
}

impl Counter {
    fn new() -> Counter {
        Counter{count:0}
    }
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}

#[test]
fn calling_next_directly() {
    let mut counter = Counter::new();

    assert_eq!(counter.next(),Some(1));
    assert_eq!(counter.next(),Some(2));
    assert_eq!(counter.next(),Some(3));
    assert_eq!(counter.next(),Some(4));
    assert_eq!(counter.next(),Some(5));
    assert_eq!(counter.next(),None);
}

#[test]
fn using_other_iterator_trait_methods() {
    let sum:u32 = Counter::new()
    .zip(Counter::new().skip(1))
    .map(|(a,b)|a*b)
    .filter(|x|x%3 == 0)
    .sum();

    assert_eq!(18,sum);
}

zip这个函数是把两个数组的数据合并到一个迭代器里面

6、改进I/○项目(minigrep)

// TODO 待续

Rust的实例:命令行程序

lib.rs

use std::fs;
use std::error::Error;
use std::env;

pub fn run(config:Config) -> Result<(),Box<dyn Error>>  {
    let contents = fs::read_to_string(config.filename)?;
    let results = if config.case_sensitive {
        search(&config.query, &contents)
    } else {
        search_case_insensitive(&config.query, &contents)
    };
    for line in results {
        println!("{}",line)
    }
    Ok(())
}

pub struct Config {
    pub query:String,
    pub filename:String,
    pub case_sensitive:bool
}

impl Config { 
    pub fn new(mut args: std::env::Args) -> Result<Config,&'static str> {
        if args.len() < 3 {
            return Err("没有足够的参数");
        }

        args.next();

        let query = match args.next() {
            Some(s) => s,
            None=>return Err("获取不到查询的参数")
        }; 

        let filename = match args.next() {
            Some(s) => s,
            None=>return Err("获取不到文本路径参数")
        }; 

        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();

        Ok(Config{query,filename,case_sensitive})
    }
}

pub fn search<'a>(query:&str,contents:&'a str) -> Vec<&'a str> {
    // let mut results = Vec::new();

    // for line in contents.lines() {
    //     if line.contains(query) {
    //         results.push(line.trim());
    //     }
    // }

    // results

    contents.lines().filter(|content|content.contains(query)).collect()
}

pub fn search_case_insensitive<'a>(query:&str,contents:&'a str) -> Vec<&'a str> {
    // let mut results = Vec::new();
    // let query = query.to_lowercase();
    // for line in contents.lines() {
    //     if line.to_lowercase().contains(&query) {
    //         results.push(line.trim());
    //     }
    // }

    // results
    let query = query.to_lowercase();
    contents.lines().filter(|content|content.to_lowercase().contains(&query)).collect()
}

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

    #[test] 
    fn case_sensitive() {
        // 区分大小写
        let query = "duct";
        let contents = "\
            Rust
            safe,fast,productive.
            Pick three.
            Duck tape.
        ";
        assert_eq!(vec!["safe,fast,productive."],search(query,contents))
    }

    #[test]
    fn case_insensitive() {
        // 不区分大小写
        let query = "rUSt";
        let contents = "\
            Rust:
            safe,fast,productive.
            Pick three.
            Trust me.
        ";
        assert_eq!(vec!["Rust:","Trust me."],search_case_insensitive(query,contents))
    }
}

main.rs

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

fn main() {

    let config = Config::new(env::args()).unwrap_or_else(|err|{
        eprintln!("参数解析失败:{}",err);
        process::exit(1);
    });

    if let Err(e) = minigrep::run(config) {
        eprintln!("程序错误{}",e);
        process::exit(1);
    }
}

7、循环vs 迭代器

迭代器是rust里面的高级抽象,但是迭代器在编译后生成的代码几乎我们手写的代码时一样的产物,这在rust里面被称为:

零开销抽象

Zero-Cost Abstraction。使用抽象时不会引入额外的运行时开销。

 

你好:我的2025

上一篇:Rust的闭包

下一篇:Rust的发布配置