Skip to content

什么是迭代器?

在Rust中,迭代器是一种用于遍历集合(数组、向量、哈希表等)元素的机制。它提供了一种抽象的方式来处理序列中的元素,而无需直接访问底层的数据结构。Rust 提供了优雅的方式来处理集合类型,这也是我们之前文章里提到过的 Rust 的零成本抽象。下面我们在进行介绍的时候会同时让一些常用 API 和 JavaScript 的来进行对比学习。

迭代器的作用

迭代器的主要作用是提供一种统一的接口来遍历集合中的元素,并允许在遍历过程中进行各种操作。通过使用迭代器,我们可以以声明性的方式编写代码,而不是通过手动的循环和索引操作。

迭代器的使用有以下几个优点:

  1. 简洁性: 迭代器提供了一组统一的方法,使得代码更加简洁易读。

  2. 延迟计算: 迭代器是惰性计算的,它只在需要的时候计算下一个元素,避免了不必要的计算。

  3. 组合性: 不同的迭代器可以通过组合使用来实现复杂的操作,例如映射(map)、过滤(filter)、折叠(fold)等。

常用迭代器 API

创建迭代器

在Rust中,迭代器可以通过集合类型的方法来创建,也可以通过标准库提供的迭代器创建方法。以下是一些常用的迭代器创建方法:

从集合创建

rust
use std::slice::Iter;

fn main() {
    // 使用数组创建也行 let vec = [1, 2, 3, 4, 5];
    let vec = vec![1, 2, 3, 4, 5];
    // 创建一个迭代器来遍历向量
    let iter_from_vec: Iter<'_, i32> = vec.iter();
    // 然后可以调用迭代器对应的 API 方法了,比如 map、filter、fold 等方法
}

使用 iter_mut() 方法

rust
use std::slice::IterMut;

fn main() {
    let mut vec = vec![1, 2, 3, 4, 5];
    // 创建一个可变迭代器,用于修改集合中的元素
    let mut iter_mut: IterMut<'_, i32> = vec.iter_mut();
    // 打印 Some(1)
    println!("{:?}", iter_mut.next());
    // 打印 Some(2)
    println!("{:?}", iter_mut.next());
    // 直至遍历结束返回 None
}

下面我们就来看看对应的 JavaScript 的示例代码

javascript
// 创建数组,数组本身就是一个迭代器
const numbers = [1, 2, 3, 4, 5];
// 使用数组的 values() 方法创建迭代器
const iterFromArray = numbers.values();
// 打印 {value: 1, done: false}
console.log(iterFromArray.next());
// 可以一直调用 next 方法直到遍历结束
// 直至遍历结束返回 {value: undefined, done: true}

迭代器方法

Rust的迭代器提供了丰富的方法来进行各种操作,以下是一些常用的迭代器方法和对应的代码示例:

Collect

它的作用是将迭代器的元素收集到一个集合类型中,可以是数组、向量、哈希表等,下面我们会举例子

map()

map() 方法用于对迭代器中的每个元素进行映射,和 JS 的 map 方法有点像

rust
fn main() {
    let vec = vec![1, 2, 3, 4, 5];
    // 使用 collect 方法将迭代器转换回向量数组
    let mapped_vec: Vec<i32> = vec.iter().map(|x| x * 2).collect();
    // mapped_vec 现在是 [2, 4, 6, 8, 10]
    println!("{:?}", mapped_vec)
}

对应的 JavaScript 代码

js
const numbers = [1, 2, 3, 4, 5];
const mappedArray = [...numbers].map(x => x * 2);
// mappedArray 现在是 [2, 4, 6, 8, 10]

filter()

filter() 方法用于对迭代器中的元素进行过滤,和 JS 的 filter 方法差不多。

rust
fn main() {
    let vec = vec![1, 2, 3, 4, 5];
    // cloned 方法会复制一份新的数组,并且可以将 &T 类型转换为 T 类型
    let filtered_vec: Vec<i32> = vec.iter().filter(|&x| x % 2 == 0).cloned().collect();
    // vec 数组没变
    println!("{:?}", vec);
    // filtered_vec 现在是 [2, 4]
    println!("{:?}", filtered_vec);
}

对应的 JavaScript 代码

js
const numbers = [1, 2, 3, 4, 5];
// js 也会创建一个新的数组
const filteredArray = [...numbers].filter(x => x % 2 === 0);
// filteredArray 现在是 [2, 4]

fold()reduce()

可以对数组中的元素进行累加或累积等操作,对应 JS 中的 reduce 方法

