LoginSignup
490
341

More than 1 year has passed since last update.

Rustのイテレータの網羅的かつ大雑把な紹介

Last updated at Posted at 2016-02-08

はじめに

正直なところ、公式ドキュメント std::iter::Iterator - Rust を読めば終了、この記事は不要、という感じはある。
しかしこのページ、例とかは豊富だし良いのだが、関数の並べ順がどうにも微妙な感じがするので、メモを兼ねて用途別(独断と偏見による分類)で紹介してみることにする。

あと、関数の型はHaskell風に書くと流れる感じがあって良い(イテレータの用途とマッチしている気がする)ので、 T -> U -> V のように書くことがある。
とはいえ、わかりやすさ優先で書くと FnMut とか FromA: Default + Extend<A> みたいな独特の情報が失われてしまったため、やはり公式のドキュメントをあわせて見ることをおすすめする。(リンクを沢山張ったので、発見次第別ウィンドウで開くくらいの勢いが良い。)

それから、以下で Iterator<T> と書いているものは、実際には別の様々な型でありうるが、どうせ具体的な型はコード中で意識することは(滅多に)ないので、 Iterator traitを実装しているものはすべて Iterator<T> と書くことにする。

見出しを活用してね!→


中身を(最大1つ)取り出す系

以下のものは、みな最大1つの要素を Option<T> で返す。

next(): Iterator<T> -> Option<T>

次の要素を Some で返す。
次の要素がなければ None を返す。

ただし、イテレータ(の next())が None を返した後に next() を呼んだとき、 None が返ることは保証されない(再び Some を返しても良い)らしい。
その保証が欲しければ、 fuse() を使うべし。
(とはいえ、普通は使い切ったイテレータを再利用することはないので、気にせずとも良いだろう。)

last(): Iterator<T> -> Option<T>

最後の要素を Some で返す。
要素がなければ、 None を返す。

正確には、「 None が返される直前に返されたオブジェクトを返す」が正しい。
ドキュメントにもそう書いてある。
(イテレータが None の後にまた Some を返すこともあるので、「最後の」という言い回しは微妙である。が、まあ直観的な使い方での「最後」である。)

ちなみに、 last() があるのだから first() があっても良いのでは、と一瞬考えてしまうが、要するにそれは next() だ。

nth(): Iterator<T> -> usize -> Option<T>

現在の位置から数えてn番目の要素を Some で返す。
(言うまでもないが、最初の要素は0番目である。)
n番目の要素を得る前に終端に到達した場合、 None を返す。

結果だけ見ればn個の要素を飛ばした後で .next() を呼び出すのと同じであるが、 nth を使う方がより最適化される可能性がある。

find(): Iterator<T> -> (&T -> bool) -> Option<T>

与えられた関数 &T -> booltrue を返す最初の要素を Some で返す。
発見できなければ None を返す。

max(): Iterator<T> -> Option<T> (ただし T: Ord)

Ord traitを実装している型のイテレータについて、その最大の要素を Some で返す。
もし同じ最大値が複数あれば、最後に出現したものを返す。
None が返るのは、そもそも要素がひとつもなかった場合である。

min()

最小値を返す。
詳細は max() に同じ。

max_by_key(): Iterator<T> -> (&T -> U) -> Option<T> (ただし U: Ord)

max() と似ているが、T の代わりに、 U で比較を行った結果の最大値を返す
公式では、絶対値が最大の要素を返すコード例が載っている。

min_by_key()

max_by_key() の最小値版。

max_by(): Iterator<T> -> (&T -> &T -> Ordering) -> Option<T>

提供された比較関数で比較をした場合の最大の要素を Some で返す。
これを使えば、たとえば「(ASCIIの)大文字・小文字を区別せず大小比較する」といった特殊な比較を使った最小値が求められる。

大文字・小文字を無視した比較(ごめんこれmin_byのサンプルだわ)
let words = vec!["abc", "FOOBAR", "xyz"];
let case_insensitive_min = words.iter().min_by(|a, b| {
    let a_iter = a.chars().map(|c| c.to_ascii_lowercase());
    let b_iter = b.chars().map(|c| c.to_ascii_lowercase());
    a_iter.cmp(b_iter)
}).unwrap();

(全体コード: Playground)

安直にやれば、 str::to_lowercase() で先に(こちらは ASCII に限らず)小文字に変換しきってから比較ということになるが、これだと文字列の生成、つまりメモリアロケーションがあるので無駄が多い。
そこで min_by を使えば、文字列を生成することなく、文字毎に変換をしつつ比較ということが可能になる。

min_by()

max_by の最小値版。

find_map(): Iterator<T> -> (T -> Option<U>) -> Option<U>

与えられた関数(T -> Option<U>)を各要素に順次適用していき、最初に Some(_) になったものを返す。

リファレンスにもある通り、 iter.find_map(f)iter.filter_map(f).next() と同じである。

処理の順番としては、 (filter_map も同じく)「map した結果から (Some を) find する」という順になるが、名前は find_map である。

