【2025 Rust学习 --- 15 迭代器的消耗】

消耗迭代器

使用带有 for 循环的迭代器,也可以显式调用 next,但有许多常见任务不必一遍又一遍地写出来。Iterator 特型提供了一大组可选方法来涵盖其中的许多任务。

简单累加:count、sum 和 product

count(计数)方法会从迭代器中提取条目,直到迭代器返回 None,并报告提 取的条目数。

rust 复制代码
use std::io::prelude::*;
fn main() {
 let stdin = std::io::stdin();
 println!("{}", stdin.lock().lines().count());
}

sum(求和)方法和 product(乘积)方法会分别计算迭代器条目之和与乘积,结果必须是整数或浮点数。

rust 复制代码
fn triangle(n: u64) -> u64 {
 (1..=n).sum()
}
assert_eq!(triangle(20), 210);
fn factorial(n: u64) -> u64 {
 (1..=n).product()
}
assert_eq!(factorial(20), 2432902008176640000);

min 与 max

Iterator 上的 min(最小)方法和 max(最大)方法会分别返回迭代器生成 的最小的一个条目与最大的一个条目。迭代器的条目类型必须实现 std::cmp::Ord,这样条 目之间才能相互比较:

rust 复制代码
assert_eq!([-2, 0, 1, 0, -2, -5].iter().max(), Some(&1));
assert_eq!([-2, 0, 1, 0, -2, -5].iter().min(), Some(&-5));

这些方法会返回一个 Option <Self::Item>以便当迭代器不再生成任何条目时能返回 None。

Rust 的浮点类型 f32 和 f64 仅实现了 std::cmp::PartialOrd 而没有实现 std::cmp::Ord,因此不能使用 min 方法和 max 方法来计算浮点数序列中的最小值和最大值。这在 Rust 设计中并不讨喜,却是经过深思熟虑的------因为不清楚这些函数该如何处理 IEEE 的 NaN 值,如果只是简单地忽略则可能会掩盖代码中更严重的问题。

如果知道如何处理 NaN 值,则可以改用 max_by 和 min_by 迭代器方法,可以提供自己的比较函数了。

max_by 与 min_by

max_by(据......最大)方法和 min_by(据......最小)方法会分别返回迭代器生 成的最大条目与最小条目,由你提供的比较函数确定规则:

rust 复制代码
use std::cmp::Ordering;
// 比较两个f64值,如果其一是NaN,则引发panic
fn cmp(lhs: &f64, rhs: &f64) -> Ordering {
 lhs.partial_cmp(rhs).unwrap()
}
let numbers = [1.0, 4.0, 2.0];
assert_eq!(numbers.iter().copied().max_by(cmp), Some(4.0));
assert_eq!(numbers.iter().copied().min_by(cmp), Some(1.0));
let numbers = [1.0, 4.0, std::f64::NAN, 2.0];
assert_eq!(numbers.iter().copied().max_by(cmp), Some(4.0)); // 引发panic

max_by 方法和 min_by 方法会通过引用将条目传给比较函数,这样一来,这两 个方法就可以与任意种类的迭代器一起高效配合使用。在上面的代码中,虽然我 们已经用 copied 获取了会生成 f64 条目的迭代器,但 cmp 函数还是期望通过 引用获取其参数。

max_by_key 与 min_by_key

使用 Iterator 上的 max_by_key(据键最大)方法和 min_by_key(据键最 小)方法可以选择最大条目或最小条目,由针对每个条目调用的闭包确定。闭包 可以选择条目的某些字段或对此条目执行某些计算。由于你通常只对与某些最小 值或最大值相关的数据感兴趣,而不仅仅是极值本身,因此这两个函数通常比 max 和 min 更有用。它们的签名如下所示:

rust 复制代码
fn min_by_key<B: Ord, F>(self, f: F) -> Option<Self::Item>
 where Self: Sized, F: FnMut(&Self::Item) -> B;
fn max_by_key<B: Ord, F>(self, f: F) -> Option<Self::Item>
 where Self: Sized, F: FnMut(&Self::Item) -> B;

给定一个接受某条目并返回任意有序类型 B 的闭包,则返回那个调 用闭包时所返回的 B 为最大或最小的条目;如果没有生成任何条目,则返回 None。

