Rustのイテレータ入門:map・filter・collectの使い方

スポンサーリンク

Rustのイテレータ入門:map・filter・collectの使い方

はじめに

Rustのイテレータは Vec や配列などのコレクションを要素ごとに処理するための仕組みです。mapfiltercollect を組み合わせることで、ループを使わず宣言的にデータ変換ができます。


イテレータを作る

コレクションに .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}(順序は不定)

チェーン(組み合わせる)

mapfilter は自由に繋げられます。

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・?演算子の使い方」を参照してください。