鍍金池/ 教程/ Java/ 迭代器
標準輸入與輸出
消息傳遞
循環(huán)
注釋
Rust for Mac OS
幾種智能指針
Cell, RefCell
trait對象 (trait object)
rust web 開發(fā)
Unsafe、原始指針
Macro
迭代器
函數(shù)
Borrow, BorrowMut, ToOwned
快速上手
二叉樹
編輯器
測試與評測
Deref
安裝Rust
哈希表 HashMap
原生類型
17.錯誤處理
VS Code 安裝配置
動態(tài)數(shù)組Vec
模式匹配
操作符和格式化字符串
Rust for Linux
函數(shù)參數(shù)
Visual Studio
vim/GVim安裝配置
閉包作為參數(shù)和返回值
安全(Safety)
Cow
生命周期( Lifetime )
閉包的實現(xiàn)
所有權(quán)(Ownership)
Atom
將Rust編譯成庫
類型、運算符和字符串
類型系統(tǒng)中的幾個常見 trait
特性
屬性和編譯器參數(shù)
Spacemacs
集合類型
Rust json處理
Heap & Stack
并行
標準庫示例
基本程序結(jié)構(gòu)
鏈表
trait 和 trait對象
前期準備
代碼風(fēng)格
編譯器參數(shù)
基于語義化版本的項目版本聲明與管理
Rust 版本管理工具: rustup
引用&借用(References&Borrowing)
注釋與文檔
10.1 trait關(guān)鍵字
模式
調(diào)用ffi函數(shù)
unsafe
并發(fā),并行,多線程編程
AsRef 和 AsMut
Rust旅程
Rust for Windows
結(jié)構(gòu)體與枚舉
條件分支
附錄I-術(shù)語表
變量綁定與原生類型
Mutex 與 RwLock
泛型
裸指針
常用數(shù)據(jù)結(jié)構(gòu)實現(xiàn)
系統(tǒng)命令:調(diào)用grep
Into/From 及其在 String 和 &str 互轉(zhuǎn)上的應(yīng)用
共享內(nèi)存
Sublime
網(wǎng)絡(luò)模塊:W貓的回音
函數(shù)返回值
包和模塊
高階函數(shù)
函數(shù)與方法
match關(guān)鍵字
隊列
目錄操作:簡單grep
語句和表達式
并發(fā)編程
閉包
測試
閉包的語法
同步
迭代器
String
Send 和 Sync
Rc 和 Arc
屬性
Emacs
優(yōu)先隊列
Prelude
cargo簡介
控制流(control flow)
數(shù)組、動態(tài)數(shù)組和字符串
FFI
模塊和包系統(tǒng)、Prelude
實戰(zhàn)篇
Rust 是一門系統(tǒng)級編程語言,被設(shè)計為保證內(nèi)存和線程安全,并防止段錯誤。作為系統(tǒng)級編程語言,它的基本理念是 “零開銷抽象”。理
運算符重載
Any和反射
rust數(shù)據(jù)庫操作
輸入輸出流
復(fù)合類型
性能測試

迭代器

從for循環(huán)講起

我們在控制語句里學(xué)習(xí)了Rust的for循環(huán)表達式,我們知道,Rust的for循環(huán)實際上和C語言的循環(huán)語句是不同的。這是為什么呢?因為,for循環(huán)不過是Rust編譯器提供的語法糖!

首先,我們知道Rust有一個for循環(huán)能夠依次對迭代器的任意元素進行訪問,即:

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

這里我們知道, (1..10) 其本身是一個迭代器,我們能對這個迭代器調(diào)用 .next() 方法,因此,for循環(huán)就能完整的遍歷一個循環(huán)。 而對于Vec來說:

let values = vec![1,2,3];
for x in values {
    println!("{}", x);
}

在上面的代碼中,我們并沒有顯式地將一個Vec轉(zhuǎn)換成一個迭代器,那么它是如何工作的呢?現(xiàn)在就打開標準庫翻api的同學(xué)可能發(fā)現(xiàn)了,Vec本身并沒有實現(xiàn) Iterator ,也就是說,你無法對Vec本身調(diào)用 .next() 方法。但是,我們在搜索的時候,發(fā)現(xiàn)了Vec實現(xiàn)了 IntoIterator 的 trait。

其實,for循環(huán)真正循環(huán)的,并不是一個迭代器(Iterator),真正在這個語法糖里起作用的,是 IntoIterator 這個 trait。

因此,上面的代碼可以被展開成如下的等效代碼(只是示意,不保證編譯成功):