查找人口最多和最少的城市:

rust 复制代码
use std::collections::HashMap;
let mut populations = HashMap::new();
populations.insert("Portland", 583_776);
populations.insert("Fossil", 449);
populations.insert("Greenhorn", 2);
populations.insert("Boring", 7_762);
populations.insert("The Dalles", 15_340);
assert_eq!(populations.iter().max_by_key(|&(_name, pop)| pop),
 Some((&"Portland", &583_776)));
assert_eq!(populations.iter().min_by_key(|&(_name, pop)| pop),
 Some((&"Greenhorn", &2)));

对条目序列进行比较

如果字符串、向量和切片的各个元素是可比较的,我们就可以使用<运算符和 == 运算符来对它们进行比较。但是,比较运算符不能用来比较迭代器,这项工 作是由像 eqlt 这样的方法来完成的。相关方法从迭代器中成对取出条目并比较,直到得出结果:

rust 复制代码
let packed = "Helen of Troy";
let spaced = "Helen of Troy";
let obscure = "Helen of Sandusky"; 
assert!(packed != spaced);
assert!(packed.split_whitespace().eq(spaced.split_whitespace()));
// 此断言为真,因为' ' < 'o'
assert!(spaced < obscure);
// 此断言为真,因为'Troy' > 'Sandusky'
assert!(spaced.split_whitespace().gt(obscure.split_whitespace()));

调用 split_whitespace 会返回字符串中以空白字符分隔的单词的迭代器。 在这些迭代器上使用 eq 方法和 gt 方法会进行逐词比较(而不是逐字符比 较),因为 &str 实现了 PartialOrd 和 PartialEq。

提供:用于相等比较的 eq 方法和 ne 方法,也包括用于 有序比较的 lt 方法、le 方法、gt 方法和 ge 方法。而 cmp 方法和 partial_cmp 方法的行为类似于 Ord 特型和 PartialOrd 特型的相应方法

any 与 all

any(任意)方法和 all(所有)方法会将闭包应用于迭代器生成的每个条目。 如果闭包对任意条目返回了 true 或对所有条目都返回了 true,则相应的方法 返回 true:

这两个方法只会消耗确定答案所需的尽可能少的条目。如果闭包已经为给定条目 返回了 true,则 any 会立即返回 true,不会再从迭代器中提取更多条目。

定位position、rposition 和 ExactSizeIterator

position(位置)方法会针对迭代器中的每个条目调用闭包,并返回调用结果为 true 的第一个条目的索引。确切而言,它会返回关于此索引的 Option:如果闭包没有为任何条目返回 true,则 position 返回 None。一旦闭包返回 true,position 就会停止提取条目:

rust 复制代码
let text = "Xerxes";
assert_eq!(text.chars().position(|c| c == 'e'), Some(1));
assert_eq!(text.chars().position(|c| c == 'z'), None);

rposition(右起位置)方法也是一样的,只是从右侧开始搜索

rposition 方法要求使用可逆迭代器,以便它能从此序列的右端提取条目。另外,它也要求这是确切大小迭代器,以便能像 position 一样对索引进行赋值,最左边的索引值为 0。所谓确切大小迭代器就是实现了 std::iter::ExactSizeIterator 特型的迭代器:

rust 复制代码
trait ExactSizeIterator: Iterator {
 fn len(&self) -> usize { ... }
 fn is_empty(&self) -> bool { ... }
}

len 方法会返回剩余的条目数,而 is_empty 方法会在迭代完成时返回 true。

也不是每个迭代器都能预知要生成的条目数,之前使用的 str::chars 迭代器就不能(UTF-8 是可变宽度编码),因此不能在字符串上 使用 rposition。但是字节数组biyes[]上的迭代器必定知道数组的长度,因此它可以实现 ExactSizeIterator。

折叠fold 与 rfold

fold(折叠)方法用于在迭代器生成的整个条目序列上累积某种结果。

给定一个初始值(我们称之为累加器)和一个闭包,fold 会以当前累加器和迭代器中的下一个条目为参数反复调用这个闭包。闭包返回的值被视为新的累加器,并将其与下一个条目一起传给闭包。最终,累加器的值就是 fold 本身返回的值。

