Rust 学习指南 - 迭代器

2,379 阅读5分钟
原文链接: www.codemore.top

简介

在编程中,我们经常需要计数,枚举,遍历某个序列。在各种编程语言中有很多方式可以做这些工作,例如C语言相似的语法:

for (int x = 0; i < 10; ++x) {
    // do something
}

虽然这种方式非常的灵活并且能够适应各种场景,但是这种方式是非常容易导致bug的,比如放错了分号导致无意义的循环等。本着安全和一致性的原则,在Rust中并没有实现类似C语言风格的遍历方式,而是通过迭代器来实现。

基本的范围循环

在Rust中循环遍历一系列数字最常用的方式是Range ,它由 ..创建,例如

for i in 1..11 {
    println!("{}", i);
}

上面的代码将会在控制台打印1-10的数字,但是不会打印11,如果想要打印11 可以使用..=

for i in 1..=11 {
     println!("{}", i);
}

如果你不需要迭代中的变量,可以直接使用_进行省略

let mut n = 0;
for _ in 1..11 {
    n += 1;
}
println!("num = {}", n);

对于上面的示例,Rust中有一个更方便的方法count(),这个方法会返回迭代器中的元素数量而无需在循环中计数。

println!("num = {}", (0..11).count());

深入理解迭代器

filter()

filter()接收一个闭包,这个闭包返回true或者false,返回的序列只会包含filter() 返回true的元素。

for i in (0..21).filter(|x| x % 2 == 0) {
    print!("{} ", i);
}

上面的函数将会返回所有 0 - 20 的偶数

rev()

由于序列都是自增的,如果需要递减序列,可以使用rev() 反转一个序列

for i in (0..11).rev() {
    print!("{} ", i);
}

上面的函数将会输出 10 - 0 的递减序列

map()

map()同样接受一个闭包作为参数,它将闭包应用于序列中所有的元素,并返回处理结果

for i in (1..10).map(|x| x * x) {
    print!("{} ", i);
}
fold()

fold()就是其他语言的reduce()它返回一个累加器,最终返回的是一个结果

let result = (1..=5).fold(0, |acc ,x| acc + x * x);
println!("result = {}", result);

上面的实现可以和下面的代码相同

let mut acc = 0;
for x in 1..=5 {
    acc += x * x;
}
let result = acc;
println!("result = {}", result);

迭代数组

和迭代Range相同,我们同样可以迭代一个数组,由于数组并不是迭代器类型,所以可以使用iter()方法将其转化为一个迭代器。

let cities = ["Toronto", "New York", "Melbourne"];

for city in cities.iter() {
    print!("{}, ", city);
}

组合使用迭代函数

在上面的例子中我们都是单独使用每个迭代函数,但是迭代器最大的优势就是可以组合使用,例如

for i in (0..=10).rev().filter(|x| x % 2 == 0) {
    print!("{} ", i);
}

上面的例子就会返回 10 - 0 所有的偶数。

如果需要一个非连续的序列,可以使用chain() 函数

let c = (1..4).chain(6..9);
for i in c {
  print!("{} ", i);
}

上面的示例 会打印 1 2 3 6 7 8

还有一个函数是zip()这个函数可以将两个迭代器组合在一起一一对应例如

let cities = ["Toronto", "New York", "Melbourne"];
let populations = [2_615_060, 8_550_405, ‎4_529_500];

let matrix = cities.iter().zip(populations.iter())

for (c, p) in matrix {
  println!("{:10}: population = {}", c, p);
}

输出

// output:
// Toronto   : population = 2615060
// New York  : population = 8550405
// Melbourne : population = 4529500

迭代字符

对于字符的迭代访问可以使用char_iter

[dependencies]
char-iter = "0.1"

然后就可以使用

extern crate char_iter;
for c in char_iter::new('Д', 'П') {
    print!("{} ", c);
}

迭代Vector

vector是Rust的基础结构之一,是一个动态数组,大多数情况下操作和数组相同,比如需要遍历vector。

let nums = vec![1,2,3,4,5];
for i in nums.iter() {
    print!("{} ", i);
}

也可以直接如下:

let nums = vec![1, 2, 3, 4, 5];
for i in &nums {
   print!("{} ", i);
}

注意,上面的例子是不可变借用,如果需要改变nums的内容需要使用可变借用