Rust-1.30 から追加された。

中身を弄らず要素を選択する系

以下のものは、みな新たなイテレータ Iterator<T> を返す。

filter(): Iterator<T> -> (&T -> bool) -> Iterator<T>

名前の通り。
{与えられた関数(&T -> bool)が true を返すものだけを返すような、新たなイテレータ}を返す。

take(): Iterator<T> -> usize -> Iterator<T>

名前の通り。
{与えられた個数(usize)ぶんの要素を返し、それ以降の要素を無視するような、新たなイテレータ}を返す。

take() したものと skip() したものを両方使いたいのであれば、 by_ref() を使うことになる。そっち参照。

skip(): Iterator<T> -> usize -> Iterator<T>

名前の通り。
{与えられた個数(usize)ぶんの要素を無視し、それ以降の要素を返すような、新たなイテレータ}を返す。

take() したものと skip() したものを両方使いたいのであれば、 by_ref() を使うことになる。そっち参照。

step_by(): Iterator<T> -> usize -> Iterator<T>

{指定された個数ごとに要素を返すような、新たなイテレータ}を返す。

たとえば、 2 を与えると、1つ要素を返し、1つ skip し、また1つ要素を返し、また1つ skip し、……というような「2つごとで要素を取り出す」イテレータを返す。
文章でわからなければ、リファレンスの example を見るのが早いだろう。

Rust 1.28.0 時点では、読み飛ばしが先に起きるか、必要になるまで読み飛ばされないかは規定されておらず、実装によって異なることが許されている。
要素を取り出すことで副作用のあるイテレータ (I/O 関係だったり inspect() でログ出力などをしたりなど)を使う場合は、注意が必要である。

take_while(): Iterator<T> -> (&T -> bool) -> Iterator<T>

名前の通り。
{与えられた関数(&T -> bool)が true を返し続ける間はその要素を返し、最初に false を返したもの含めそれ以降の要素を無視するような、新たなイテレータ}を返す。
要するに「最初に false になったものを含まず、それ以前」である。
ちょうど skip_while() の反対だ。

take_while() したものと skip_while() したものを両方使いたいのであれば、 by_ref() を使うことになる。そっち参照。
訂正: take_while() は、与えた条件を満たさない最初の要素を捨ててしまうので、 by_ref() を使っても駄目だ。
素直にループ等で書くべきだろう。

skip_while(): Iterator<T> -> (&T -> bool) -> Iterator<T>

名前の通り。
{与えられた関数(&T -> bool)が true を返し続ける間はその要素を無視し、最初に false を返したもの含めそれ以降の要素を返すような、新たなイテレータ}を返す。
要するに「最初に false になったものを含め、それ以降」である。

take_while() したものと skip_while() したものを両方使いたいのであれば、 by_ref() を使うことになる。そっち参照。
訂正: take_while() は、与えた条件を満たさない最初の要素を捨ててしまうので、 by_ref() を使っても駄目だ。
素直にループ等で書くべきだろう。

イテレータが持つ要素全体について何かの情報を得る系

以下のものは、だいたい Iterator<T> -> U (ただし U はイテレータではない) のような型になる。
個々の要素(T 型)について何かを弄った値を返すというわけではない。
一般的な情報を得るので、大抵の場合、 UT とは直接には殆ど関係のない型になる。

size_hint(): Iterator<T> -> (usize, Option<usize>)

わかる範囲での残りの要素数の、最低と最高を返す。
最低が usize で、最高が Option<usize> だ。
珍しいことに、この関数はイテレータをconsume しない

デフォルトでは(つまり個々のイテレータ型に特別に実装しなければ)、 (0, None) が返される。
「残りの要素は最低0個で、最高で幾つなのかは不明」という意味だから、まあ何も言ってないのと同じだ。

この関数は元々は、メモリ領域確保の際の最適化などでの利用を意図して用意されているらしい。(たとえば、 collect::<Vec<_>>() とかで予めreserveしておく要素数を見積って効率化を目論む、とかだろう。)
しかしイテレータ側での実装にバグがあるかもしれないから、信用しすぎるなよ、とも書いてある

簡単な用途では、この関数を使う機会はないと考えて良いだろう。

count(): Iterator<T> -> usize

要素を悉く読み飛ばし、その要素数を返す。
正確には、{イテレータ(の next())が最初に None を返すまでに返した要素}の個数を返す。

size_hint() とは違って、イテレータをconsumeするので注意。

all(): Iterator<T> -> (T -> bool) -> bool

名前の通り。
要素を与えられた関数(T -> bool)に渡したとき、すべての要素について true が返れば true を、そうでなければ(つまり1つ以上の要素で false を返れば) false を返す。

any(): Iterator<T> -> (T -> bool) -> bool

名前の通り。
要素を与えられた関数(T -> bool)に渡したとき、1つ以上の要素について true が返れば true を、そうでなければ(つまりすべての要素で false が返れば) false を返す。

position(): Iterator<T> -> (T -> bool) -> Option<usize>