如果序列为空,则 fold 只返回初始累加器。 使用迭代器值的许多其他方法可以改写成对 fold 的使用:

rust 复制代码
fn fold<A, F>(self, init: A, f: F) -> A
 where Self: Sized, F: FnMut(A, Self::Item) -> A;

A 是累加器的类型。闭包的第一个参数 init 及其返回值都是类型 A, fold 自身的返回值也是类型 A。

rust 复制代码
let a = [5, 6, 7, 8, 9, 10];
assert_eq!(a.iter().fold(0, |n, _| n+1), 6); // 计数
assert_eq!(a.iter().fold(0, |n, i| n+i), 45); // 求和
assert_eq!(a.iter().fold(1, |n, i| n*i), 151200); // 乘积
// 最大值
assert_eq!(a.iter().cloned().fold(i32::min_value(), std::cmp::max),
 10);

累加器的值会移动进闭包或者从闭包中移动出来,因此你可以将 fold 与各种非 Copy 的累加器类型一起使用:

rust 复制代码
let a = ["Pack", "my", "box", "with",
 "five", "dozen", "liquor", "jugs"];
// 另见切片的`join`方法,它不会像这里一样帮你在末尾添加额外的空格
let pangram = a.iter()
 .fold(String::new(), |s, w| s + w + " ");
assert_eq!(pangram, "Pack my box with five dozen liquor jugs ");

rfold(右起折叠)方法与 fold 方法基本相同,但需要一个双端迭代器,并从后往前处理各个条目

try_fold 与 try_rfold

try_fold(尝试折叠)方法与 fold 方法基本相同,但迭代可以提前退出, 无须消耗迭代器中的所有值。传给 try_fold 的闭包返回的值会指出它是应该立即返回,还是继续折叠迭代器的条目。

闭包可以返回多种类型的值,根据类型值,try_fold 方法可判断继续折叠的方式。

  • 如果闭包返回 Result,可能是因为它执行了 I/O 或其他一些容易出错的操作,那就返回 Ok(v) 令 try_fold 继续折叠,同时将 v 作为新的累加器值。
  • 如果返回 Err(e),则 try_fold 立即停止折叠。折叠后的最终值是一个带有最终累加器值的 Result,或者由闭包返回的错误值。

如果闭包返回 Option:

  • Some(v) 表示折叠应该以 v 作为新的累加 器值继续前进
  • None 表示迭代应该立即停止。折叠后的最终值也是 Option 类型的。

最后,闭包还可以返回一个 std::ops::ControlFlow 值。这种类型是 一个具有两个变体的枚举,即 Continue(c) 和 Break(b),分别表示使用新的累加器值 c 继续或提前中止迭代。折叠的最终结果是一个 ControlFlow 值:如果折叠消耗了整个迭代器,并生成了最终的累加器值 v,则为 Continue(v);如果闭包中途返回了值 b,则为 Break(b)。 Continue© 和 Break(b) 的行为与 Ok© 和 Err(b) 完全一样。使用 ControlFlow 而不用 Result 的优点在于,有时候提前退出并不表示出错了, 而只是表明提前得出了答案,这种情况下它可读性更好。

一段对从标准输入读取的数值进行求和的程序:

rust 复制代码
use std::error::Error;
use std::io::prelude::*;
use std::str::FromStr;
fn main() -> Result<(), Box<dyn Error>> {
 let stdin = std::io::stdin();
 let sum = stdin.lock().lines().try_fold(0, |sum, line| -> Result<u64, Box<dyn Error>> {
 Ok(sum + u64::from_str(&line?.trim())?)
 })?;
 println!("{}", sum);
 Ok(())
}

缓冲输入流上的 lines 迭代器会生成 Result 类型的条目,并且将 String 解析为整数时也可能会出错。在这里闭包返回 Result,所以我们可以使用 ? 运算符 将本次失败从闭包传播到 main 函数。

try_fold 非常灵活,被用来实现 Iterator 的许多其他消费者方法,比如 all 的一种实现:

