Rustのイテレータ入門:map・filter・collectの使い方
はじめに
Rustのイテレータは Vec や配列などのコレクションを要素ごとに処理するための仕組みです。map・filter・collect を組み合わせることで、ループを使わず宣言的にデータ変換ができます。
イテレータを作る
コレクションに .iter() を呼ぶとイテレータが得られます。
let v = vec![1, 2, 3, 4, 5]; let iter = v.iter(); // &i32 を返すイテレータ
| メソッド | 返す型 | 使い方 |
|---|---|---|
.iter() |
&T(参照) |
元のVecをそのまま使い続けたい |
.iter_mut() |
&mut T(可変参照) |
要素を変更したい |
.into_iter() |
T(所有権移動) |
元のVecが不要になる |
map(各要素を変換する)
全要素に関数を適用して新しいイテレータを作ります。
let v = vec![1, 2, 3, 4, 5]; // 各要素を2倍にする let doubled: Vec<i32> = v.iter().map(|x| x * 2).collect(); println!("{:?}", doubled); // [2, 4, 6, 8, 10] // 文字列に変換する let words = vec!["hello", "world"]; let upper: Vec<String> = words.iter().map(|s| s.to_uppercase()).collect(); println!("{:?}", upper); // ["HELLO", "WORLD"]
filter(条件に合う要素だけ残す)
条件を満たす要素だけを残したイテレータを作ります。
let v = vec![1, 2, 3, 4, 5, 6]; // 偶数だけ残す let evens: Vec<&i32> = v.iter().filter(|x| *x % 2 == 0).collect(); println!("{:?}", evens); // [2, 4, 6] // 文字列の長さで絞る let words = vec!["cat", "elephant", "dog", "rhinoceros"]; let long: Vec<&&str> = words.iter().filter(|s| s.len() > 4).collect(); println!("{:?}", long); // ["elephant", "rhinoceros"]
iter() は参照を返すため、filter の引数は &&T になります。into_iter() を使うと &T になってすっきりします。
let v = vec![1, 2, 3, 4, 5, 6]; let evens: Vec<i32> = v.into_iter().filter(|x| x % 2 == 0).collect(); println!("{:?}", evens); // [2, 4, 6]
collect(イテレータをコレクションに変換する)
イテレータを Vec などのコレクションに変換します。型注釈が必要です。
let v = vec![1, 2, 3]; // Vec に集める let collected: Vec<i32> = v.into_iter().collect(); // String に集める let chars = vec!['R', 'u', 's', 't']; let s: String = chars.into_iter().collect(); println!("{}", s); // Rust // HashSet に集める(重複を除去) use std::collections::HashSet; let v = vec![1, 2, 2, 3, 3, 3]; let set: HashSet<i32> = v.into_iter().collect(); println!("{:?}", set); // {1, 2, 3}(順序は不定)
チェーン(組み合わせる)
map・filter は自由に繋げられます。
let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // 偶数だけ取り出して2乗する let result: Vec<i32> = v.iter() .filter(|&&x| x % 2 == 0) .map(|&x| x * x) .collect(); println!("{:?}", result); // [4, 16, 36, 64, 100]
よく使うイテレータメソッド
fold(畳み込み)
初期値から始めて全要素を一つの値にまとめます。
let v = vec![1, 2, 3, 4, 5]; let sum = v.iter().fold(0, |acc, x| acc + x); println!("{}", sum); // 15 let product = v.iter().fold(1, |acc, x| acc * x); println!("{}", product); // 120
sum・product(合計・積)
let v = vec![1, 2, 3, 4, 5]; let sum: i32 = v.iter().sum(); println!("{}", sum); // 15 let product: i32 = v.iter().product(); println!("{}", product); // 120
count(件数)
let v = vec![1, 2, 3, 4, 5]; let count = v.iter().filter(|&&x| x > 3).count(); println!("{}", count); // 2
any・all(条件チェック)
let v = vec![1, 2, 3, 4, 5]; let has_even = v.iter().any(|x| x % 2 == 0); println!("{}", has_even); // true let all_positive = v.iter().all(|x| *x > 0); println!("{}", all_positive); // true
find(最初に条件を満たす要素)
let v = vec![1, 2, 3, 4, 5]; let first_even = v.iter().find(|&&x| x % 2 == 0); println!("{:?}", first_even); // Some(2)
enumerate(インデックス付き)
let v = vec!["apple", "banana", "orange"]; for (i, item) in v.iter().enumerate() { println!("{}: {}", i, item); } // 0: apple // 1: banana // 2: orange
zip(2つのイテレータを組み合わせる)
let names = vec!["田中", "鈴木", "佐藤"]; let scores = vec![90, 75, 85]; let combined: Vec<(&&str, &i32)> = names.iter().zip(scores.iter()).collect(); for (name, score) in &combined { println!("{}: {}", name, score); } // 田中: 90 // 鈴木: 75 // 佐藤: 85
flat_map(変換 + 平坦化)
let words = vec!["hello world", "foo bar"]; let chars: Vec<&str> = words.iter() .flat_map(|s| s.split_whitespace()) .collect(); println!("{:?}", chars); // ["hello", "world", "foo", "bar"]
take・skip(件数の制限)
let v = vec![1, 2, 3, 4, 5]; let first_three: Vec<&i32> = v.iter().take(3).collect(); println!("{:?}", first_three); // [1, 2, 3] let skip_two: Vec<&i32> = v.iter().skip(2).collect(); println!("{:?}", skip_two); // [3, 4, 5]
for ループとの比較
イテレータが使える場面では、for ループより宣言的に書けます。
let v = vec![1, 2, 3, 4, 5, 6]; // for ループ let mut result = Vec::new(); for x in &v { if x % 2 == 0 { result.push(x * x); } } // イテレータ(同じ意味) let result: Vec<i32> = v.iter() .filter(|&&x| x % 2 == 0) .map(|&x| x * x) .collect();
まとめ
| メソッド | 説明 |
|---|---|
map(f) |
各要素を変換する |
filter(f) |
条件を満たす要素だけ残す |
collect() |
イテレータをコレクションに変換する |
fold(init, f) |
全要素を1つの値に畳み込む |
sum() / product() |
合計・積を求める |
count() |
件数を数える |
any(f) / all(f) |
条件チェック |
find(f) |
最初に条件を満たす要素を返す |
enumerate() |
インデックス付きで処理する |
zip(iter) |
2つのイテレータを組み合わせる |
flat_map(f) |
変換して平坦化する |
take(n) / skip(n) |
件数を制限・スキップする |
RustのVec初期化については「RustのVec初期化まとめ:vec!マクロからVec::newまで」を参照してください。
RustのHashMapの使い方は「RustのHashMap入門:基本操作から頻出パターンまとめ」を参照してください。
Rustのファイル読み書きは「Rustのファイル読み書き入門:fs・BufReader・BufWriterの使い方」を参照してください。
Rustのエラーハンドリング(Result・Option・?)は「Rustのエラーハンドリング入門:Result・Option・?演算子の使い方」を参照してください。