Rust语言从入门到精通系列之Iterator迭代器深入详解

目录
  • 迭代器的基本概念
    • 迭代器是什么?
    • Iterator trait
  • Animal示例
  • 迭代器的常见用法
    • map方法
    • filter方法
    • enumerate方法
    • flat_map方法
    • zip方法
    • fold方法
  • 结论

在Rust语言中,迭代器(Iterator)是一种极为重要的数据类型,它们用于遍历集合中的元素。Rust中的大多数集合类型都可转换为一个迭代器,使它们可以进行遍历,这包括数组、向量、哈希表等。

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

在本文中,我们将探讨Rust语言中的迭代器的相关知识,并且以我们的老朋友Animal为例,提供相关的示例代码。

熟悉Java的Stream和Lambda的同学,学习本章节时,会格外的感觉“亲切”。

迭代器的基本概念

迭代器是什么?

在Rust中,迭代器是一个实现了Iterator trait的类型。该trait定义了一组行为,用于支持遍历集合中的元素。通过实现Iterator trait,类型可以被转换为一个迭代器,从而支持Iterate的操作。

Iterator trait

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

除了next方法之外,Iterator trait 还定义了其他许多有用的方法,比如map、filter等,这些方法可以对迭代器中的元素进行操作和转换。

pub trait Iterator {
      type Item;
    fn next(&mut self) -> Option<Self::Item>;
    //  多种内置实现方法, skip, map, reduce, collect
    //  和Java中的Stream内置方法非常类似.
}

Animal示例

接下来我们探讨实现一个Animal迭代器,Animal实现Iterator trait,使其可以通过迭代器遍历Animal的各个属性。 以下是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
    }
}

此时,我们已经将我们的类型转换为迭代器,我们就可以在它上面调用各种Iterator trait 的方法。例如,我们可以使用for循环遍历Animal对象的每一个属性:

#[derive(Debug)]
struct Animal {
    name: String,
    age: u32,
    kind: String,
    i:i32,
}
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
    }
}
fn main() {
    let mut animal = Animal {
        name: "Tom".to_string(),
        age : 15,
        kind: "cat".to_string(),
        i : 0
    };
    println!("Name: {}", animal.next().unwrap());
    println!("Age: {}", animal.next().unwrap());
    println!("Kind: {}", animal.next().unwrap());
}
//  输出结果:
// Name: Tom
// Age: 15
// Kind: cat

在上述代码中,我们定义了一个Animal类型的Iterator,并定义了一个名为i的内部状态变量。该变量用于追踪遍历的进度,并决定下一个迭代器值的内容。最终成功打印了animal的全部信息。

下面继续我们的学习,定一个Animal向量并遍历打印每一个Animal的所有属性:

fn print_all_attributes(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_attributes(animals);
}
//  输出结果:
// Name: Tom
// Age: 15
// Kind: cat

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

迭代器的常见用法

map方法

map方法是Iterator trait 中非常重要的一个方法,它可以让我们对迭代器中的每一个元素进行转换操作,并返回新的迭代器。例如:

fn main() {
    let animals = vec![Animal {
        name: "Tom".to_string(),
        age : 15,
        kind: "cat".to_string(),
        i : 0
    }, Animal {
        name: "Jerry".to_string(),
        age : 7,
        kind: "mouse".to_string(),
        i : 0
    }];
    let list: Vec<String> = animals
        .into_iter()
        .map(|ani| ani.name.clone())
        .collect();
    println!("{:?}", list)
}
// 输出 ["Tom", "Jerry"]

上述代码中,我们定义了一个包含2个的向量animals,并使用iter方法将其转换为一个迭代器。然后,我们使用map方法对这个迭代器中的Animal的name操作,返回一个新的迭代器,并使用collect方法将其转换为向量list。

filter方法

假设我们现在想寻找年龄大于等于3岁的动物,我们可以使用filter方法来实现。

fn main() {
    let animals = vec![Animal {
        name: "Tom".to_string(),
        age : 15,
        kind: "cat".to_string(),
        i : 0
    }];
    let filtered_animals: Vec<Animal> = animals
        .into_iter()
        .filter(|animal| animal.age >= 3)
        .collect();
    println!("{:?}", filtered_animals)
}
//  输出结果:
//  [Animal { name: "Tom", age: 15, kind: "cat", i: 0 }]

在上述代码中,我们使用into_iter方法将Animal向量转换为迭代器,并使用filter方法过滤其中年龄大于等于3岁的动物,最终返回一个新的Animal向量。

enumerate方法