rust 复制代码
fn all<P>(&mut self, mut predicate: P) -> bool
 where P: FnMut(Self::Item) -> bool,
 Self: Sized
{
 use std::ops::ControlFlow::*;
 self.try_fold((), |_, item| {
 if predicate(item) { Continue(()) } else { Break(()) }
 }) == Continue(())
}

这里无法用普通的 fold 来写,all 的语义承诺了一旦 predicate 返 回 false 就停止从底层迭代器中消耗条目,但 fold 总是会消耗掉整个迭代器。

try_rfold(尝试右起折叠)方法与 try_fold 方法相同,只是从 后面而不是前面开始提取值,并且要求传入一个双端迭代器。

第N+1个 nth 与 nth_back

rust 复制代码
fn nth(&mut self, n: usize) -> Option<Self::Item>
 where Self: Sized;

nth(第 n 个)方法会接受索引参数 n,从迭代器中跳过 n 个条目,并返回下一 个条目,如果序列提前结束了,则返回 None。

调用 .nth(0) 等效于 .next()。

nth 不会像适配器那样接手迭代器的所有权,因此可以多次调用:

rust 复制代码
let mut squares = (0..10).map(|i| i*i);
assert_eq!(squares.nth(4), Some(16));
assert_eq!(squares.nth(0), Some(25));
assert_eq!(squares.nth(6), None);

nth_back(倒数第 n 个)方法与 nth 方法很像,只是从双端迭代器的后面往 前提取。调用 .nth_back(0) 等效于 .next_back():返回最后一个条目; 如果迭代器为空则返回 None。

last

rust 复制代码
fn last(self) -> Option<Self::Item>;

last(最后一个)方法会返回迭代器生成的最后一个条目,如果为空则返回 None。

会从前面开始消耗所有迭代器的条目,即便此迭代器是可逆的也会如此。如果你有一个可逆迭代器并且不想消耗它的所有条目,应该只写 iter.next_back()。

查找find、rfind 和 find_map

find(查找)方法会从迭代器中提取条目,返回第一个由给定闭包回复 true 的条目,如果序列在找到合适的条目之前就结束了则返回 None

rust 复制代码
fn find<P>(&mut self, predicate: P) -> Option<Self::Item>
 where Self: Sized, P: FnMut(&Self::Item) -> bool;

rfind(右起查找)方法与此类似,但要求迭代器为双端迭代器并从后往前搜索 值,返回最后一个给定闭包回复为 true 的条目。

有时候,闭包不仅仅是一个简单的谓词------对每个条目进行逻辑判断并继续向前,它还可能是更复杂的东西,比如生成一个有意义的值。在这种情况下就需要 使用 find_map(查找并映射)

rust 复制代码
fn find_map<B, F>(&mut self, f: F) -> Option<B> 
where F: FnMut(Self::Item) -> Option<B>;

find_map 和 find 很像,但其闭包不会返回 bool,而是返回某个值的 Option。find_map 会返回第一个类型为 Some 的 Option。

构建集合:collect 与 FromIterator

collect 并不是向量专用的,事实上,它可以构建出 Rust 标准库中任意类 型的集合,只要迭代器能生成合适的条目类型即可:

rust 复制代码
use std::collections::{HashSet, BTreeSet, LinkedList, HashMap, BTreeMap};
let args: HashSet<String> = std::env::args().collect();
let args: BTreeSet<String> = std::env::args().collect();
let args: LinkedList<String> = std::env::args().collect();
// 只有键--值对才能收集到Map中,因此对于这个例子,
// 要把字符串序列和整数序列拉合在一起
let args: HashMap<String, usize> = std::env::args().zip(0..).collect();
let args: BTreeMap<String, usize> = std::env::args().zip(0..).collect();

某些集合 类型(如 Vec 或 HashMap)知道如何从迭代器构造自身,就会自行实现 std::iter::FromIterator(来自迭代器)特型,而 collect 只是一个便 捷的浅层包装而已:

rust 复制代码
trait FromIterator<A>: Sized {
 fn from_iter<T: IntoIterator<Item=A>>(iter: T) -> Self;
}

一个集合类型实现了 FromIterator,那么它的类型关联函数 from_iter 就能从一个可迭代者生成的 A 类型条目中构建出一个该类型的值。