与えられた関数(T -> bool)で true を返すような最初の要素の順番を、 Some で返す。
そのような要素がなければ、 None を返す。

もし順番が usize で表現できる最大値を超えていれば、panicする(メインスレッドだったら普通だと落ちる)ので注意。
(とはいえ、普通に使っていれば問題にはならないはず。)

rposition(): I -> (T -> bool) -> Option<usize> (ただし IExactSizeIterator<T> かつ DoubleEndedIterator<T>)

与えられた関数(T -> bool)で true を返すような最後の要素の(最初側から数えての)順番を、 Some で返す。
そのような要素がなければ、 None を返す。

これを使えるイテレータは条件が少し厳しく、要素数が事前に正しくわかっており、かつ後ろから辿れる必要がある。
配列(arrayやslice)や Vec<T> などはその条件を満たす。

イテレータひとつを、中身をほとんど変えず、役立つ形に作り替える系 (強化系)

以下のものは、様々な型の値を返すが、だいたいが Iterator<T> -> Iterator<T> とか Iterator<T> -> Iterator<&T> とかである。
が、「元のイテレータの要素自体には(殆ど)手を加えない」点は共通している。

peekable(): Iterator<T> -> Iterator<T>

peek() というメソッドが使えるような新たなイテレータ(Peekable<T>)を返す。
peek() の型は、 Peekable<T> -> Option<&T> である。
名前の通り、値を取得してイテレータ側から奪い去る前に、前以て参照を貰って中身を確認できるというわけだ。

cloned(): Iterator<&T> -> Iterator<T>

iter.cloned() は、 iter.map(|r| r.clone()) のように動作する。
(実際の実装では、元のイテレータの next() の結果についてOption::cloned()を呼び出すようになっている。)
要するに、参照のイテレータを、所有権のある(cloneされた)オブジェクトのイテレータにするためのものだ。
なお、参照のイテレータでないものについて cloned() を呼ぶことはできない(型エラーになる)ので、いつでも cloned() を使っておけばいいというものでもない。
必要なときにだけ使おう。

copied(): Iterator<&T> -> Iterator<T> (ただし TCopy)

Rust-1.36 で追加された。

根本的には cloned() と同じだが、 T が単純なメモリコピーで複製できる型に限られたバージョン。
iter.copied() は、 iter.map(|r| *r) のように (あるいは同じことだが iter.map(|&r| r) のように) 動作する。

v.clone() だと clone に高いコストがかかる可能性が否めず、それがコードから読み取りづらい。
一方、 Copy トレイトが実装された型は、一般に clone (copy) のコストが極めて低い。
よって、コストが小さなコピーであることが明確になるよう、可能なときは .clone() よりも単純なコピーを、また .cloned() よりも .copied() を使うべきである。

fuse(): Iterator<T> -> Iterator<T>

{元のイテレータ(の next())が一度でも None を返したら、それ以降必ず None を返すような、新たなイテレータ}を返す。
電子部品のヒューズみたいなもので、一度要素が切れたら再び繋がることはない、ということだ。

None の後に再び Some を返すイテレータというのがどんなものかは、公式に(若干強引な気はするが)例が載っているので、気になるのであれば参照されたし。
また、実用的な例としては、メッセージキューのメッセージを順次取得するイテレータ(たとえばglutin::PollEventsIterator - Rust等)がある。
これはメッセージを一度すべて読み切って None が返ってきたとしても、その後またメッセージが到着すれば Some が返るようなイテレータだ。

by_ref(): Iterator<T> -> &Iterator<T>

イテレータをconsume せずに、そのイテレータのmutableな参照を返す。
こいつはちょっと特殊だ。

イテレータを別のイテレータに変化させる系(map() とか take() とか)の関数の大半は、元のイテレータを消費(consume)して新たなイテレータを返す。
しかし、たとえば「 take() したイテレータの残りを、また後で使いたい」などの場合、 take() の時点で元のイテレータの所有権を失っては困るわけだ。

let vec_iter = (40..46).collect::<Vec<_>>().into_iter();

println!("first three:");
for (i, n) in vec_iter.take(3).enumerate() {
    println!("{}: {}", i, n);
}

println!("rest:");
for (i, n) in vec_iter.enumerate() { // <- エラー!
    // ↑ "use of moved value: `vec_iter` [E0382]"
    println!("{}: {}", i, n);
}

上の例だと、二度目に使いたいとき(エラーの行)、既に vec_itervec_iter.take(3) によってconsumeされており、呼び出し元のコードは vec_iter の所有権を失っているため、次の使用(vec_iter.enumerate())でエラーになったわけだ。

で、どうするかというと、こうする。

// イテレータは何度も使う(かつ状態が変更される)ので、mutableにする
let mut vec_iter = (40..46).collect::<Vec<_>>().into_iter();

println!("first three:");
// `by_ref()` してから `take()` する
for (i, n) in vec_iter.by_ref().take(3).enumerate() {
    println!("{}: {}", i, n);
}

