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):使用抽象时不会引入额外的运行时开销。