在最简单的情况下,实现代码可以构造出一个空集合,然后将迭代器中的条目一 个个添加进去。例如,std::collections::LinkedList 的 FromIterator 就是这样实现的。

从某个迭代器 iter 构造 出一个向量可以非常简单:

rust 复制代码
let mut vec = Vec::new();
for item in iter {
 vec.push(item)
}
vec
rust 复制代码
trait Iterator {
 ...
 fn size_hint(&self) -> (usize, Option<usize>) {
 (0, None)
 }
}

size_hint 方法会返回迭代器要生成的条目数的下限与可选上限,默认返回 0 作为下限而没有指定上限,这实际上就是在说"我也不知道上限"。许多迭代器可 以做得更好,比如,Range 上的迭代器肯定知道它将生成多少个条目,Vec 或 HashMap 上的迭代器也知道。这类迭代器为 size_hint 提供了自己的专有定 义。

Vec 的 FromIterator 实现在一开始就正确设置新向量的缓冲区大小。但在插入条目时仍然会检查缓冲区是否足 够大,因此即使这种提示不正确,也只会影响性能,而不会影响安全。其他类型 也可以采取类似的步骤,比如,HashSet 和 HashMap 就会使用Iterator::size_hint来为其哈希表选择合适的初始大小。

关于Rust自动的类型推断的一点说明:一开始同样是调用 std::env::args(). collect(),却根据上下文生成了 4 种类型的集合, 这可能有点儿奇怪。collect 的返回类型就是它的类型参数,所以前两个调用 等价于以下代码:

rust 复制代码
let args = std::env::args().collect::<Vec<String>>();
let args = std::env::args().collect::<HashSet<String>>();

Extend 拓展特型

rust 复制代码
trait Extend<A> {
 fn extend<T>(&mut self, iter: T)
 where T: IntoIterator<Item=A>;
}

一个类型实现了 std::iter::Extend(扩展)特型,那么它的 extend 方法就能将一些可迭代的条目添加到集合中:

rust 复制代码
let mut v: Vec<i32> = (0..5).map(|i| 1 << i).collect();
v.extend([31, 57, 99, 163]);
assert_eq!(v, [1, 2, 4, 8, 16, 31, 57, 99, 163]);

所有的标准集合都实现了 Extend,因此它们都有 extend 方法,String 也实 现了,但具有固定长度的数组和切片则未实现。

std::iter::FromIterator 非常相似:后者会创建新集合,而 Extend 会扩展现有集合。事实上,标准库中 FromIterator 的好几个实现都只是简单地创建一个新的空集合,然后调用 extend 填充它,比如 std::collections::LinkedList 的 FromIterator 就是这样实现的

rust 复制代码
impl<T> FromIterator<T> for LinkedList<T> {
 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
     let mut list = Self::new();
     list.extend(iter);
     list
 }
}

二分区partition

rust 复制代码
fn partition<B, F>(self, f: F) -> (B, B)
 where Self: Sized,
 B: Default + Extend<Self::Item>,
 F: FnMut(&Self::Item) -> bool;

partition(分区)方法会将迭代器的条目划分到两个集合中,并使用闭包来 决定每个条目归属的位置

rust 复制代码
let things = ["doorknob", "mushroom", "noodle", "giraffe", "grapefruit"];
let (living, nonliving): (Vec<&str>, Vec<&str>)
 = things.iter().partition(|name| name.as_bytes()[0] & 1 != 0);
assert_eq!(living, vec!["mushroom", "giraffe", "grapefruit"]);
assert_eq!(nonliving, vec!["doorknob", "noodle"]);

与 collect 一样,partition 也可以创建你喜欢的任意种类的集合,但这些 集合必须属于同一个类型。和 collect 一样,你需要指定返回类型:前面的示例明确写出了 living 和 nonliving 的类型,并让类型推断为调用 partition 选择正确的类型参数

collect 要求其结果类型实现了 FromIterator,而 partition 则要求其结果类型实现了 std::default::Default,因为所有 Rust 集合都实现了 std::default::Default,而 std::default::Extend 则用于将元素添加到集合中。

