当前位置: 首页 > news >正文

【Rust迭代器】Rust迭代器用法解析与应用实战

在这里插入图片描述

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Rust开发,Python全栈,Golang开发,云原生开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏:Rust语言通关之路
景天的主页:景天科技苑

在这里插入图片描述

文章目录

  • Rust迭代器
    • 1. 迭代器基础
      • 1.1 什么是迭代器
      • 1.2 创建迭代器
      • 1.3 使用迭代器
    • 2. 迭代器适配器
      • 2.1 map
      • 2.2 filter
      • 2.3 filter_map
      • 2.4 flat_map
      • 2.5 take和take_while
      • 2.6 skip和skip_while
      • 2.7 zip
      • 2.8 enumerate
      • 2.9 chain
      • 2.10 cycle
    • 3. 消费迭代适配器
      • 3.1 collect
      • 3.2 fold
      • 3.3 any和all
      • 3.4 find和position
      • 3.6 max和min
      • 3.7 sum和product
      • 3.8 count
      • 3.9 last和nth
    • 4. 高级迭代器用法
      • 4.1 自定义迭代器
      • 4.2 Peekable迭代器
      • 4.3 迭代器性能
      • 4.4 并行迭代器
    • 5. 迭代器性能优化技巧
      • 1)避免中间集合:尽量使用迭代器链而不是创建中间集合
      • 2)使用for_each代替for循环:在某些情况下可能更高效
      • 3)利用短路求值:any和all会在确定结果后立即停止迭代
      • 4)选择合适的迭代器:根据需求选择iter()、iter_mut()或into_iter()
      • 5)预分配空间:当最终需要集合时,可以预分配空间提高性能
    • 6. 常见问题与解决方案
      • 1)迭代器被消耗:迭代器是消耗性的,一旦遍历完成就不能再使用
      • 2)所有权问题:into_iter()会获取所有权
      • 3)无限迭代器:某些迭代器(如cycle)是无限的,需要与take等适配器一起使用
      • 4)惰性求值:迭代器是惰性的,只有在消费者调用时才会执行
    • 7. 总结

Rust迭代器

迭代器是Rust语言中一个强大且高效的概念,它为处理集合和数据序列提供了统一、安全且零成本抽象的接口。
Rust的迭代器不仅使用方便,而且在编译时就能进行大量优化,使得最终生成的代码性能通常能与手写的循环相媲美。
迭代器模式允许你对一个项的序列进行某些处理。迭代器(iterator)负责遍历序列中的每一项和决定序列何时结束的逻辑。
当使用迭代器时,我们无需重新实现这些逻辑。

1. 迭代器基础

1.1 什么是迭代器

在Rust中,迭代器是实现了Iterator trait的任何类型都是迭代器。这个Iterator trait是标准库中的trait。源码中,这个trait定义如下:

pub trait Iterator {type Item;//type Item和Self::Item这种用法叫做定义trait的关联类型。这个在项目中用的比较多。这个next方法是被要求实现该trait唯一必须要实现的方法,该trait的其他方法是有默认实现fn next(&mut self) -> Option<Self::Item>; // 提供了许多默认方法...
}

next方法是被要求实现该trait唯一必须要实现的方法,该trait的其他方法是有默认实现
实例对象每调用一次next方法,返回一个元素,当迭代器结束时,返回None。
任何实现了Iterator trait的类型都可以被迭代,产生一系列值。

1.2 创建迭代器

Rust中的大多数集合类型都提供了创建迭代器的方法:

fn main() {// 创建迭代器,迭代器是实现了Iterator trait的对象// 通过集合创建迭代器let mut vec = vec![1, 2, 3];// 获取不可变引用迭代器let iter = vec.iter(); // 产生 &i32// 获取可变引用迭代器let iter_mut = vec.iter_mut(); // 产生 &mut i32// 获取所有权迭代器let into_iter = vec.into_iter(); // 产生 i32
}

创建可变引用迭代器

//创建mutable迭代器
let mut vec = vec![1, 2, 3];
for num in vec.iter_mut() {*num += 1;println!("{}", num);
}

在这里插入图片描述

创建获取所有权迭代器