rust
fn main() {
    let vec = vec![1, 2, 3, 4, 5];
    let sum: i32 = vec.iter().cloned().fold(0, |acc, x| acc + x);
    // 输出结果是 15
    println!("{}", sum);

    let sum: i32 = vec.iter().cloned().reduce(|acc, x| acc + x).unwrap();
    // 输出结果是 15
    println!("{}", sum);
}

对应的 JavaScript 代码

js
const numbers = [1, 2, 3, 4, 5];
// 使用 reduce()
const sum = [...numbers].reduce((acc, x) => acc + x, 0);
// sum 现在为 15

any()all()

any() 方法用于检查迭代器中是否存在满足条件的元素,all() 方法用于检查是否所有元素都满足条件,分别对应的是 JS 的 some 和 every 方法。

rust
fn main() {
    let vec = vec![1, 2, 3, 4, 5];
    // 看看是不是任意一个值是偶数
    let has_even = vec.iter().any(|&x| x % 2 == 0);
    // has_even 现在为 true
    println!("{}", has_even);

    // 看看是不是所有的值都是偶数
    let all_even = vec.iter().all(|&x| x % 2 == 0);
    // all_even 现在为 false
    println!("{}", all_even);
}
js
const numbers = [1, 2, 3, 4, 5];
// 使用 some() 和 every()
const hasEven = [...numbers].some(x => x % 2 === 0);
// hasEven 现在为 true

const allEven = [...numbers].every(x => x % 2 === 0);
// allEven 现在为 false

zip()

zip() 方法用于将两个迭代器合并成一个。

rust
fn main() {
    let numbers = vec![1, 2, 3];
    let words = vec!["one", "two", "three"];
    let zipped: Vec<_> = numbers.iter().zip(words.iter()).collect();
    // zipped 现在为 [(1, "one"), (2, "two"), (3, "three")]
    println!("{:?}", zipped);
}
js
const numbers = [1, 2, 3];
// 使用 zip()(JS 没有直接的 zip 方法,这里用了 lodash 库来模拟)
const zipped = _.zip(numbers, words);
// zipped 现在为 [[1, "one"], [2, "two"], [3, "three"]]

// 或者用 map 来模拟也可以
const words = ["one", "two", "three"];
const zipped = numbers.map((value, index) => [value, words[index]]);
// zipped 现在为 [[1, "one"], [2, "two"], [3, "three"]]

enumerate()

枚举方法,一般用于在迭代的时候可以访问到迭代器的对应下标索引:

rust
fn main() {
    let numbers = vec![1, 2, 3];
    for (i, number) in numbers.iter().enumerate() {
        println!("{i}, {number}");
    }
}
// 打印:
// Index: 0, Fruit: apple
// Index: 1, Fruit: banana
// Index: 2, Fruit: cherry

对应 JS 代码

js
const fruits = ["apple", "banana", "cherry"];
fruits.forEach((fruit, index) => {
    console.log(`Index: ${index}, Fruit: ${fruit}`);
});

takeskip

take 方法用于获取迭代器的前 N 个元素,而 skip 方法用于跳过迭代器的前 N 个元素。

rust
fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    // 调用 into_iter 方法会移动所有权,所以我们用 clone 方法克隆一份新数据
    let first_three: Vec<_> = numbers.clone().into_iter().take(3).collect();
    // first_three 现在为 [1, 2, 3]
    println!("{:?}", first_three);

    let last_two: Vec<_> = numbers.clone().into_iter().skip(3).collect();
    // last_two 现在为 [4, 5]
    println!("{:?}", last_two);
}

对应的 JS 代码

js
const numbers = [1, 2, 3, 4, 5];
const firstThree = numbers.slice(0, 3);
// firstThree 现在为 [1, 2, 3]

const lastTwo = numbers.slice(3);
// lastTwo 现在为 [4, 5]

chain()

chain 方法用于将两个迭代器连接成一个新的迭代器

rust
fn main() {
    let numbers = vec![1, 2, 3];
    let more_numbers = vec![4, 5, 6];
    let combined: Vec<_> = numbers.into_iter().chain(more_numbers).collect();
    // combined 现在为 [1, 2, 3, 4, 5, 6]
    println!("{:?}", combined);
}

对应的 JS 代码

js
const numbers = [1, 2, 3];
const moreNumbers = [4, 5, 6];
const combined = numbers.concat(moreNumbers);
// combined 现在为 [1, 2, 3, 4, 5, 6]

for_each()