println!("rest:");
for (i, n) in vec_iter.enumerate() { // <- おk
    println!("{}: {}", i, n);
}

これはコンパイルでき、正しく動作する。
もちろん、出力はこうなる。

output:
0: 40
1: 41
2: 42
rest:
0: 43
1: 44
2: 45

参考までに

型を見ればわかるとおり、 iter.by_ref() というのは、つまり (&mut iter) と同じなのだが、C++等に馴染んでいる人にはこのコードは直観的にわかりかねるだろう(少なくとも私はそうだった)。

公式ドキュメントの、Iterator traitを実装している型のリストをよ〜く見ると、
impl<'a, I> Iterator for &'a mut I where I: Iterator + ?Sized
というのがある。
「任意のイテレータ型 I について、 &mut I 型は Iterator traitを実装している」という意味だ。
実はRustでは、ポインタや参照のような型についてもメソッド(のようなもの)を実装することができ、この場合では「イテレータの参照」について next() 等のメソッドを実装している。
そして、 iter.by_ref().take(3)(&mut iter).take(3) というのは、 iter そのものではなく、 iter への参照について take() を呼び出しているのだ。
で、 Iterator::take(&mut iter, 3) のような形で呼び出されるから、 iter はconsumeされず、所有権も持ったままでいられる、という原理だ。

参考までに、該当箇所のソース (github)の引用もしておこう。

#[stable(feature = "rust1", since = "1.0.0")]
impl<'a, I: Iterator + ?Sized> Iterator for &'a mut I {
    type Item = I::Item;
    fn next(&mut self) -> Option<I::Item> { (**self).next() }
    fn size_hint(&self) -> (usize, Option<usize>) { (**self).size_hint() }
}

inspect(): Iterator<T> -> (&T -> ()) -> Iterator<T>

{イテレータの要素が必要とされる毎に、その要素を副作用のある関数に渡して実行するような、新たなイテレータ}を返す関数。

要するに、中身を覗き見できる。
Rubyで言うところの tap だ。
(参考: TapがRubyの新たな制御構造の世界を開く)

公式のコード例を見れば、何もかも理解できることだろう。

イテレータひとつを、順を変化させたり情報を付随させたりして、役立つ形に作り替える系 (進化系)

以下のものは、様々な型の値を返す。
が、「元のイテレータの要素自体には(殆ど)手を加えない」点は共通している。

enumerate(): Iterator<T> -> Iterator<(usize, T)>

T を返すイテレータから、 enumerate() を呼んだ時点を最初として、要素の順番を (順番, T) の形で一緒に返すような、新たなイテレータ}を返す。

iter.enumerate() は、 (0_usize..).zip(iter) と同義である。
もし usize 以外の要素で順番が必要なのであれば、 zip() を使った方が良い。

cycle(): Iterator<T> -> Iterator<T>

イテレータを永遠にループさせる。
つまり、最後の要素の次に、また最初の要素から繰り返す。
(元のイテレータ(の next()) が None を返すような場合、イテレータの状態が cycle() 呼び出し直後と同じになる……というイメージ。伝われ!)

公式ドキュメントの例を見た方が早い気はした。

scan(): Iterator<T> -> (&mut S -> T -> Option<B>) -> Iterator<B>

fold() の途中経過が見える版。
S は状態、 B は新しいイテレータが吐く型だ。

詳しくは公式ドキュメントの例を見ればわかるが、渡した関数が、状態の更新と、次にイテレータが返す値の計算の両方を担っている。

公式とは別の例も出してみよう。おなじみフィボナッチ数列だ。

let fib = (0..12).scan((0, 1), |prev, _| {
        let ret = prev.0;
        *prev = (prev.1, prev.0 + prev.1);
        Some(ret)
    })
    .collect::<Vec<_>>();
println!("fib: {:?}", fib);
実行結果
fib: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

べつにループでやればいい話ではあるのだが、まあとにかく状態を持てるということは普通のループっぽいことができるということだ。

(何かいい例募集中)
例はコメントを参照。

rev(): DoubleEndedIterator<T> -> DoubleEndedIterator<T>

名前の通り。
{元のイテレータとは順番を逆にして返すような、新たなイテレータ}を返す。

参考

DoubleEndedIterator<T> というのは、最後尾から前へと辿れるイテレータのことだ。
(単に後ろ向きに進めるイテレータというわけではない。)
C++で言うところの、後ろ向きに進むForwardIteratorであり、BidirectionalIteratorではない

DoubleEndedIterator<T> は、 Iterator<T> でもあるが、それに加えて next_back() というメソッドを持っている。
その動作についてはドキュメント(前リンク)を見るべし。

イテレータふたつを組み合わせて、役立つ形に作り替える系 (合体系)

以下のものは、 Iterator<T> -> Iterator<U> -> Iterator<TとUに関係ある型> のような感じである。

chain(): Iterator<T> -> IntoIterator<T> -> Iterator<T>

イテレータを繋げる。
iter1.chain(iter2) は、 iter1 から要素を全部読みきった後、続けて iter2 の要素を返す。 iter2 の要素も全部読んだら、今度こそ終わりとなる。