//创建获取所有权的迭代器
let vec = vec![1, 2, 3];
for num in vec.into_iter() {println!("{}", num);
}
println!("{:?}", vec); // 错误!vec的所有权已移动到迭代器中

在这里插入图片描述

在 Rust 中,迭代器是 惰性的(lazy),这意味着直到调用方法消费迭代器之前它都不会有效果。

1.3 使用迭代器

创建迭代器之后,可以选择用多种方式利用它。
最简单的使用方式是for循环:

fn main() {let vec = vec![1, 2, 3];//使用迭代器for num in vec.iter() {println!("{}", num);}
}

在这里插入图片描述

使用for_each代替for循环:在某些情况下可能更高效

//for_each
let nums = vec![1, 2, 3, 4, 5];
//for_each方法会遍历迭代器中的所有元素,并对每个元素执行闭包
nums.iter().for_each(|x| println!("{}", x));

在这里插入图片描述

也可以手动调用next()方法:
返回的是Option
在这里插入图片描述

let mut iter = vec![1, 2, 3].iter();
//手动调用next方法
println!("{:?}", iter.next());
println!("{:?}", iter.next());
println!("{:?}", iter.next());
println!("{:?}", iter.next());

当迭代器结束时,返回None
在这里插入图片描述

2. 迭代器适配器

Rust提供了丰富的迭代器适配器方法,可以对迭代器进行各种转换和组合。

2.1 map

map对每个元素应用一个函数:
let nums = vec![1, 2, 3];
let squares: Vec<_> = nums.iter().map(|x| x * x).collect();
println!("{:?}", squares);

在这里插入图片描述

2.2 filter

filter只保留满足条件的元素:

let nums = vec![1, 2, 3, 4, 5];
let evens: Vec<_> = nums.iter().filter(|x| *x % 2 == 0).collect();
println!("{:?}", evens);

在这里插入图片描述

2.3 filter_map

结合了filter和map的功能:
filter_map 会调用 parse 方法,如果成功则返回 Some,失败则返回 None

let strings = vec!["3", "seven", "8", "nine"];
let numbers: Vec<i32> = strings.iter().filter_map(|s| s.parse().ok()).collect();
println!("{:?}", numbers);

在这里插入图片描述

2.4 flat_map

将每个元素转换为迭代器后展平
flat_map 会将每个单词的字符展开成一个迭代器,然后将这些迭代器连接起来

let words = vec!["hello", "world"];
let letters: Vec<_> = words.iter().flat_map(|w| w.chars()).collect();
println!("{:?}", letters);

在这里插入图片描述

2.5 take和take_while

take获取前n个元素:
在Rust中,Iterator 类型的 take(n) 方法是用来创建一个新的迭代器,这个新的迭代器将只包含原始迭代器的前 n 个元素。这意味着原始迭代器不会被消耗掉,你可以在之后再次使用它。

//take
let numbers = vec![1, 2, 3, 4, 5];// 使用 take 方法,获取前3个元素
let iter = numbers.iter().take(3);// 遍历新的迭代器
for number in iter {println!("{}", number);
}// 原始迭代器仍然可以再次使用
for number in numbers.iter() {println!("{}", number);
}

在这里插入图片描述

take_while获取满足条件的连续元素:

//take_while
let numbers = vec![1, 2, 3, 4, 5];// 使用 take_while 方法,获取满足条件小于3的元素
// take_while 会返回一个新的迭代器,这个迭代器会从原始迭代器中获取元素,直到满足条件为止
let iter = numbers.iter().take_while(|x| **x < 3);// 遍历新的迭代器
for number in iter {println!("{}", number);
}

在这里插入图片描述

2.6 skip和skip_while

与take相反,跳过元素:

//skip
let numbers = vec![1, 2, 3, 4, 5];// 使用 skip 方法,跳过前3个元素
let iter = numbers.iter().skip(3);// 遍历新的迭代器
for number in iter {println!("{}", number);
}//skip_while
let numbers = vec![1, 2, 3, 4, 5];// 使用 skip_while 方法,跳过满足条件小于3的元素
let iter = numbers.iter().skip_while(|x| **x < 3);// 遍历新的迭代器
for number in iter {println!("{}", number);
}