遍历每一个元素并做一些操作,不返回值,对应于 JS 的 forEach 方法,只不过在迭代中没法拿到下标索引

rust
fn main() {
    let numbers = [1, 2, 3, 4, 5];
    numbers.iter().for_each(|val| {
        println!("val: {val}");
    });
}

clonedcopied

cloned 方法用于创建一个新的迭代器,其中每个元素都是原始迭代器中元素的克隆,而 copied 方法则用于创建一个新的迭代器,其中每个元素都是原始迭代器中元素的复制。

cloned 适用于任何实现了 Clone trait 的类型,它调用 clone 方法创建元素的克隆,

copied 适用于实现了 Copy trait 的类型,它通过位复制的方式创建元素的副本,避免了显式的 clone 调用

rust
#[derive(Clone, Copy, Debug)]
struct Number {
    value: i32,
}

fn main() {
    let numbers = vec![
        Number { value: 1 },
        Number { value: 2 },
        Number { value: 3 },
    ];

    let mut cloned: Vec<Number> = numbers.iter().cloned().collect();
    cloned[0].value = 10;
    //numbers: [Number { value: 1 }, Number { value: 2 }, Number { value: 3 }], cloned: [Number { value: 10 }, Number { value: 2 }, Number { value: 3 }]
    println!("numbers: {:?}, cloned: {:?}", numbers, cloned);

    let mut copied: Vec<_> = numbers.iter().copied().collect();
    copied[1].value = 10;
    // numbers: [Number { value: 1 }, Number { value: 2 }, Number { value: 3 }], copied: [Number { value: 1 }, Number { value: 10 }, Number { value: 3 }] 
    println!("numbers: {:?}, copied: {:?}", numbers, copied);
}

其它 API

另外还有 max, 对应 JS 的 max 方法,min 对应 JS 的 min 方法, rev 对应 JS 的 reverse 方法,count 方法对应于 JS 数组的 .length ,另外还有很多很多的方法,这里列举不完了,大家可以在实战中自己去尝试

for_each 对应

迭代器的实战例子

为了更好地理解迭代器在实际应用中的威力,让我们搞一个更复杂的例子:从一个日志文件中提取关键信息并进行统计。

假设我们有一个日志文件,其中包含了用户的访问记录。每行日志的格式如下:

plaintext
timestamp,username,action

比如根目录下有一个 log.txt 文件,里面的内容是:
2024-03-10,PuffMeow,exp

我们的目标是统计每个用户的访问次数。首先,我们将文件内容读取并解析为结构体:

rust
use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;

#[derive(Debug)]
struct LogEntry {
    timestamp: String,
    username: String,
    action: String,
}

// 用于新建结构体
impl LogEntry {
    fn new(timestamp: &str, username: &str, action: &str) -> Self {
        LogEntry {
            timestamp: timestamp.to_string(),
            username: username.to_string(),
            action: action.to_string(),
        }
    }
}

接下来,我们使用迭代器从文件中读取日志并进行处理:

rust

fn main() -> io::Result<()> {
    let file_path = "log.txt";
    let file = File::open(file_path)?;

    // 使用 BufReader 封装文件读取器
    let reader = BufReader::new(file);

    // 使用 lines() 方法获取迭代器,每次迭代返回一行日志
    let log_entries: Vec<LogEntry> = reader
        .lines()
        .filter_map(|line| {
            // 将每行日志解析为 LogEntry 结构体
            let line = line.ok()?;
            let parts: Vec<&str> = line.split(',').collect();
            if parts.len() == 3 {
                Some(LogEntry::new(parts[0], parts[1], parts[2]))
            } else {
                None
            }
        })
        .collect();

    // 统计每个用户的访问次数
    let user_access_count: HashMap<&str, usize> = log_entries
        .iter()
        .map(|entry| entry.username.as_str())
        .fold(HashMap::new(), |mut acc, username| {
            *acc.entry(username).or_insert(0) += 1;
            acc
        });

    // 输出统计结果
    // 输出结果:
    // User: PuffMeow, Access Count: 1
    for (username, count) in user_access_count {
        println!("User: {}, Access Count: {}", username, count);
    }

    Ok(())
}

总结

Rust 的迭代器是一种强大的工具,提供了一套很方便的 API,大部分功能基本不用我们自己实现了,使得处理集合数据变得更加简单、安全和高效。通过使用迭代器,我们能够以声明性的方式编写代码,将注意力集中在业务逻辑上,而不是底层的循环和索引操作上。好了,今天的文章先到这儿吧,码字一个多小时做的学习总结,抽象。

每天进步一丢丢