其他语言提供的 partition 操作往往只是将迭代器拆分为两个迭代器,而不会构建两个集合------这种处理方法之所以在 Rust 中并非好的选择,是因为那些已 从底层迭代器中提取但尚未从已分区迭代器中提取的条目需要在某处进行缓冲, 毕竟如果无论如何都要在内部构建某种集合,那还不如直接返回这些集合本身。

for_each 与 try_for_each

for_each(对每一个)方法会简单地对每个条目调用某个闭包,与简单的 for 循环非常相似,你同样可以在其中使用像 break 和 continue 这样的控制结构

如果你的闭包需要容错或提前退出,可以使用 try_for_each(尝试对每一 个)。

自定义迭代器

为自己的类型实现 IntoIterator 特型和 Iterator 特型

rust 复制代码
struct I32Range {
 start: i32,
 end: i32
}

要想迭代 I32Range 就需要两个状态:当前值和迭代应该结束的界限。这恰好 非常适合 I32Range 类型本身,使用 start 作为下一个值,并使用 end 作为 界限。因此,你可以像这样实现 Iterator:

rust 复制代码
impl Iterator for I32Range {
 type Item = i32;
 fn next(&mut self) -> Option<i32> {
     if self.start >= self.end {
     	return None;
 	 }
     let result = Some(self.start);
     self.start += 1;
     result
 }
}

for 循环会使用 IntoIterator::into_iter 将其操作数转换为迭代 器。但是标准库为每个实现了 Iterator 的类型都提供了 IntoIterator 的 通用实现,所以 I32Range 可以直接使用

rust 复制代码
let mut pi = 0.0;
let mut numerator = 1.0;
for k in (I32Range { start: 0, end: 14 }) {
     pi += numerator / (2*k + 1) as f64;
     numerator /= -3.0;
}
pi *= f64::sqrt(12.0);
// IEEE 754精确定义了此结果
assert_eq!(pi as f32, std::f32::consts::PI);

I32Range 是一个特例,因为其迭代目标和迭代器的类型是一样的。但还有 许多情况没这么简单

rust 复制代码
enum BinaryTree<T> {
     Empty,
     NonEmpty(Box<TreeNode<T>>)
}
struct TreeNode<T> {
     element: T,
     left: BinaryTree<T>,
     right: BinaryTree<T>
}

遍历二叉树的经典方式是递归,使用函数调用栈来跟踪你当前在树中的位置以及尚未访问的节点。但是当为 BinaryTree 实现 Iterator 时,对 next 的 每次调用都必须生成一个值并返回。为了跟踪尚未生成的树节点,迭代器必须维护自己的栈。下面是 BinaryTree 的一种可能的迭代器类型:

rust 复制代码
use self::BinaryTree::*;
// `BinaryTree`的有序遍历状态
struct TreeIter<'a, T> {
 // 跟踪树节点引用的栈。由于我们使用了`Vec`的`push`方法
 // 和`pop`方法,因此栈顶就是向量的末尾
 //
 // 迭代器接下来要访问的节点位于栈顶,栈顶之下的那些祖先
 // 仍未访问。如果栈已空,则本迭代结束
 unvisited: Vec<&'a TreeNode<T>>
}

创建了一个新的 TreeIter 时,它的初始状态应该是即将生成的树中最左侧的 叶节点。根据 unvisited 栈的规则,它应该让那个叶节点位于栈顶,然后是栈顶节点未访问的祖先:位于树的左边缘的节点。我们可以通过从根到叶遍历树的 左边缘并推入我们遇到的每个节点来初始化 unvisited(未访问的栈),因此 我们将在 TreeIter 上定义一个方法来执行此操作:

rust 复制代码
impl<'a, T: 'a> TreeIter<'a, T> {
 fn push_left_edge(&mut self, mut tree: &'a BinaryTree<T>) {
     while let NonEmpty(ref node) = *tree {
         self.unvisited.push(node);
         tree = &node.left;
     }
 }
}

写成 mut tree 能让代码中的循环在沿着左边缘向下移动时更改 tree 指向的 节点,但是由于 tree 是共享引用,它不能改变节点本身

有了这个辅助方法,就可以给 BinaryTree 提供一个 iter 方法,让它返回遍历树的迭代器了:

