本文へスキップ
BecomeCoder

Rustコース · 第11章 Rustらしい書き方 ― クロージャとイテレータ · レッスン44

イテレータアダプタ ― map・filter・collect

ブラウザで完結

導入

「Vecの各要素を2倍にする」「条件に合う要素だけ残す」——こういう処理を毎回 for ループと空の Vec を用意して書くのは冗長です。Rust では、こうした変換を鎖のようにつなげる書き方が用意されています。前のレッスンで学んだクロージャが、ここで本領を発揮します。

説明

Vec や範囲(1..=5 など)はイテレータとして扱え、mapfiltercollect といったアダプタをつなげて処理を組み立てられます。

fn main() {
    let squares: Vec<i32> = (1..=5).map(|x| x * x).collect();
    println!("{:?}", squares);

    let evens: Vec<i32> = squares.into_iter().filter(|x| x % 2 == 0).collect();
    println!("{:?}", evens);
}
  • .map(|x| x * x) … 各要素にクロージャを適用し、変換した新しい要素の列を作ります。
  • .filter(|x| x % 2 == 0) … クロージャが true を返す要素だけを残します。
  • .collect() … 変換し終わった要素を、指定した型(ここでは Vec<i32>)にまとめます。collect はどんな型に集めるか自分では決められないため、let squares: Vec<i32> = ... のように受け取り側に型注釈を書いて教えます(::<Vec<i32>>() のような書き方をせず、こちらの形が読みやすくて実務でもよく使われます)。

ここで大事なのは、mapfilter呼んだ瞬間には何も実行されないということです。これを遅延評価と呼びます。collectsumfor_each のように「結果を取り出す(消費する)」メソッドを最後に呼んだときに、初めてパイプライン全体が実行されます。無駄な中間 Vec を作らず、必要な分だけ計算が進む——これも Rust の「ゼロコスト抽象化」の一例です。

他にもよく使うアダプタがあります。

fn main() {
    let v = vec![1, 2, 3, 4, 5];

    // sum: 合計。fold: 好きな初期値・演算で畳み込む
    let total: i32 = v.iter().sum();
    let product = v.iter().fold(1, |acc, x| acc * x);
    println!("{} {}", total, product);

    // enumerate: 添字付きで、for_each: 消費しながら1つずつ処理
    let fruits = vec!["apple", "banana", "cherry"];
    fruits.iter().enumerate().for_each(|(i, name)| {
        println!("{}: {}", i, name);
    });
}
  • sum() … 合計を計算して1つの値にまとめます(結果を代入する変数の型注釈が必要です)。
  • fold(初期値, |acc, x| ...)acc(これまでの累積値)と x(今の要素)から次の acc を作る、というのを繰り返します。sum の一般化版です。
  • enumerate()(添字, 値) のペアに変換します。
  • for_each(|x| ...) … 各要素に対して処理を実行するだけで、値は集めません(for ループのメソッド版)。

このほかにも find(条件に合う最初の要素)、any/all(条件を満たす要素が1つでもある/すべて満たす)、zip(2つを組にする)、rev(逆順にする)、take/skip(先頭を取る/飛ばす)、count(個数を数える)などがあり、組み合わせて使います。

やってみよう

下のエディタで map の中の式や filter の条件を書き換えて、パイプラインの結果がどう変わるか確かめてみましょう。sumfold も試してみてください。

演習

1..=10 の範囲から、filter で偶数だけを取り出し、map でそれぞれを2倍にし、最後に sum で合計を求めて println! で出力してください。

ヒント1を見る

(1..=10).filter(|x| x % 2 == 0) で偶数(2, 4, 6, 8, 10)だけが残ります。

ヒント2を見る

続けて .map(|x| x * 2) で2倍にし、let total: i32 = ....sum(); の形で合計を受け取ってから println!("{}", total); します。

実際に動かしてみよう

下のエディタにRustを書いて「実行」を押すと、学習用シミュレータが println! の出力を表示します(本物のrustcではなく、教材の範囲を再現した軽量エンジンです)。struct・enum・match・トレイト・Vec/String/HashMap・Option/Result なども動きます。本文の例を書き換えて動かしてみましょう(所有権・借用チェッカーのコンパイルエラーは再現しないため、動きの確認用として使ってください)。

Rust — ブラウザ内で実行(学習用シミュレータ)

Rustの教材サブセットを動かす学習用シミュレータを読み込みます(本物のrustcではなく、動きを再現した軽量な自作エンジンです)。
スクロールして表示された時点でも自動で読み込まれます。