在这里插入图片描述

2.7 zip

将两个迭代器合并为一个元组迭代器:

//zip
let numbers = vec![1, 2, 3, 4, 5];
let letters = vec!['a', 'b', 'c', 'd', 'e'];// 使用 zip 方法,将两个迭代器组合成一个元组迭代器
let iter = numbers.iter().zip(letters.iter());// 遍历新的迭代器
for (number, letter) in iter {println!("{} - {}", number, letter);
}

在这里插入图片描述

2.8 enumerate

为每个元素添加索引:

//enumerate
let numbers = vec![1, 2, 3, 4, 5];// 使用 enumerate 方法,获取索引和元素
let iter = numbers.iter().enumerate();// 遍历新的迭代器
for (index, number) in iter {println!("{} - {}", index, number);

在这里插入图片描述

2.9 chain

连接两个迭代器:
注意:chain只能连接相同类型的迭代器

//chain
//注意:chain只能连接相同类型的迭代器
let numbers = vec![1, 2, 3];
let letters = vec![4, 5, 6];// 使用 chain 方法,将两个迭代器连接起来
let iter = numbers.iter().chain(letters.iter());// 遍历新的迭代器
for item in iter {println!("{}", item);
}

在这里插入图片描述

2.10 cycle

无限循环迭代器:

//cycle
let numbers = vec![1, 2, 3];// 使用 cycle 方法,将迭代器循环起来
let iter = numbers.iter().cycle(); // cycle会无限循环迭代器// 遍历新的迭代器
for (index, item) in iter.enumerate() {//设置个退出条件,否则会无限循环if index > 10 {break;}println!("{}", item);
}

在这里插入图片描述

3. 消费迭代适配器

消费者是消耗迭代器并产生最终结果的方法。

3.1 collect

将迭代器转换为集合:

let nums = 1..=5;
let squared: Vec<i32> = nums.map(|x| x * x).collect();
println!("{:?}", squared);

在这里插入图片描述

3.2 fold

累积计算:

//fold
//fold求和
let nums = 1..=5;
let sum = nums.fold(0, |acc, x| acc + x);println!("{}", sum);//使用fold求平均值
let nums = vec![1, 2, 3, 4, 5];
let avg = nums.iter().fold(0.0, |acc, x| acc + (*x as f64)) / (nums.len() as f64);println!("{}", avg);

在这里插入图片描述

3.3 any和all

检查是否存在或所有元素满足条件:

//any
let nums = vec![1, 2, 3, 4, 5];
//any方法会返回true或false,表示迭代器中任意一个元素满足条件即可
let has_even = nums.iter().any(|x| x % 2 == 0);println!("{}", has_even);//all
//all方法会返回true或false,表示迭代器中所有元素都满足条件才返回true
let nums = vec![1, 2, 3, 4, 5];
let all_even = nums.iter().all(|x| x % 2 == 0);println!("{}", all_even);

在这里插入图片描述

3.4 find和position

find和position都会返回Option
查找元素:

//find 返回的是满足条件的元素的引用Option
let nums = vec![1, 2, 3, 4, 5];
//find方法会返回Option,表示迭代器中找到的第一个满足条件的元素
let first_even = nums.iter().find(|x| **x % 2 == 0);//find方法返回Option,所以需要使用unwrap_or_else来处理None的情况
let first_even = first_even.unwrap_or_else(|| &0);
println!("{:?}", first_even);//position。返回的是索引Option
let nums = vec![1, 2, 3, 4, 5];
//position方法会返回Option,表示迭代器中找到的第一个满足条件的元素的索引
let first_even_index = nums.iter().position(|x| *x % 2 == 0);//position方法返回Option,所以需要使用unwrap_or_else来处理None的情况
let first_even_index = first_even_index.unwrap_or_else(|| 0);
println!("{:?}", first_even_index);

在这里插入图片描述

3.6 max和min

查找最大最小值:
返回的也是Option

//max
let nums = vec![1, 2, 3, 4, 5];
//max方法会返回Option,表示迭代器中最大的元素
let max = nums.iter().max();//max方法返回Option,所以需要使用unwrap_or_else来处理None的情况
let max = max.unwrap_or_else(|| &0);
println!("{:?}", max);//min
let nums = vec![1, 2, 3, 4, 5];
//min方法会返回Option,表示迭代器中最小的元素
let min = nums.iter().min();//min方法返回Option,所以需要使用unwrap_or_else来处理None的情况
let min = min.unwrap_or_else(|| &0);
println!("{:?}", min);

在这里插入图片描述

3.7 sum和product

求和与求积:

//sum
let nums = vec![1, 2, 3, 4, 5];
//sum方法会返回迭代器中所有元素的和
let sum = nums.iter().sum::<i32>(); //sum方法需要指定返回值的类型,在变量处标注类型也可以
println!("{:?}", sum);//product
let nums = vec![1, 2, 3, 4, 5];
//product方法会返回迭代器中所有元素的积
let product = nums.iter().product::<i32>(); //product方法需要指定返回值的类型,在变量处标注类型也可以
println!("{:?}", product);

在这里插入图片描述

3.8 count

计算元素数量:

//count
let nums = vec![1, 2, 3, 4, 5];
//count方法会返回usize,表示迭代器中所有元素的数量
let count = nums.iter().count();
println!("{:?}", count);

在这里插入图片描述

3.9 last和nth

last: 表示获取迭代器中最后一个元素
nth: 表示获取迭代器中第n个元素,n表示索引位置

//last
let nums = vec![1, 2, 3, 4, 5];
//last方法会返回Option,表示迭代器中最后一个元素
let last = nums.iter().last();//last方法返回Option,所以需要使用unwrap_or_else来处理None的情况
let last = last.unwrap_or_else(|| &0);
println!("{:?}", last);//nth
let nums = vec![1, 2, 3, 4, 5];
//nth方法会返回Option,表示迭代器中第n个元素,n从0开始
let third = nums.iter().nth(2);//nth方法返回Option,所以需要使用unwrap_or_else来处理None的情况
let third = third.unwrap_or_else(|| &0);
println!("{:?}", third);

在这里插入图片描述

4. 高级迭代器用法

4.1 自定义迭代器

我们可以实现自己的迭代器类型:

//自定义迭代器
struct Counter {count: u32,max: u32,
}impl Counter {fn new(max: u32) -> Counter {Counter { count: 0, max }}
}
//自定义迭代器,只需要我们创建的类实现Iterator这个trait即可
impl Iterator for Counter {type Item = u32; //type Item = u32;表示迭代器中元素的类型,在trait中定义//next方法是Iterator trait中定义的,我们需要实现它//next方法的返回值是Option<Self::Item>,表示迭代器中下一个元素,如果迭代器中没有下一个元素,则返回None//next方法的参数是&mut self,表示迭代器的可变引用,因为迭代器需要在每次调用next方法时修改内部的状态//Self::Item表示迭代器中元素的类型,我们在上面定义为u32fn next(&mut self) -> Option<Self::Item> {//如果计数器小于最大值,则增加计数器并返回Some(self.count),否则返回Noneif self.count < self.max {self.count += 1;Some(self.count)} else {None}}
}
fn main() {//创建Counter实例,传的参数是最大值let counter = Counter::new(5);//for循环会自动调用迭代器的next方法,直到迭代器返回Nonefor num in counter {println!("{}", num);}
}

在这里插入图片描述

4.2 Peekable迭代器

peekable允许我们查看下一个元素而不消耗它:
eekable 是 Rust 迭代器的一个适配器方法,它可以将任何迭代器转换为可以"偷看"下一个元素的迭代器。
这个功能在很多场景下非常有用,特别是当你需要基于下一个元素的值来决定当前如何处理时。
基本用法

let mut iter = [1, 2, 3].iter().peekable();

主要使用场景

  1. 前瞻性处理(Look-ahead Processing)
    当需要根据下一个元素决定当前元素如何处理时:
let numbers = [1, 2, 3, 4, 5];
let mut iter = numbers.iter().peekable();while let Some(num) = iter.next() {if let Some(&&next_num) = iter.peek() {println!("当前: {}, 下一个: {}", num, next_num);} else {println!("当前: {}, 这是最后一个", num);}
}
  1. 合并连续重复项
    处理连续重复的元素:
fn dedup_adjacent<I: Iterator<Item = i32>>(iter: I) -> impl Iterator<Item = i32> {let mut peekable = iter.peekable();std::iter::from_fn(move || {while let Some(current) = peekable.next() {if peekable.peek() != Some(&current) {return Some(current);}}None})
}
  1. 解析器和词法分析器
    在编写解析器时经常需要前瞻:
fn tokenize(input: &str) -> Vec<Token> {let mut chars = input.chars().peekable();let mut tokens = Vec::new();while let Some(c) = chars.next() {match c {'0'..='9' => {let mut num = c.to_string();while let Some('0'..='9') = chars.peek() {num.push(chars.next().unwrap());}tokens.push(Token::Number(num.parse().unwrap()));}// 其他token处理...}}tokens
}
  1. 条件性跳过元素
    基于后续元素决定是否跳过当前元素:
let mut iter = [1, 2, 3, 4, 5].iter().peekable();
let mut result = Vec::new();while let Some(&num) = iter.next() {if num == 3 && iter.peek() == Some(&&4) {// 跳过3如果下一个是4continue;}result.push(num);
}
  1. 分隔符处理
    处理以特定模式分隔的数据:
fn split_by_double_newline(text: &str) -> Vec<&str> {let mut lines = text.lines().peekable();let mut paragraphs = Vec::new();let mut current = String::new();while let Some(line) = lines.next() {current.push_str(line);if lines.peek() == Some(&"") {paragraphs.push(current);current = String::new();lines.next(); // 跳过空行}}if !current.is_empty() {paragraphs.push(current);}paragraphs
}

Peekable的重要方法
Peekable 迭代器提供了几个特有方法:
1)peek() - 返回下一个元素的引用但不消耗它
if iter.peek() == Some(&value) { … }

2)next() - 返回下一个元素并且消耗它
let next = iter.next();

3)next_if() - 条件性消耗下一个元素
// 只有当下一个元素匹配谓词时才消耗它
while let Some(item) = iter.next_if(|&x| x > 5) { … }

4)next_if_eq() - 只有当下一个元素等于给定值时才消耗它
// 只有当下一个元素是5时才消耗它
if let Some(five) = iter.next_if_eq(&5) { … }

示例:

//peekable
let nums = vec![1, 2, 3, 4, 5];
//peekable方法会返回一个Peekable迭代器,这个迭代器可以预览下一个元素,但是不会消费下一个元素
let mut iter = nums.iter().peekable();//peek方法会返回Option,表示预览下一个元素,但是不会消费下一个元素
let next = iter.peek();
println!("{:?}", next);//next方法会返回Option,表示获取下一个元素,并消费这个元素
let next = iter.next();
println!("{:?}", next);//再次调用peek方法,会返回下一个元素的引用,因为上一次调用next方法已经消费了上一个元素
let next = iter.peek();
println!("{:?}", next);

在这里插入图片描述

4.3 迭代器性能

Rust迭代器是零成本抽象,编译器能将其优化为高效的代码。例如:
let sum: i32 = (1…=100).filter(|x| x % 2 == 0).sum();
编译器会将其优化为类似手写循环的代码,而不会有额外的开销。

4.4 并行迭代器

需要在项目中安装第三方库

cargo add rayon

在这里插入图片描述

使用rayon库可以实现并行迭代:

//并行迭代器
use rayon::prelude::*;
fn main() {//使用并行迭代器let sum: u128 = (1..=100000).into_par_iter().filter(|x| x % 2 == 0) //过滤出偶数.sum(); //求和println!("{}", sum);
}

在这里插入图片描述

5. 迭代器性能优化技巧

1)避免中间集合:尽量使用迭代器链而不是创建中间集合

// 不好
let filtered: Vec<_> = nums.iter().filter(|x| x % 2 == 0).collect();
let doubled: Vec<_> = filtered.iter().map(|x| x * 2).collect();// 好
let result: Vec<_> = nums.iter().filter(|x| x % 2 == 0).map(|x| x * 2).collect();

2)使用for_each代替for循环:在某些情况下可能更高效