IntoIterator<T> というのは、つまり雑に言えば「 Iterator<T> に変換可能」ということだ。
自明な例では、 Iterator<T> などは IntoIterator<T> でもある。
他にも、 Vec<T>Iterator<T> に変換できる(正確には impl<T> IntoIterator for Vec<T> で実装されている)ので、 Iterator<T>::chain()Vec<T> を渡す、以下のようなコードも書ける。

for i in vec![1, 2, 3].into_iter().chain(vec![4, 5, 6]) {
    println!("{}", i);
}

また、 iter1.chain(iter2).chain(iter3) などのように複数を一気に繋げることもできる。

zip(): Iterator<T> -> IntoIterator<U> -> Iterator<(T, U)>

2つのイテレータから、{両方の要素を順に取って、そのペアを返すようなイテレータ}を作る。

ここで説明するより、公式ドキュメントの例を見た方が早いだろう。

IntoIterator<U> については、 chain() のところで説明した通りだ。

イテレータが持つ個々の要素を変更して流す系 (加工系)

型は、だいたい Iterator<T> -> (T -> U) -> Uに関係するイテレータ型 みたいな感じである。

map(): Iterator<T> -> (T -> U) -> Iterator<U>

名前から想像できる通りだ。

少しだけ注意が必要なのは、 map() 等は基本的に評価が遅延されるので、これ単体では for 文の代わりには使えないということである。
わかりやすいコードを書くのなら、あくまで値を変化させるために使うべきだろう。

filter_map(): Iterator<T> -> (T -> Option<U>) -> Iterator<U>

イテレータに対して、{要素を受け取って Option を返す関数}を適用し、 Some だったものの中身だけを繋げて返す関数。
要するに、名前の通り filtermap を同時に行うということだ。

別のイメージとしては、**Option のイテレータについて、内側の Some を剥がす(unwrapする)**感覚だ。

以下のようなコードが:

例1
for temp_opt in iter.map(foo) {
    if let Some(i) = temp_opt {
        bar(i);
    }
}
例2
for temp_iter in iter.map(foo).filter(Option::is_some).map(Option::unwrap) {
    bar(i);
}

filter_map を使うと、以下のようにできる:

for i in iter.filter_map(foo) {
    bar(i);
}

結構便利だ。

公式のドキュメントでは、「失敗するかもしれない(Result を返す)関数でmapし、その結果から Ok なものだけを取り出して、その中身を列挙するイテレータを返す」、要するに成功例だけを列挙するという、いかにも実用的な例が載っているので、一度は見てみるべきである。

flat_map(): Iterator<T> -> (T -> IntoIterator<U>) -> Iterator<U>

イテレータに対して、{要素を受け取ってイテレータ(に変換できるもの)を返す関数}を適用し、結果出てきたそれぞれのイテレータを順に繋げて返す関数である。

イメージとしては、**ネストしたイテレータについて、内側のイテレータを剥がす(unwrapする)**感覚だ。

こんなコードが:

例1
for temp_iter in iter.map(foo) {
    for i in temp_iter {
        bar(i);
    }
}

こうなる:

for temp_iter in iter.flat_map(foo) {
    bar(i);
}

参考

勘の良い人は(というか型を見れば)お気付きだろうが、 filter_map()flat_map()Option 特化版である。
そして Option<T>IntoIterator<T> でもある(〜を実装している)から、 filter_map()flat_map() に機械的に置き換えても、何ら問題なく動作する。

むしろ、 Result<T> なんかも IntoIterator<T> を実装しているので、公式ドキュメントの filter_map() の例は、 flat_map を使うともっと簡単になる。

公式の例
let a = ["1", "2", "lol"];

let mut iter = a.iter().filter_map(|s| s.parse().ok());

assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), None);
flat_mapを使う
let a = ["1", "2", "lol"];

// ↓ `.ok()` 要らず
let mut iter = a.iter().flat_map(|s| s.parse());

assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), None);

どちらも使える場合、文字数を節約できる flat_map() を使うか、型と意味の明確な filter_map() を使うかは……まあ皆さんの信仰次第だ。

flatten(): Iterator<IntoIterator<T>> -> Iterator<T>

{イテレータを返すイテレータ}に対して、{返されたイテレータを繋げたイテレータ}を返す。
Rust 1.29 で安定化された。

挙動としては .flat_map(|iter| iter) と同等だが、よくある用法の割には恒等関数をいちいち与えるのが微妙なので追加された(のかもしれない。知らんけど。経緯はこの辺りを読めば書いてありそう。)

flattenとflat_mapの挙動
fn main() {
    let iter = vec![vec![0, 1, 2], vec![3, 4]].into_iter();
    assert_eq!(
        iter.clone()
            .flatten()
            .collect::<Vec<_>>(),
        iter.clone()
            .flat_map(|x| x)
            .collect::<Vec<_>>(),
    );
}