enumerate方法会将一个迭代器中的元素和它们的索引配对,并返回一个新的迭代器。例如:

fn main() {
    let animals = vec![Animal {
        name: "Tom".to_string(),
        age : 15,
        kind: "cat".to_string(),
        i : 0
    }, Animal {
        name: "Jerry".to_string(),
        age : 7,
        kind: "mouse".to_string(),
        i : 0
    }];
    for (i, animal) in animals.iter().enumerate() {
        println!("{}: {:?}", i, animal);
    }
}
// 输出:
// 0: Animal { name: "Tom", age: 15, kind: "cat", i: 0 }
// 1: Animal { name: "Jerry", age: 7, kind: "mouse", i: 0 }

上述代码中,我们定义了一个包含2个Animal的向量animals,并使用iter方法将其转换为一个迭代器。然后,我们使用enumerate方法将每Animal与其索引配对,并在for循环中打印出来。

flat_map方法

flat_map方法是Iterator trait 中比较少见的方法之一,它可以用于将嵌套的迭代器展开为单个迭代器。例如:

#[derive(Debug, Clone)]
struct Animal {
    name: String,
    age: u32,
    kind: String,
    i: i32,
}
fn main() {
    let cat = Animal {
        name: "Tom".to_string(),
        age: 15,
        kind: "cat".to_string(),
        i: 0,
    };
    let mouse = Animal {
        name: "Jerry".to_string(),
        age: 7,
        kind: "mouse".to_string(),
        i: 0,
    };
    let animals = vec![vec![cat], vec![mouse]];
    let list: Vec<Animal> = animals.iter().flat_map(|x| x.iter().cloned()).collect();
    println!("{:?}", list)
}
// 输出 [Animal { name: "Tom", age: 15, kind: "cat", i: 0 }, Animal { name: "Jerry", age: 7, kind: "mouse", i: 0 }]

上述代码中,我们定义了一个二维向量animals,并使用iter方法将它转换为迭代器。然后,我们使用flat_map方法将它展开为一个一维的迭代器,并使用collect方法将其转换为向量list。

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);
    }
}
//    输出结果:
// Tom is 3 years old.
// Jerry is 4 years old.
// Bob is 5 years old.

上述代码中,我们使用iter方法将names和ages向量转换为迭代器,并使用zip方法对它们进行配对。对于每一对元素,我们调用println!函数并打印它们。

fold方法

fold方法在Rust中也十分重要,它可以接受一个初始值和一个闭包,遍历迭代器中的每一个元素,并将它们合并成单个值。例如:

fn main() {
    let cat = Animal {
        name: "Tom".to_string(),
        age: 15,
        kind: "cat".to_string(),
        i: 0,
    };
    let mouse = Animal {
        name: "Jerry".to_string(),
        age: 7,
        kind: "mouse".to_string(),
        i: 0,
    };
    let animals = vec![cat, mouse];
    let sum = animals.iter().fold(0, |t, ani| t + ani.age );
    println!("{}", sum)
}
// 输出 22

上述代码中,我们定义了一个包含2个Animal的向量animals,并使用iter方法将其转换为一个迭代器。然后,我们使用fold方法对这个迭代器中的age进行累加,并返回结果sum。

结论

迭代器是Rust语言中非常重要的数据类型,它们用于遍历集合中的元素,并支持各种操作。在本教程中,我们探讨了迭代器的基本概念和常见用法,以Animal为例子,提供了相应的演示代码。希望读者能够掌握Rust迭代器的相关内容,并且在实际编程中得到应用。

以上就是Rust语言从入门到精通系列之Iterator迭代器深入详解的详细内容,更多关于Rust Iterator迭代器的资料请关注我们其它相关文章!

(0)