rust 复制代码
impl<T> BinaryTree<T> {
 fn iter(&self) -> TreeIter<T> {
     let mut iter = TreeIter { unvisited: Vec::new() };
     iter.push_left_edge(self);
     iter
 }
}

iter 方法会构造一个带有空 unvisited 栈的 TreeIter,然后调用 push_left_edge 进行初始化。根据 unvisited 栈规则的要求,最左边的节点位于栈顶。 遵循标准库的做法,也可以通过调用 BinaryTree::iter 在对树的共享引用 上实现 IntoIterator:

rust 复制代码
impl<'a, T: 'a> IntoIterator for &'a BinaryTree<T> {
 type Item = &'a T;
 type IntoIter = TreeIter<'a, T>;
 fn into_iter(self) -> Self::IntoIter {
 ``self.iter()
 }
}

这个 IntoIter 定义将 TreeIter 定义为针对&BinaryTree的迭代器类型

最后,在 Iterator 实现中,我们开始实际遍历树。与 BinaryTree 的 iter 方法一样,迭代器的 next 方法也遵循栈规则:

rust 复制代码
impl<'a, T> Iterator for TreeIter<'a, T> {
 type Item = &'a T;
 fn next(&mut self) -> Option<&'a T> {
     // 找到此迭代必须生成的节点,或完成迭代(如果为`None`,
     // 请使用`?`运算符立即返回)
     let node = self.unvisited.pop()?;
     // 在`node`之后,我们接下来生成的条目必须是`node`右子树中最左边
     // 的子节点,所以要从这里向下推进。这个辅助方法恰好是我们想要的
     self.push_left_edge(&node.right);
     // 生成一个对本节点值的引用
     Some(&node.element)
 }
}

如果栈为空,则迭代完成。否则,node 就是对当前要访问的节点的引用,此调用将返回一个指向其 element 字段的引用。但首先,我们必须将迭代器的状态 推进到下一个节点。如果这个节点具有右子树,则下一个要访问的节点是该子树 最左侧的节点,我们可以使用 push_left_edge 将它及其未访问的祖先压入 栈。但是如果这个节点没有右子树,则 push_left_edge 没有任何效果,这正 是我们想要的:我们希望新的栈顶是 node 的第一个未访问的祖先(如果有的 话)。

有了 IntoIterator 和 Iterator 这两个实现,我们终于可以使用 for 循环 来通过引用迭代 BinaryTree 了。使用 BinaryTree 的 add 方法:

rust 复制代码
// 构建一棵小树
let mut tree = BinaryTree::Empty;
tree.add("jaeger");
tree.add("robot");
tree.add("droid");
tree.add("mecha");
// 对它进行遍历
let mut v = Vec::new();
for kind in &tree {
  v.push(*kind);
}
assert_eq!(v, ["droid", "jaeger", "mecha", "robot"]);

所有常用的迭代器适配器和消费者都能用在我们这棵树上:

rust 复制代码
assert_eq!(tree.iter()
 .map(|name| format!("mega-{}", name))
 .collect::<Vec<_>>(), vec!["mega-droid", "mega-jaeger","mega-mecha", "mega-robot"]);
相关推荐
Panda-gallery1 小时前
【Rust】错误处理机制
开发语言·后端·rust
Panda-gallery1 小时前
【Rust】结构体的方法语法
开发语言·后端·rust
从善若水1 小时前
【Rust学习笔记】Rust 的所有权介绍
笔记·学习·rust
fnd_LN2 小时前
HTML学习笔记记录---速预CSS(1) 选择器类型
css·笔记·学习·html
viperrrrrrrrrr73 小时前
大数据学习(33)-spark-transformation算子
大数据·hive·学习·spark·mapreduce
Panda-gallery3 小时前
【Rust】常见集合
开发语言·后端·rust
陈序缘3 小时前
Rust实现智能助手 - 项目初始化
开发语言·后端·语言模型·rust
timer_0173 小时前
Rust 1.84.0 发布
开发语言·后端·rust
私人珍藏库4 小时前
《废品机械师抢先版》V0.7.3.b776官方中文学习版
学习