(0..100).for_each(|x| println!("{}", x));

3)利用短路求值:any和all会在确定结果后立即停止迭代

4)选择合适的迭代器:根据需求选择iter()、iter_mut()或into_iter()

5)预分配空间:当最终需要集合时,可以预分配空间提高性能

let mut result = Vec::with_capacity(100);
(0..100).map(|x| x * 2).for_each(|x| result.push(x));

6. 常见问题与解决方案

1)迭代器被消耗:迭代器是消耗性的,一旦遍历完成就不能再使用

let mut iter = vec![1, 2, 3].into_iter();
iter.next();  // 消耗第一个元素
let sum: i32 = iter.sum();  // 只会计算剩下的元素

2)所有权问题:into_iter()会获取所有权

一旦使用了into_iter(),原数据将不可再使用
let vec = vec![1, 2, 3];
let iter = vec.into_iter();  // vec的所有权被转移
// 这里不能再使用vec

3)无限迭代器:某些迭代器(如cycle)是无限的,需要与take等适配器一起使用

let first_ten = (0..).cycle().take(10).collect::<Vec<_>>();

4)惰性求值:迭代器是惰性的,只有在消费者调用时才会执行

let iter = (0..10).map(|x| {println!("Processing {}", x);x * 2
});  // 这里不会打印任何东西let result: Vec<_> = iter.collect();  // 现在才会执行

