不吐不快:最近在力扣的过程中,经常会遇到for循环、while循环,各种循环的过程,然后边界判断一箩筐,状态转换一箩筐,最后还没有访问越界。小心翼翼地打开力扣的题解,看到他们优雅地用迭代器各种求解,“艹,这么牛逼,玛德。”。我实在是受够了自己羸弱的代码能力,决定把迭代交给Rust官方的迭代器。话不多说,我们也走起!
迭代器,顾名思义,就是帮助我们进行迭代。我们可以先想这么一个事情,假设现在需要从1打印到100,我们正常做法是什么?
for i in (0..100) {
print!("{}", i);
}
如果你会这样写,说明你已经掌握90%迭代器的使用方法了。但可惜的是,我们经常会各种访问越界,为什么呢?因为我们通常在迭代的过程中,是需要对集合(例如数组、向量、链表等)进行访问和操作的。
这么一访问,坏事了,天知道会有什么问题。
为了防止我们胡乱地访问,Rust官方提供了一种声明式的方式来遍历序列,其核心思想是数据处理和数据本身分离。
什么意思呢,还是上边的代码,我们优化一下。
(0..100).for_each(|x|print!("{}", x));
我们能够明显地看到,遍历这一繁琐的操作就这样被我们一笔带过了,我们会更加专注于功能上的实现,也就是打印输出。
到现在还是懵懵懂懂的对不对?
别着急,还剩10%,如果我们乍一看各种教程方法论,里边各种规则会让我们十分头疼,我会给每一个规则都举出一些例子,尽量通俗易懂一些。
先创建一个迭代器
我现在定义一个数组let nums = vec![1,2,3,4,5];,我现在假设有这么一个需求,我需要对每一个元素都进行平方,并且返回一个新的数组,该怎么办呢?
先看传统方法:
let mut squared = Vec::new();
for num in nums {
squared.push(num * num);
}
很棒,优美的写法,但是我们发现没有,这实际上是一种一对一的映射,1变成1,2变成4,以此类推,刚好Rust给我们提供了这种映射方式,请看:
let squared: Vec<i32> = nums.iter().map(|x| x*x).collect();
// 也可以写成
let squared: Vec<i32> = nums.iter()
.map(|x| x*x)
.collect();
这iter是啥呀,map、collect又是啥呀?
实际上,.iter(), .iter_mut(), .into_iter(),这三种方法都可以用来创建迭代器。
let vec = vec![1,2,3];
let iter = vec.iter();
let iter1 = vec.into_iter();
let iter2 = vec.iter_mut();
into_iter,它被叫做消费迭代器,会获取集合的所有权,集合本身会被消费掉。这意味着在使用into_iter之后,原集合不能再被访问,因为它的所有权已经被转移给了迭代器。
fn process_lines(lines: Vec<String>) {
for line in lines.into_iter() {
// 处理每一行,获取所有权
println!("Processing line: {}", line);
// 可以在这里对 line 进行修改或消耗操作
// line 在每次迭代后都会被丢弃
}
// 此时 lines 向量已经不能被访问,因为它已经被消费掉
}
fn main() {
let file_lines = vec!["Line 1".to_string(), "Line 2".to_string()];
process_lines(file_lines);
// 下面的代码会报错,因为 file_lines 已经被移动
// println!("After processing: {:?}", file_lines);
}
iter是一个只读的迭代器,它不会获取集合的所有权,只是借用集合的引用来进行迭代。这样可以在不改变集合内容的情况下,安全地读取每一个元素。注意,这里提到是读取,实际上也是不能修改的。
我们可以看下边这个例子:
fn main() {
let nums = vec![1, 2, 3, 4, 5];
// 使用 |&x| 提取值
let squares: Vec<i32> = nums.iter().map(|&x| x * x).collect();
println!("Squares with |&x|: {:?}", squares);
// 不使用 |&x|,手动解引用
let squares_ref: Vec<i32> = nums.iter().map(|x| *x * *x).collect();
println!("Squares without |&x|: {:?}", squares_ref);
}
nums.iter()会返回一个迭代器,每次迭代返回一个&i32的值,而在map的过程中,|&x|在闭包参数中进行模式匹配,自动解引用x,那么x就是i32类型的值,因此x*x是直接对值进行操作。
而|x|接受一个&i32类型的引用,x手动解引用x,获取i32类型的值,因此x同样是对值进行操作。
最后iter_mut()是生成一个可变迭代器,允许你在迭代过程中修改集合中的元素,它和iter类似,但是对元素提供了可以修改的访问权限,例如:
fn scale_values(vec: &mut Vec<f64>, factor: f64) {
for num in vec.iter_mut() {
*num *= factor;
}
}
fn main() {
let mut values = vec![1.0, 2.0, 3.0];
scale_values(&mut values, 2.5);
// values 可以在这里继续被访问和修改,因为它只是被借用给了 scale_values 函数
println!("Scaled values: {:?}", values);
}
实际上会对vec中的所有的值进行修改,但是又没有获取到它的所有权。
迭代器的大方法
之前我们提到的map就是迭代器中比较常见的方法,下面我们分类介绍一些比较常见好用的方法,同时也把我们在刷题过程中遇到的迭代(动态规划、双指针)等问题应用起来。
未完待续......
Rust 迭代器 | 菜鸟教程 明天接着写,不着急一天写完,主要是为了自己总结,并不是为了阅读量。