let values = vec![1, 2, 3];

{
    let result = match IntoIterator::into_iter(values) {
        mut iter => loop {
            match iter.next() {
                Some(x) => { println!("{}", x); },
                None => break,
            }
        },
    };
    result
}

在這個代碼里,我們首先對Vec調(diào)用 into_iter 來判斷其是否能被轉(zhuǎn)換成一個迭代器,如果能,則進行迭代。

那么,迭代器自己怎么辦?

為此,Rust在標準庫里提供了一個實現(xiàn):

impl<I: Iterator> IntoIterator for I {
    // ...
}

也就是說,Rust為所有的迭代器默認的實現(xiàn)了 IntoIterator,這個實現(xiàn)很簡單,就是每次返回自己就好了。

也就是說:

任意一個 Iterator 都可以被用在 for 循環(huán)上!

無限迭代器

Rust支持通過省略高位的形式生成一個無限長度的自增序列,即:

let inf_seq = (1..).into_iter();

不過不用擔心這個無限增長的序列撐爆你的內(nèi)存,占用你的CPU,因為適配器的惰性的特性,它本身是安全的,除非你對這個序列進行collect或者fold! 不過,我想聰明如你,不會犯這種錯誤吧! 因此,想要應(yīng)用這個,你需要用take或者take_while來截斷他,必須? 除非你將它當作一個生成器。當然了,那就是另外一個故事了。

消費者與適配器

說完了for循環(huán),我們大致弄清楚了 IteratorIntoIterator 之間的關(guān)系。下面我們來說一說消費者和適配器。

消費者是迭代器上一種特殊的操作,其主要作用就是將迭代器轉(zhuǎn)換成其他類型的值,而非另一個迭代器。

而適配器,則是對迭代器進行遍歷,并且其生成的結(jié)果是另一個迭代器,可以被鏈式調(diào)用直接調(diào)用下去。

由上面的推論我們可以得出: 迭代器其實也是一種適配器!

消費者

就像所有人都熟知的生產(chǎn)者消費者模型,迭代器負責(zé)生產(chǎn),而消費者則負責(zé)將生產(chǎn)出來的東西最終做一個轉(zhuǎn)化。一個典型的消費者就是collect。前面我們寫過collect的相關(guān)操作,它負責(zé)將迭代器里面的所有數(shù)據(jù)取出,例如下面的操作:

let v = (1..20).collect(); //編譯通不過的!

嘗試運行上面的代碼,卻發(fā)現(xiàn)編譯器并不讓你通過。因為你沒指定類型!指定什么類型呢?原來collect只知道將迭代器收集到一個實現(xiàn)了 FromIterator 的類型中去,但是,事實上實現(xiàn)這個 trait 的類型有很多(Vec, HashMap等),因此,collect沒有一個上下文來判斷應(yīng)該將v按照什么樣的方式收集??!

要解決這個問題,我們有兩種解決辦法:

  1. 顯式地標明v的類型:

      let v: Vec<_> = (1..20).collect();
  2. 顯式地指定collect調(diào)用時的類型:

      let v = (1..20).collect::<Vec<_>>();

當然,一個迭代器中還存在其他的消費者,比如取第幾個值所用的 .nth()函數(shù),還有用來查找值的 .find() 函數(shù),調(diào)用下一個值的next()函數(shù)等等,這里限于篇幅我們不能一一介紹。所以,下面我們只介紹另一個比較常用的消費者—— fold 。

當然了,提起Rust里的名字你可能沒啥感覺,其實,fold函數(shù),正是大名鼎鼎的 MapReduce 中的 Reduce 函數(shù)(稍微有點區(qū)別就是這個Reduce是帶初始值的)。

fold函數(shù)的形式如下:

fold(base, |accumulator, element| .. )

我們可以寫成如下例子:

let m = (1..20).fold(1u64, |mul, x| mul*x);

需要注意的是,fold的輸出結(jié)果的類型,最終是和base的類型是一致的(如果base的類型沒指定,那么可以根據(jù)前面m的類型進行反推,除非m的類型也未指定),也就是說,一旦我們將上面代碼中的base1u64 改成 1,那么這行代碼最終將會因為數(shù)據(jù)溢出而崩潰!

適配器

我們所熟知的生產(chǎn)消費的模型里,生產(chǎn)者所生產(chǎn)的東西不一定都會被消費者買賬,因此,需要對原有的產(chǎn)品進行再組裝。這個再組裝的過程,就是適配器。因為適配器返回的是一個新的迭代器,所以可以直接用鏈式請求一直寫下去。