リファレンスにも書いてあるが、 flatten はネストしたイテレータを1層しか剥がさない。
迷ったら .flat_map(|x| x) の挙動を想像すればいい。

map_while(): Iterator<T> -> (T -> Option<U>) -> Iterator<U>

イテレータに対して{要素を受け取って何か Option を返す関数}を適用し、関数が Some を返している間だけ要素を吐き出すようなイテレータにする。

雑に言えば「条件を満たしている間だけ加工したいが、駄目なのが来たら残りは捨てる」という場合にこれが使える。

雰囲気としては名前通り maptake_while を組み合わせたものだと思っても良いしドキュメントでもそう説明されているが、個人的にはどちらかというと「filter_map の兄弟」くらいの気持ちで見ている。

map_whileのそれらしい利用例と、mapとtake_whileで書いたバージョン
let command_output = vec![
    "42",
    "314",
    "141421356",
    // この行以降は数字であっても無視したい
    "Error: Invalid format at line 4",
    "0",
];

let by_map_while: Vec<_> = command_output
    .iter()
    // 行を受け取って数字に変換し、成功したら `Some(_)` を、失敗したら `None` を返す関数を渡す。
    .map_while(|line| line.parse::<i32>().ok())
    .collect();
assert_eq!(by_map_while, [42, 314, 141421356]);

let by_map_and_take_while: Vec<_> = command_output
    .iter()
    // 行を受け取って数字に変換し、成功したら `Some(_)`、失敗したら `None` にする。
    .map(|line| line.parse::<i32>().ok())
    .take_while(|parsed| parsed.is_some())
    .map(|succeeded| succeeded.expect("Some であることが確認済"))
    .collect();
assert_eq!(by_map_and_take_while, [42, 314, 141421356]);

(Playground)

型だけを見ると filter_map とほぼ同じなのだが、使うべき場所は明確に異なるので迷うことはない。
条件に合わない要素を無視して続行するなら filter_mapを使おう。
条件に合わない要素で走査を中断するなら map_while
を使おう。

filter_mapとmap_whileの端的で本質的な違い
let input = [
    "0",         // good
    "42",        // good
    "oops",      // bad
    "314",       // good again
    "141421356", // also good
];

// filter_map: 駄目なのは無視して続行
let filter_map: Vec<_> = input.iter().filter_map(|s| s.parse::<i32>().ok()).collect();
assert_eq!(
    filter_map,
    [0, 42, 314, 141421356],
    "駄目なものだけが無視されている"
);

// map_while: 駄目なのが来たら残りは捨てる
let map_while: Vec<_> = input.iter().map_while(|s| s.parse::<i32>().ok()).collect();
assert_eq!(map_while, [0, 42], "oops に遭遇した時点で走査が中断された");

(Playground)

イテレータの全要素を活用して、別物を作り出す系

直観的には、reduceとかfoldみたいなイメージだ。
型は様々で、 Iterator<T> -> Tに関係ある型 くらいのことしか言えない。

collect(): Iterator<T> -> Tのコレクション

名前の通り。
(コレクションというのは、C++で言うところのコンテナである。)
into_iter() の逆みたいなやつだ。

とはいえ、実際のところRustでコレクションとして使える型は実に様々で、 VecString は勿論、 Result なんかもいける。
特に collect::<Result<Vec<_>, _> の使い方などなかなか面白いので、是非公式ドキュメントの例などを確認してほしい。

partition(): Iterator<T> -> (&T -> bool) -> (Tのコレクション, Tのコレクション)

与えられた述語(&T -> bool)が true を返す要素と false を返す要素を分類し、それらを別々のコレクションに格納してタプルで返す関数である。
(念のため書いておくが、タプルの最初のコレクションが true だった要素、次の(最後の)コレクションが false だった要素である。)

fold(): Iterator<T> -> B -> (B -> T -> B) -> B

みんな大好きfold。
ここで説明するまでもない。 類似する reduce() が加わったので区別のため説明しよう。

ネストした関数呼び出しを木構造で表現することを考えてみよう。

f(a, b):

  f
 / \
a   b
h(f(a, b), g(c, d)):

     h
    /  \
   /    \
  f      g
 / \    / \
a   b  c   d