相关推荐

  • Rust Atomics and Locks 源码解读

    目录 正文 load 和 store 使用 AtomicBool实现通知线程停止的案例 正文 在 Rust 中,原子性操作是指在多线程并发环境下对共享数据进行操作时,保证操作的原子性,即不会出现数据竞争等问题.Rust 提供了原子类型和原子操作来支持多线程并发编程. Rust 的原子类型包括 AtomicBool.AtomicIsize.AtomicUsize.AtomicPtr 等.这些类型的实现都使用了底层的原子操作指令,保证了它们的读写操作是原子的,不会被其他线程中断. 在 Rust 中,

  • 向Rust学习Go考虑简单字符串插值特性示例解析

    目录 fmt.Printf 或 fmt.Sprintf 写拼装字符串业务 简单字符串插值 其他语言例子 Swift Kotlin C Rust 争论矛盾点 总结 fmt.Printf 或 fmt.Sprintf 写拼装字符串业务 在日常开发 Go 工程中,我们经常会用 fmt.Printf 或 fmt.Sprintf 去写类似的拼装字符串的业务. 如下代码: fmt.Printf("Hello Gopher %s, you are %d years old and you're favorite

  • Rust Atomics and Locks内存序Memory Ordering详解

    目录 Rust内存序 重排序和优化 happens-before Relexed Ordering Release 和 Acquire Ordering SeqCst Ordering Rust内存序 Memory Ordering规定了多线程环境下对共享内存进行操作时的可见性和顺序性,防止了不正确的重排序(Reordering). 重排序和优化 重排序是指编译器或CPU在不改变程序语义的前提下,改变指令的执行顺序.在单线程环境下,重排序可能会带来性能提升,但在多线程环境下,重排序可能会破坏程序

  • Rust Atomics and Locks并发基础理解

    目录 Rust 中的线程 线程作用域 所有权共享 借用和数据竞争 内部可变 rust 中的线程安全 Send 和 Sync 线程阻塞和唤醒 Rust 中的线程 在 Rust 中,线程是轻量级的执行单元,可以并行执行多个任务.Rust 中的线程由标准库提供的 std::thread 模块支持,使用线程需要在程序中引入该模块.可以使用 std::thread::spawn() 函数创建一个新线程,该函数需要传递一个闭包作为线程的执行体.闭包中的代码将在新线程中执行,从而实现了并发执行.例如: use

  • 从迷你todo 命令行入门Rust示例详解

    目录 一个迷你 todo 应用 需要安装的依赖 文件目录组织 主文件 读取文件 状态处理工厂函数 Trait(特征) Create trait Get trait Delete trait Edit trait 导出 trait 为 struct 实现 trait Pending Done 导出 struct Process 输入处理 最后 一个迷你 todo 应用 该文章将使用 Rust 从零去做一个入门级别的 TODO 命令行应用 你将学会什么? 基本的命令行操作 文件读写和文件结构组织 我

  • Rust 所有权机制原理深入剖析

    目录 what's ownership? Scope (作用域) ownership transfer(所有权转移) move clone copy References and Borrowing(引用和借用) Mutable References(可变引用) Dangling References(悬垂引用) what's ownership? 常见的高级语言都有自己的 Garbage Collection(GC)机制来管理程序运行的内存,例如 Java.Go 等.而 Rust 引入了一种全

  • Rust编写自动化测试实例权威指南

    目录 一. 简述 二. 编写测试 三. 测试相关的宏和函数 3.1. 使用assert!宏检查结果 3.2. 使用assert_eq!宏和assert_ne!宏判断相等性 3.3. 添加自定义的错误提示信息 3.4. 使用should_panic检查paninc 3.5. 使用Result<T, E>编写测试 四. 控制测试的运行方式 4.1. 并行或串行的进行测试 4.2. 显示函数输出 4.3. 只运行部分特定名称的测试 4.4. 通过显示指定来忽略某些测试 五. 测试的组织结构 5.1.

  • Rust语言从入门到精通系列之Iterator迭代器深入详解

    目录 迭代器的基本概念 迭代器是什么? Iterator trait Animal示例 迭代器的常见用法 map方法 filter方法 enumerate方法 flat_map方法 zip方法 fold方法 结论 在Rust语言中,迭代器(Iterator)是一种极为重要的数据类型,它们用于遍历集合中的元素.Rust中的大多数集合类型都可转换为一个迭代器,使它们可以进行遍历,这包括数组.向量.哈希表等. 使用迭代器可以让代码更加简洁优雅,并且可以支持一些强大的操作,例如过滤.映射和折叠等. 在本

  • 入门到精通Java SSO单点登录原理详解

    目录 1. 基础概念 2. 单点登录 3. CAS 流程 4. OAuth 流程 5. CAS和OAuth的区别 1. 基础概念 SSO单点登录(Single sign-on) 所谓单点登录就是在多个应用系统中,用户只需登录一次就可以访问所有相互信任的系统. CAS 中央认证服务(Central Authentication Service) CAS是由美国耶鲁大学发起的一个企业级开源项目,旨在为WEB应用系统提供一种可靠的单点登录解决方案(WEB SSO). OAuth2.0 开放授权(Ope

  • C++入门教程之内联函数与extern "C"详解

    目录 一.   内联函数 1.概念及分析 2.特性 3.宏 二.   extern “C” 1.C++程序 2.C程序 总结 一.   内联函数 1.概念及分析 以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率. int Add(int a, int b) { int c = a + b; return c; } int main() { int ret= Add(1, 2); return 0; } 在我

  • Ajax 入门之 GET 与 POST 的不同处详解

    在之前的随笔中,本着怀旧的态度总结了一篇 兼容不同浏览器 建立XHR对象的方法: 在建立好XHR对象之后,客户端需要做的就是,将数据以某种方式传递到服务器,以获得相应的响应,在这里,  Ajax技术总结的第二季,我将重点阐述 提交数据的两种方式. 在这之前需要了解一下我们的HTTP传输协议: HTTP 的工作方式是客户机与服务器之间的请求-应答协议. 举例:客户端(浏览器)向服务器提交 HTTP 请求:服务器向客户端返回响应.响应包含关于请求的状态信息以及可能被请求的内容.而想要基于HTTP协议

  • Jquery揭秘系列:ajax原生js实现详解(推荐)

    讲到ajax这个东西,我们要知道两个对象XMLHTTPRequest和ActiveXObject ,提供了对 HTTP 协议的完全的访问,包括做出 POST 和 HEAD 请求以及普通的 GET 请求的能力.可以同步或异步返回 Web 服务器的响应,并且能以文本或者一个 DOM 文档形式返回内容.XMLHTTPRequest基本上算是标准化了,兼容大部分浏览器ActiveXObject这玩儿意儿是微软的东西,所以是为了兼容IE版本,我们用的只是它的xmlHTTP功能. 为了功能的明确和清晰,我们

  • C语言实现opencv提取直线、轮廓及ROI实例详解

    一.Canny检测轮廓 在上一篇文章中有提到sobel边缘检测,并重写了soble的C++代码让其与matlab中算法效果一致,而soble边缘检测是基于单一阈值的,我们不能兼顾到低阈值的丰富边缘和高阈值时的边缘缺失这两个问题.而canny算子则很好的弥补了这一不足,从目前看来,canny边缘检测在做图像轮廓提取方面是最优秀的边缘检测算法. canny边缘检测采用双阈值值法,高阈值用来检测图像中重要的.显著的线条.轮廓等,而低阈值用来保证不丢失细节部分,低阈值检测出来的边缘更丰富,但是很多边缘并

  • tensorflow入门:TFRecordDataset变长数据的batch读取详解

    在上一篇文章tensorflow入门:tfrecord 和tf.data.TFRecordDataset的使用里,讲到了使用如何使用tf.data.TFRecordDatase来对tfrecord文件进行batch读取,即使用dataset的batch方法进行:但如果每条数据的长度不一样(常见于语音.视频.NLP等领域),则不能直接用batch方法获取数据,这时则有两个解决办法: 1.在把数据写入tfrecord时,先把数据pad到统一的长度再写入tfrecord:这个方法的问题在于:若是有大量

  • C语言自定义数据类型的结构体、枚举和联合详解

    结构体基础知识 首先结构体的出现是因为我们使用C语言的基本类型无法满足我们的需求,比如我们要描述一本书,就需要书名,作者,价格,出版社等等一系列的属性,无疑C语言的基本数据类型无法解决,所以就出现了最重要的自定义数据类型,结构体. 首先我们创建一个书的结构体类型来认识一下 struct Book { char name[20]; char author[20]; int price; }; 首先是struct是结构体关键字,用来告诉编译器你这里声明的是一个结构体类型而不是其他的东西,然后是Boo

  • Android入门教程之组件Activity的生命周期详解

    目录 返回栈 Activity 状态 1. 运行状态 2. 暂停状态 3. 停止状态 4. 销毁状态 Activity 的生存期 onCreate() onStart() onResume() onPause() onStop() onDestroy() onRestart() 完整生存期 可见生存期 前台生存期 Activity 回收处理 返回栈 Android 中的 Activity 是可以层叠的,我们每启动一个新的 Activity,就会覆盖在原有的 Activity 之上,然后点击 Ba

  • Java入门绊脚石之Override和Overload的区别详解

    目录 前言: 一.方法重写(Override) 1.方法重写基本概念 2.方法重写基本规则及注意事项 二.overload方法重载 1.什么是重载 2.重载的规则 3.总结: 前言: 各位小伙伴们,大家好,一日不见,如隔一日,今天我给大家分享一下大家在学习java过程当中遇到的一个问题,也是一道面试题,java中,Override和Overload的区别. 一.方法重写(Override) 1.方法重写基本概念 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变.即

随机推荐