7. 总结

Rust的迭代器是一个强大而灵活的工具,它提供了高效、安全的方式来处理各种数据序列。
通过本文的学习,相信大家应该已经掌握了迭代器的基本用法、各种适配器和消费者方法,以及如何在实际项目中应用它们。
迭代器不仅能写出更简洁、更表达性的代码,还能保持高性能,这是Rust语言"零成本抽象"哲学的重要体现。
记住,熟练掌握迭代器需要实践。尝试在你们自己的Rust项目中使用迭代器,开始时可能会遇到一些困难,但随着经验的积累,你会越来越欣赏它们带来的便利和效率。

http://www.xdnf.cn/news/522541.html

相关文章:

  • Python Django 的 ORM 编程思想及使用步骤
  • R语言数据可视化
  • Elasticsearch 深入分析三种分页查询【Elasticsearch 深度分页】
  • 力扣面试150题--从前序与中序遍历序列构造二叉树
  • Windows 下 Nginx 安装与配置指南 [特殊字符]
  • Axure难点解决分享:垂直菜单展开与收回(4大核心问题与专家级解决方案)
  • LeetCode 35 搜索插入位置题解
  • Axure设计数字乡村可视化大屏:构建乡村数据全景图
  • 【滑动窗口】LeetCode 1004题解 | 最大连续1的个数 Ⅲ
  • 小程序弹出层/抽屉封装 (抖音小程序)
  • CSS- 4.6 radiu、shadow、animation动画
  • CVE-2015-4553 Dedecms远程写文件
  • prisma连接非关系型数据库mongodb并简单使用
  • 【QT】类A和类B共用类C
  • 分布式数据库TiDB:深度解析原理、优化与架构设计
  • 永磁同步电机高性能控制算法(22)——基于神经网络的转矩脉动抑制算法为什么低速时的转速波动大?
  • 批量剪辑 + 矩阵分发 + 数字人分身源码搭建全技术解析,支持OEM
  • 【NLP】37. NLP中的众包
  • VR 互动实训与展示,借科技开启沉浸式体验新篇​
  • 【内测征集】LarkVR 播控系统上新:VR 应用一站式专业播控与管理工具
  • 基于CATIA参数化圆锥建模的自动化插件开发实践——NX建模之圆锥体命令的参考与移植(一)
  • Python函数——万字详解
  • Windows 安装显卡驱动
  • Linux云计算训练营笔记day11(Linux CentOS7)
  • esp32课设记录(五)整个项目开源github
  • 用Python将 PDF 中的表格提取为 Excel/CSV
  • 腾讯云安装halo博客
  • 游戏引擎学习第294天:增加手套
  • LeetCode 217.存在重复元素
  • 大语言模型训练数据格式:Alpaca 和 ShareGPT