コンテンツにスキップ

Rustのイテレータ

イテレータ(iterator)とは、値を1つずつ順番に取り出せるもののことです。じつは for ループのページから、名前を知らないまま使ってきました。コレクションを for ループで回すとき、裏ではいつもイテレータが動いているのです。

fn main() {
let numbers = vec![1, 2, 3];
let mut iter = numbers.iter();
println!("{:?}", iter.next()); // Some(1)
println!("{:?}", iter.next()); // Some(2)
println!("{:?}", iter.next()); // Some(3)
println!("{:?}", iter.next()); // None
}

実行結果:

Some(1)
Some(2)
Some(3)
None
▶ 実際に試してみよう

.next() は Option を返します。要素があるあいだは Some(値)、なくなったら None です。for ループの正体は、None が返ってくるまで裏で .next() を何度も呼び出しているだけなのです。

.map() はクロージャを受け取り、それをすべての要素に適用して、新しいイテレータを作ります。

fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().map(|n| n * 2).collect();
println!("{:?}", doubled);
}

実行結果:

[2, 4, 6, 8, 10]
▶ 実際に試してみよう

.collect() は、イテレータを具体的なコレクション(ここでは Vec<i32>)に戻すメソッドです。これがないと、まだ何も計算されていないイテレータが置いてあるだけになってしまいます。

fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
let evens: Vec<&i32> = numbers.iter().filter(|n| **n % 2 == 0).collect();
println!("{:?}", evens);
}

実行結果:

[2, 4, 6]
▶ 実際に試してみよう

**n と * が2つ並んでいるのは見慣れないかもしれません。.filter() はクロージャに「参照の参照」(&&i32)を渡すので、ふつうの数と比べるには2回参照外しをする必要があるのです。よくある小さなクセなので、今はくわしい理由まで気にしなくて大丈夫です。

ここからが、イテレータのメソッドをつなげる(チェーンする)ことの本領です。いくつもの手順を、読みやすいひとつの流れにまとめられます。

fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
let result: Vec<i32> = numbers
.iter()
.filter(|n| **n % 2 == 0) // 偶数だけを残す
.map(|n| n * 10) // それぞれを10倍する
.collect();
println!("{:?}", result);
}

実行結果:

[20, 40, 60]
▶ 実際に試してみよう

同じ処理を、for ループと空の Vec への push で書いた場合と比べてみてください。それも間違いではありません。でもチェーンで書いたほうは「絞り込んで、10倍して、集める」という、やりたい手順そのままの順番で読めます。

fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let total: i32 = numbers.iter().sum();
let count = numbers.iter().count();
let has_even = numbers.iter().any(|n| n % 2 == 0);
println!("合計: {total}、個数: {count}、偶数を含む: {has_even}");
}

実行結果:

合計: 15、個数: 5、偶数を含む: true
▶ 実際に試してみよう

.sum() はすべての要素の合計、.count() は要素の数を返します。.any() は、条件を満たす要素が1つでもあれば true を返します。これらは、どのイテレータにもあるメソッドのほんの一部です。ほかにも enumerate、zip、take、rev など、たくさんあります。独自のイテレータのページでは自分でイテレータを作り、これらのメソッドがすべて無料でついてくることを確かめます。

1から10までの数から奇数だけを残し、それぞれを2乗して、全部足し合わせてください。mut の変数は使わずに、一つのイテレータのチェーンで書きます。ヒント:.filter()、.map()、.sum() をつなげて、結果には let total: i32 のように型をつけます。

解答を見る
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let total: i32 = numbers
.iter()
.filter(|n| **n % 2 == 1) // 1, 3, 5, 7, 9 を残す
.map(|n| n * n) // 1, 9, 25, 49, 81
.sum();
println!("奇数の2乗の合計: {total}");
}

実行結果:

奇数の2乗の合計: 165
▶ 実際に試してみよう

total に : i32 が必要なのは、.sum() がいくつもの数値型に合計できるので、どれがほしいのかRustには推測できないからです。

vec!["the", "quick", "brown", "fox", "jumps"] から始めます。3文字より長い単語だけを大文字にした新しい Vec<String> を作り、{:?} で表示してください。(文字数で選び、大文字に変換する問題なので、単語は英語のままにしています。日本語の文字は1文字が3バイトで、大文字もありません。)ヒント:.filter(|w| w.len() > 3) で長い単語を残し、.to_uppercase() で &str を大文字の String に変換します。

解答を見る
fn main() {
let words = vec!["the", "quick", "brown", "fox", "jumps"];
let loud: Vec<String> = words
.iter()
.filter(|w| w.len() > 3)
.map(|w| w.to_uppercase())
.collect();
println!("{:?}", loud);
}

実行結果:

["QUICK", "BROWN", "JUMPS"]
▶ 実際に試してみよう

words 自体は変わりません。.iter() は借用するだけなので、チェーンのあとでも words を使えます。

  • イテレータは .next() で値を1つずつ取り出します。.next() は Option を返します。
  • for ループの裏ではイテレータが動いています。
  • .map() は各要素を変換し、.filter() は条件に合う要素だけを残し、.collect() は結果を具体的なコレクションに戻します。
  • イテレータのメソッドをチェーンするほうが、可変の変数に結果をためていく手書きのループより、たいていわかりやすくなります。

理解度チェック

1. イテレータの .next() は何を返しますか?

2. .iter().map(...).collect() のようなチェーンの最後にある .collect() は何をしますか?

3. .filter(|n| **n % 2 == 0) で、n を2回参照外ししているのはなぜですか?

4. for ループでコレクションを回すとき、裏で動いているのは何ですか?

5. .filter() の結果のイテレータに残るのはどの要素ですか?

スコア: 0 / 5