このとき、 a, b, c, d, e を吐くイテレータ iter について `iter.fold(init, f) を実行した結果は次のような木となる。

iter.fold(init, f) = f(f(f(f(f(init, a), b), c), d), e):
            f
           / \
          f   e
         / \
        f   d
       / \
      f   c
     / \
    f   b
   / \
init  a

この一方的に左に伸びた木をパタパタと末端の f(init, a) から計算していき最終的にひとつの値を得る。
この過程を木を「折り畳む」と表現するのが fold という演算である。

ひとつ具体例を出すと、 1+2+3+4 は fold で表現できる。

vec![2, 3, 4].fold(1, |x, y| x + y) = ((1+2)+3)+4:

      +
     / \
    +   4
   / \
  +   3
 / \
1   2

(3+3)+4:

    +
   / \
  +   4
 / \
3   3

6+4:

  +
 / \
6   4

10:

10

図や名前でピンとこなかった人は素直に標準ライブラリ (Rust 1.53.0) での実装を読むのが良いだろう。

stdでの実装
    fn fold<B, F>(mut self, init: B, mut f: F) -> B
    where
        Self: Sized,
        F: FnMut(B, Self::Item) -> B,
    {
        let mut accum = init;
        while let Some(x) = self.next() {
            accum = f(accum, x);
        }
        accum
    }

try_fold(): Iterator<T> -> B -> (B -> T -> Try<B=Ok>) -> Try<B=Ok>

失敗するかもしれない関数について fold 的な操作を行う関数。
Rust-1.27 で安定化された

Try というのは ? 演算子でシュッと値を抜き出したり脱出できたりするような型について実装される trait で、 rust 1.27 時点では ResultOption に実装されている。
(公式リファレンスの例では、 Option が使われている。)

try_fold は「結果が失敗かもしれない」場合に使えるので、「 Result を吐き出すようなイテレータを弄って Result を得る」場合にも使えるし、「イテレータ自体はエラーを吐かないが、集約が失敗するかもしれない」場合にも使える。

イテレータが失敗を吐くかもしれない場合
let vec = vec![Ok(1), Ok(2), Err("oops"), Ok(4), Err("Oh my buddha")];
let sum_res = vec.into_iter()
    .try_fold(
        0,
        |sum, item| item.map(|v| sum + v)
    );
assert_eq!(sum_res, Err("oops"));

(playground)

集約が失敗するかもしれない例(公式ドキュメントより)
let a = [1, 2, 3];

// the checked sum of all of the elements of the array
let sum = a.iter().try_fold(0i8, |acc, &x| acc.checked_add(x));

assert_eq!(sum, Some(6));

(playground)

try_fold では ErrNone に遭遇した場合にそれ以上イテレータの要素を辿ることがない(ループを脱出したのと同じような効果になる)のが特徴的である。
今までででも foldResult::and_then 等を駆使して似たようなコードを書くことはできたが、そういった場合は途中でイテレーションを中断できず、イテレータを最後まで消費してしまっていた。
(take_while などを更に組み合わせれば完全な再現は可能だが……)
try_fold はループや fold & and_then よりもシンプルになるので、積極的に使っていきたいところである。

reduce(): Iterator<T> -> (T -> T) -> Option<T>

fold() にとてもよく似ているが、 fold は状態の初期値を引数で与えるのに対して reduce は状態の初期値としてイテレータが最初に吐く要素が使われる。
そのため、状態と出力は必然的にイテレータが吐く値の型を使ったものになる。
イテレータが何も吐かなかった場合、そもそも初期状態を設定できないので None を返す。

Rust 1.53.0 における reduce() の実装 はまさに fold() をそのまま使ったものになっており、何が欲しかったのかわかりやすい。

stdにおける実装
    fn reduce<F>(mut self, f: F) -> Option<Self::Item>
    where
        Self: Sized,
        F: FnMut(Self::Item, Self::Item) -> Self::Item,
    {
        let first = self.next()?;
        Some(self.fold(first, f))
    }

unzip(): Iterator<(T, U)> -> (Tのコレクション, Uのコレクション)

ペアを列挙するイテレータを、ペアの中の一方の要素だけを集めた2つのコレクションに分解する関数。
要するに、 zip() の逆みたいなことをする。

公式の例を見ればすぐに理解できるだろう。

sum(): Iterator<T> -> Tの和の型

イテレータの全ての(正確には最初にNoneが返るまでの)要素の和を返す。
これはfold()で実現可能に見えるし実際その通りだが、こちらの方が若干楽ができたりする。

リファレンスにはfn sum<S>(self) -> S where S: Sum<Self::Item>とあるが、ここでSとは「対象のイテレータの要素の型を集めて作ることのできる、目的の値の型」である(たとえばIterator<i32>に対してi32、などである)。
fold()は二項演算(F: FnMut(B, Self::Item) -> B)を繰り返して最終的な結果を求めるが、このsum()ではイテレータによって要素を一気に受け取って結果を返す。つまり、たとえば内部的には二項演算でなく複数要素を一気に処理したり、といった実装も可能なのである。

参考: collect()

sum()collect()は、用途が違うだけで根本的には同じものである。
型を見てみよう。

fn sum<S>(self) -> S where S: Sum<Self::Item>;
fn collect<B>(self) -> B where B: FromIterator<Self::Item>;

名前と戻り値のtraitしか違わないことがわかる。
collect()で使われる::std::iter::FromIteratorは、イテレータから作れるオブジェクト(典型的にはVecHashMap等のコンテナ)に対して定義するものであるが、::std::iter::Sumはその中でも特に、イテレータから計算できるに対して定義するものである。
このように用途で分けることによって、たとえばIterator<i32>からVec<i32>を作りたい場合とi32の和を求めたい場合で同じ書き方で別の動作をさせることができるようになるのである。

Rust-1.16.0 から、 std::iter::Sumstd::iter::ProductResult に対しても実装されたため、 collect()Result を作るときのような(すなわち、 Ok な値の和や積を求めて Ok で返そうとし、もし Err があれば和や積でなくそのエラー値を返すような)コードを書けるようになった

// ぜんぶ `Ok` なので、結果は `Ok` の和
let arr1: &[Result<u32, &str>] = &[Ok(1), Ok(2), Ok(3)];
assert_eq!(arr1.iter().cloned().sum::<Result<_, _>>(), Ok(6));

// `Err` があったので和は計算できない、 `Err` が返る
let arr2: &[Result<u32, &str>] = &[Ok(1), Err("ouch"), Ok(3)];
assert_eq!(arr2.iter().cloned().sum::<Result<u32, _>>(), Err("ouch"));

(Playground: https://play.rust-lang.org/?gist=41791f3168dcaf76faa8ae71c34d05e2&version=stable&backtrace=0)

product(): Iterator<T> -> Tの積の型

sum()の積バージョン。詳細は(traitと意味を除いて)sum()と同じ。

for_each(): Iterator<T> -> (T -> ()) -> ()

プログラマおなじみ(?)の for each である。
Rust-1.21.0 で追加された
for_each() でできることは for 文でできることなので、特になにか新しいことができるわけでもなく、何かが非常に楽になるというものではない。
しかも、 for と違って for_each() を使うと、 break; や continue; 相当の処理は記述できない。

for_each() が有用なのは、 for 〜 in の後ろに書くには長すぎる式などを綺麗に書いて処理したい場合などである。

forの例
for v in iter.foo(1).bar(|v| 2).baz(3).qux(4) {
    poyo(v);
}
for_eachの例
iter
    .foo(1)
    .bar(|v| 2)
    .baz(3)
    .qux(4)
    .for_each(poyo);

リファレンスアナウンスでは、 range (0..5 とか)等を組み合わせて使う方法や、実際に flat_mapfilter などを使ってイテレータの式が長くなった場合の例などが書いてあるので、読んでみると良いかもしれない。

この関数は何か有意義な値を返すものではなく、関数 (T -> ()) の副作用を目的として利用されるものであるが、うまく分類できなかったので便宜上「別物を作り出す系」に置くことにした。

try_for_each(): Iterator<T> -> (T -> Try<B=()>) -> Try<B=()>

失敗するかもしれない関数について for_each 的な操作を行う関数。
Rust-1.27 で安定化された

これといって特記すべきこともないが、今まで for で書いていたものがシンプルにできる、ありがたい関数である。
もし成功時にも何らかの意味ある値を返したいのであれば、 try_fold を使うべきである。

辞書順比較系

ふたつのイテレータを辞書順比較する。

cmp(): Iterator<T> -> IntoIterator<T> -> Ordering

辞書順比較し、その結果(::std::cmp::Ordering::Less とか)を返す。

例のごとく IntoIterator<T> を受け取るので、イテレータを明示的に作って渡さずとも比較に使うことができる。

partial_cmp(): Iterator<T> -> IntoIterator<T> -> Option<Ordering>

辞書順比較し、その結果(::std::cmp::Ordering::Less とか)を Some で返す。
None が返るのは、比較できない値を比較した場合である。

参考

詳細はPartialOrdでも読んだ方が良いのだが、簡単に説明しておこう。

PartialOrd という「半順序」が定義されていることを表現するtraitがあって、こいつを使った比較(PartialOrd::partial_cmp())は Option<Ordering> を返す。
具体的な例として有名なのが f64 等で、 !(NaN >= 0) かつ !(Nan < 0) であるから、全順序(いわゆる普通の大小関係)にはできないわけである。そういう場合には、比較の結果として None が返る。

PartialOrd に似たものとしてPartialEqがあるので、併せて見るのが良いだろう。
こちらはもう少しわかりやすい。要するに「同値関係の、反射律の成り立たない版」だからだ。
反射律の破れる例は、 NaN != NaN みたいなやつである。

eq(), ne(), lt(), le(), gt(), ge(): Iterator<T> -> IntoIterator<T> -> bool

名前の通り。
辞書順比較し、関数名の示す関係が成り立つなら true を、それ以外なら false を返す。


まとめ

  • ループのネストが深いときは、中身の何かを剥がす系のメソッドを探そう
  • 「剥がしてまた包む」ような冗長に見える処理があるときは、剥がした中身に作用するメソッドを探そう
  • イテレータの代わりに、「イテレータにできるもの(IntoIterator)」を渡せるメソッドもあるので、うまく使おう
  • OptionResult 等もイテレータにできることを憶えておこう
  • 関数を受け取るメソッドは、渡される関数が Self::Item を受け取るか &Self::item を受け取るかで挙動が推測できるゾ
    • Self::Item は変換系か、イテレータを返さないメソッド
    • &Self::Item はフィルタ系で、かつイテレータを返すメソッド
  • それはさておき、公式ドキュメントを読もう!

See also

490
341
9

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
490
341