let mut nums = vec![1, 2, 3, 4, 5];
for i in &mut nums {
    *i *= 2;
}
println!("{:?}", nums);

当然,上面这种方式是不推荐,可以直接使用map() 处理

let nums = vec![1,2,3,4,5];
let nums = nums.iter().map(|x| x * 2).collect::<Vec<i32>>();

collect() 的作用是接受迭代器返回的值,然后将其收集转化为需要的类型集合。例如需要1 - 10 的Vec<i32> 类型.

let v = (1..11).collect::<Vec<i32>>();

如果需要获取vector的索引和值,可以使用enumearte() 函数

let v = vec![1,2,3];
for (i, n) in vec.iter().enumerate() {
    println!("v[{}] = {}", i, n);
}
// output:
// v[0] = 1
// v[1] = 2
// v[2] = 3

一些其他有用的函数例如,max(), min(),sum() 等

let v = vec![1,2,3];
let max = v.iter().max();
let min = v.iter().min();
println!("max = {:?}, min = {:?}", max, min);

// output: max = Some(5), min = Some(-2)
let grades = vec![4, 5, 6, 9, 7, 4, 8];
let sum: i32 = grades.iter().sum();
let gpa = sum as f32 / grades.len() as f32;

println!("sum = {}, gpa = {:.2}", sum, gpa);

// output: sum = 43, gpa = 6.14

itertools

itertools 库提供了非常多有用的迭代方法

Cargo.toml

[dependencies]
itertools = "0.6"

比如如果要处理上面使用filter() 获取所有偶数的例子,使用itertools 可以使用step() 实现

extern crate itertools;

use itertools::Itertools;

for i in (0..11).step_by(2) {
    print!("{} ", i);
}

unique() 函数可以进行去重

extern crate itertools;

use itertools::Itertools;

let data = vec![1,4,5,1,4,5];

let unique = data.iter().unique();
for d in unique {
  print!("{} ", d);
}

join() 函数可以将序列通过分隔符链接起来

extern crate itertools;

use itertools::Itertools;

let creatures = vec!["banshee", "basilisk", "centaur"];
let list = creatures.iter().join(", ");
println!("In the enchanted forest, we found {}.", list);

// output: In the enchanted forest, we found banshee, basilisk, centaur.

sorted_by() 进行排序

extern crate itertools;
use itertools::Itertools;

let happiness_index = vec![
    ("Canada", 7), ("Iceland", 4), ("Netherlands", 6),
    ("Finland", 1), ("New Zealand", 8), ("Denmark", 3),
    ("Norway", 2), ("Sweden", 9), ("Switzerland", 5)
  ];
let top_contries = happiness_index
  .into_iter()
  .sorted_by(|a, b| (&a.1).cmp(&b.1))
  .into_iter()
  .take(5);

for (country, rating) in top_contries {
  println!("# {}: {}", rating, country);
}

// output:
// # 1: Denmark
// # 2: Switzerland
// # 3: Iceland
// # 4: Norway
// # 5: Finland

自己编写迭代器

我们将会编写一个简单的迭代器,他的作用是输出一堆温度表示(华氏,摄氏),类型是(f32, f32)。 他们的计算公式是 c = (f - 32) / 1.8

首先定义一个struct

struct FahrToCel {
   fahr: f32,
   step: f32,
}
impl FahrToCel {
   fn new(fahr: f32, step: f32) -> FahrToCelc {
   FahrToCelc { fahr: fahr, step: step }
 }
}

下面为FahrToCel 实现Iterator trait

impl Iterator for FahrToCel {
    type Item = (f32, f32);
    
    fn next(&mut self) -> Option<Self::Item> {
        let curr_fahr = self.fahr;
    	let curr_celc = (self.fahr - 32.0) / 1.8;
    	self.fahr = self.fahr + self.step;
    	Some((curr_fahr, curr_celc))
    }
}

fn main() {
  // pass the starting temperature and step to the initializer function
  let ftc = FahrToCelc::new(0.0, 5.0);

  // produce the iterator table of first 5 values
  let temp_table = ftc.take(5);

  // print out the temperature table nicely
  for (f, c) in temp_table {
    println!("{:7.2} °F = {:7.2} °C", f, c);
  }
}