前面提到了 Reduce 函數(shù),那么自然不得不提一下另一個配套函數(shù) —— map :

熟悉Python語言的同學(xué)肯定知道,Python里內(nèi)置了一個map函數(shù),可以將一個迭代器的值進行變換,成為另一種。Rust中的map函數(shù)實際上也是起的同樣的作用,甚至連調(diào)用方法也驚人的相似!

(1..20).map(|x| x+1);

上面的代碼展示了一個“迭代器所有元素的自加一”操作,但是,如果你嘗試編譯這段代碼,編譯器會給你提示:

warning: unused result which must be used: iterator adaptors are lazy and
         do nothing unless consumed, #[warn(unused_must_use)] on by default
(1..20).map(|x| x + 1);
 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

呀,這是啥?

因為,所有的適配器,都是惰性求值的!

也就是說,除非你調(diào)用一個消費者,不然,你的操作,永遠也不會被調(diào)用到!

現(xiàn)在,我們知道了map,那么熟悉Python的人又說了,是不是還有filter???答,有……用法類似,filter接受一個閉包函數(shù),返回一個布爾值,返回true的時候表示保留,false丟棄。

let v: Vec<_> = (1..20).filter(|x| x%2 == 0).collect();

以上代碼表示篩選出所有的偶數(shù)。

其他

上文中我們了解了迭代器、適配器、消費者的基本概念。下面將以例子來介紹Rust中的其他的適配器和消費者。

skip和take

take(n)的作用是取前n個元素,而skip(n)正好相反,跳過前n個元素。

let v = vec![1, 2, 3, 4, 5, 6];
let v_take = v.iter()
    .cloned()
    .take(2)
    .collect::<Vec<_>>();
assert_eq!(v_take, vec![1, 2]);

let v_skip: Vec<_> = v.iter()
    .cloned()
    .skip(2)
    .collect();
assert_eq!(v_skip, vec![3, 4, 5, 6]);

zip 和 enumerate的恩怨情仇

zip是一個適配器,他的作用就是將兩個迭代器的內(nèi)容壓縮到一起,形成 Iterator<Item=(ValueFromA, ValueFromB)> 這樣的新的迭代器;

let names = vec!["WaySLOG", "Mike", "Elton"];
let scores = vec![60, 80, 100];
let score_map: HashMap<_, _> = names.iter()
    .zip(scores.iter())
    .collect();
println!("{:?}", score_map);

enumerate, 熟悉的Python的同學(xué)又叫了:Python里也有!對的,作用也是一樣的,就是把迭代器的下標顯示出來,即:

let v = vec![1u64, 2, 3, 4, 5, 6];
let val = v.iter()
    .enumerate()
    // 迭代生成標,并且每兩個元素剔除一個
    .filter(|&(idx, _)| idx % 2 == 0)
    // 將下標去除,如果調(diào)用unzip獲得最后結(jié)果的話,可以調(diào)用下面這句,終止鏈式調(diào)用
    // .unzip::<_,_, vec<_>, vec<_>>().1
    .map(|(idx, val)| val)
    // 累加 1+3+5 = 9
    .fold(0u64, |sum, acm| sum + acm);

println!("{}", val);

一系列查找函數(shù)

Rust的迭代器有一系列的查找函數(shù),比如:

  • find(): 傳入一個閉包函數(shù),從開頭到結(jié)尾依次查找能令這個閉包返回true的第一個元素,返回Option<Item>
  • position(): 類似find函數(shù),不過這次輸出的是Option<usize>,第幾個元素。
  • all(): 傳入一個函數(shù),如果對于任意一個元素,調(diào)用這個函數(shù)返回false,則整個表達式返回false,否則返回true
  • any(): 類似all(),不過這次是任何一個返回true,則整個表達式返回true,否則false
  • max()min(): 查找整個迭代器里所有元素,返回最大或最小值的元素。注意:因為第七章講過的PartialOrder的原因,maxmin作用在浮點數(shù)上會有不符合預(yù)期的結(jié)果。

以上,為常用的一些迭代器和適配器及其用法,僅作科普,對于這一章。我希望大家能夠多練習(xí)去理解,而不是死記硬背。

好吧,留個習(xí)題:

習(xí)題

利用迭代器生成一個升序的長度為10的水仙花數(shù)序列,然后對這個序列進行逆序,并輸出

上一篇:屬性下一篇:模式