Skip to main content

2 posts tagged with "迭代器"

View All Tags

Rust 提供了强大的类型系统,但在可迭代特性上仍有一些可改进之处。

本文将讨论 CollectionIterable 特性的定义与实现,以及它们在提升代码通用性与可复用性上的作用。


背景

在 Rust 的核心库中,我们已经有了 IteratorIntoIterator 特性。 然而,对于可以多次迭代的集合类型(Collection)或通用的可迭代类型(Iterable),目前还缺乏统一的特性支持。 我们将深入探讨这些特性的重要性和实现方式。


核心概念

Iterator

Iterator 特性用于逐步遍历集合中的元素或计算结果。它是一种懒加载模型,仅在调用消费方法时才执行计算。

常见的 Iterator 方法包括:

  • 迭代器到迭代器的转换filtermapflat_map
  • 迭代器的消费方法collectreducefold
let numbers = vec![1, 2, 3];
let doubled: Vec<_> = numbers.iter().map(|x| x * 2).collect();
println!("{:?}", doubled); // [2, 4, 6]

IntoIterator

IntoIterator 特性允许将类型转换为迭代器:

pub trait IntoIterator {
type Item;
type IntoIter: Iterator<Item = Self::Item>;

fn into_iter(self) -> Self::IntoIter;
}

它支持三种实现方式:

  1. 消费自身:x.into_iter()
  2. 消费不可变引用:(&x).into_iter()
  3. 消费可变引用:(&mut x).into_iter()

定义 Collection 和 Iterable 特性

Collection 和 CollectionMut 特性

Collection 表示可以反复生成共享引用迭代器的集合,CollectionMut 进一步扩展为支持生成可变引用迭代器。

trait Collection {
type Item;
type Iter<'i>: Iterator<Item = &'i Self::Item>
where
Self: 'i;

fn iter(&self) -> Self::Iter<'_>;
}

trait CollectionMut: Collection {
type IterMut<'i>: Iterator<Item = &'i mut Self::Item>
where
Self: 'i;

fn iter_mut(&mut self) -> Self::IterMut<'_>;
}

通过 IntoIterator 的实现,可以简洁地为所有符合条件的集合实现这些特性:

impl<X> Collection for X
where
X: IntoIterator,
for<'a> &'a X: IntoIterator<Item = &'a <X as IntoIterator>::Item>,
{
type Item = <X as IntoIterator>::Item;
type Iter<'i> = <&'i X as IntoIterator>::IntoIter;

fn iter(&self) -> Self::Iter<'_> {
<&X as IntoIterator>::into_iter(self)
}
}

impl<X> CollectionMut for X
where
X: IntoIterator,
for<'a> &'a mut X: IntoIterator<Item = &'a mut <X as IntoIterator>::Item>,
{
type IterMut<'i> = <&'i mut X as IntoIterator>::IntoIter;

fn iter_mut(&mut self) -> Self::IterMut<'_> {
<&mut X as IntoIterator>::into_iter(self)
}
}

Iterable 特性

Iterable 是对更广泛可迭代类型的定义,支持生成值迭代器,而不是引用迭代器。

trait Iterable {
type Item;
type Iter: Iterator<Item = Self::Item>;

fn iter(&self) -> Self::Iter;
}

实现方式如下:

impl<'a, X> Iterable for &'a X
where
&'a X: IntoIterator,
{
type Item = <&'a X as IntoIterator>::Item;
type Iter = <&'a X as IntoIterator>::IntoIter;

fn iter(&self) -> Self::Iter {
self.into_iter()
}
}

示例

Collection 示例

以下示例展示了如何使用 Collection 计算集合的统计信息:

fn statistics(numbers: &impl Collection<Item = i64>) -> Stats {
let count = numbers.iter().count() as i64;
let mean = numbers.iter().sum::<i64>() / count;
let sum_sq_errors: i64 = numbers.iter().map(|x| (x - mean) * (x - mean)).sum();
let std_dev = f64::sqrt(sum_sq_errors as f64 / (count - 1) as f64) as i64;

Stats { count: count as usize, mean, std_dev }
}

支持的集合类型包括:

  • 数组和向量
  • 标准库集合(如 HashSetVecDeque
  • 第三方集合(如 SmallVecArrayVec

CollectionMut 示例

以下示例展示了如何使用 CollectionMut 修改集合中的元素:

fn increment_by_sum(numbers: &mut impl CollectionMut<Item = i32>) {
let sum: i32 = numbers.iter().sum();
for x in numbers.iter_mut() {
*x += sum;
}
}

Iterable 示例

以下示例展示了使用 Iterable 的灵活性,包括对范围和自定义生成器的支持:

fn statistics(numbers: impl Iterable<Item = i64>) -> Stats {
/* 与 Collection 示例相同 */
}

statistics(7..21); // 支持范围
statistics(FibUntil(10)); // 自定义生成器

优势与结论

优势

  • 自动实现:无需额外配置,集合类型可以自动实现 CollectionCollectionMut
  • 广泛适用:支持标准集合、自定义集合和生成器类型。
  • 灵活性:通过 Iterable 扩展了迭代器的应用范围。

总结

通过引入 CollectionIterable 特性,我们显著提升了代码的复用性和通用性。 Rust 的类型系统为实现这些特性提供了极大的便利,再次展现了其强大的能力 ❤️🦀。

链接

鱼雪

在Rust中,迭代器是一种尤为重要的数据类型, 被用于遍历集合中的元素。 Rust中的大多数集合类型都可以转换成迭代器, 从而可以对它们进行遍历。 包括:数组(Array)、向量(Vec)、散列表(HashMap)

使用迭代器可以使代码更加简洁、优雅,并且支持 过滤映射折叠 等强大的操作

迭代器的基本概念

在Rust中,迭代器是一种实现了Iterator trait的类型。 该trait定义了在集合中遍历元素的一系列行为。 通过实现Iterator trait,可以将一个类型转换为一个迭代器, 从而实现迭代器等操作。

Iterator Trait

Iterator trait定义了迭代器的核心行为。 它包括next方法和其它几个方法。 next方法为集合中的下一个元素返回一个**Option值**, 直到所有元素都遍历完毕,此时返回None

除了next方法,Iterator trait还定义了许多其他有用的方法, 例如mapfilter等,允许对迭代器元素进行操作和转换

pub trait Iterator {
type Item;

fn next(&mut self) -> Option<Self::Item>;
}

Animal案例

让我们探索Animal结构体的迭代器的实现。 Animal类型实现了Iterator trait, 从而可以对其属性进行迭代。 以下是Animal类型的定义:

#[derive(Debug)]
struct Animal {
name: String,
age: u32,
kind: String,
i: i32,
}

我们为Animal实现Iterator trait,以使用for循环迭代其属性:

impl Iterator for Animal {
type Item = String;

fn next(&mut self) -> Option<Self:Item> {
let next_attribute = match self.i {
0 => Some(self.name.clone()),
1 => Some(self.age.to_string()),
2 => Some(self.kind.clone()),
_ => None,
};
self.i += 1;
next_attribute
}
}

现在我们已经将类型转换为迭代器,
我们可以从迭代器中调用各种方法。
例如,可以使用`for`循环来迭代`Animal`的每个属性:

```rust
fn main() {
let mut animal = Animal {
name: "Tome".to_string(),
age: 15,
kind: "cat".to_string(),
i: 0,
}
}

在上述代码中,我们定义了一个Animal的迭代器, 包括一个名为i的内部状态变量。 代码成功打印了Animal的所有信息。

让我们通过定义Animal向量并迭代打印每个Animal的属性来继续我们的探索:

fn print_all_attrs(animals: Vec<Animal>) {
for mut animal in animals {
println!("Name: {}", animal.next().unwrap());
println!("Age : {}", animal.next().unwrap());
println!("Kind: {}", animal.next().unwrap());
}
}

fn main() {
let animals = vec![Animal {
name: "Tom".to_string(),
age: 15,
kind: "cat".to_string(),
i: 0,
}];
print_all_attrs(animals);
}

在这段代码中,我们使用for循环来迭代所有Animal对象并逐一打印它们的属性。

迭代器的常见用途

map方法

map方法是Iterator trait中的一个关键方法。 它允许我们转换迭代器中的每个元素并返回一个新的迭代器。 例如:

fn main() {
let animals = vec![
Animal {
name: "Tom".to_string(),
age: 14,
kind: "cat".to_string(),
i: 0,
},
Animal {
name: "Jerry".to_string(),
age: 7,
kind: "mouse".to_string(),
i: 1,
},
];

let list: Vec<String> = animals
.into_iter()
.map(|ani| ani.name.clone())
.collect();
println!("{:?}", list);
}

在上面的代码中,我们定义了一个带有两个Animal对象的向量, 并使用map方法从每个Animal中提取name属性, 返回一个新的迭代器。 然后,collect()方法将其转换为向量。

filter方法

假设我们想找到三岁或以上的Animal。 我们可以使用过滤器来实现这一点:

fn main() {
let animals = vec![
Animal {
name: "Tom".to_string(),
age: 33,
kind: "cat".to_string(),
i: 3,
},
];

let filtered_animals: Vec<Animal> = animals
.into_iter()
.filter(|animal| animal.age >= 3)
.collect();
println!("{:?}", filtered_animals);
}

在上面的代码中,我们使用filter方法选择年龄为三岁或以上的Animal, 返回一个新的Animal向量。

enumerate方法

enumerate方法将迭代器中的每个元素与其索引配对, 返回一个新的迭代器。例如:

fn main() {
let animals = vec![
Animal {
name: "Tom".to_string(),
age: 33,
kind: "cat".to_string(),
i: 3,
},
Animal {
name: "Jay".to_string(),
age: 22,
kind: "mouse".to_string(),
i: 4,
},
];

for (i, animal) in animals.iter().enumerate() {
println!("{}: {:?}", i, animal);
}
}

在上面代码中,我们使用enumerate方法将每个Animal与其索引配对, 并使用for循环打印结果。

flat_map方法

flat_map方法不太常见但很有用。 它展平嵌套迭代器到单个迭代器中。例如:

fn main() {
let cat = Animal {
name: "Tom".to_string(),
age: 21,
kind: "cat".to_string(),
i: 3,
};

let mouse = Animal {
name: "Jerry".to_string(),
age: 3,
kind: "mouse".to_string(),
i: 2,
};

let animals = vec![vec![cat], vec![mouse]];

let list: Vec<Animal> = animals
.iter()
.flat_map(|x| x.iter().cloned())
.collect();
println!("{:?}", list);
}

在上面代码中,我么定义了一个2D向量Animals,并使用flat_map方法 将其展平为1D迭代器,将其转换回向量。

zip方法

要同时迭代两个向量,我们可以使用zip方法进行配对:

fn main() {
let names = vec!["Tom", "Jerry", "Bob"];
let ages = vec![3, 4, 5];

for (name, age) in names.iter().zip(ages.iter()) {
println!("{} is {} years old", name, age);
}
}

在上面代码中,我们使用zip方法将姓名和年龄向量中的元素配对, 并在for循环中打印每对。

fold方法

fold方法在Rust中时必不可少的; 它接受一个初始值和一个闭包, 迭代元素, 并将它们合并为一个值。例如:

fn main() {
let cat = Animal {
name: "Tom".to_string(),
age: 13,
kind: "cat".to_string(),
i: 0,
};
let mouse = Animal {
name: "Jerry".to_string(),
age: 7,
kind: "mouse".to_string(),
i: 1,
};

let animals = vec![cat, mouse];

let sum = animals
.iter()
.fold(0, |t, ani| t + ani.age);
println!("{}", sum);
}

在上面代码中,我们定义了一个带有两个Animal对象的向量, 并使用fold方法累加年龄属性, 返回结果总和。

总结

在Rust中,迭代器是遍历集合元素和支持各种操作的重要数据类型。 在本文中,我们探讨了迭代器的基本概念和常用方法, 并使用Animal示例演示了相应的代码。 希望读者能够扎实地理解Rust迭代器,并在实际编程中有效地应用它们。

鱼雪