LoginSignup
9
5

More than 1 year has passed since last update.

メソッドチェーンってなに

メソッドチェーンとは、その名前の通り、メソッドをつなげたものです。
例えば次のようなものです。

let arr = array.method1().method2().method3();

実際は以下のような使われ方をします。

use itertools::Itertools;

fn main() {
    let arr = vec![0, 0, 0, 0, 0]
        .iter()
        .enumerate()
        .map(|(idx, _)| idx + 1)
        .filter(|v| *v > 2)
        .sorted()
        .collect::<Vec<_>>()
    ;

    println!("{:?}", arr); // -> [3, 4, 5]
}

何をしているかわからなくて抵抗感があるかもしれませんが、慣れるととても書きやすい書き方です。
ここでは、そんなメソッドチェーンを書けるようになることを目指します。

Rust ではイテレータを利用すると、自然とメソッドチェーンになります。また、他の言語でメソッドチェーンが使われる場面もRust のイテレータと似た書き方になります。そのため、メソッドチェーンの説明としてイテレータを紹介します。

実際のメソッド

よく使うメソッドをいくつか挙げます。最低限これらを知っていればなんとかなるまであります。
要素数と要素の形(型)に注目すると少しわかりやすくなるので、要素数と要素の形についても書きますね。

基本的に Rust について書きますが、多くの言語にも似たものが実装されているので、気になる方は調べてみてください。

iter()collect()

イテレータで使うメソッドの中でも特に重要なのが iter()collect() です。これらはイテレータを書くたびに現れます。

簡単に書くと「iter() で開始して collect() で終了」という使われ方をします。

イテレータの処理は高速化などの理由から、専用の中間表現を使います。この中間表現への変換が iter() です。また、処理が終わったイテレータを中間表現から元に戻すのが collect() です。

(※言語によっては中間表現がないものもあって、そういう場合は iter()collect() に相当するものもありません)

それぞれもうちょっと詳しく書きますね。

iter()

iter() は要素に対するイテレータ(反復子)を生成しています。上に書いた中間表現のことです。これ単体ではなにも起こりません。

具体的には、処理中は IterIntoIter のような型になっています。
image.png
iter() のほかに into_iter()iter_mut() がありますが、その違いはここでは触れません。

collect()

collect() は中間表現としてのイテレータを コレクション1に変換します。コレクション以外の、数値などを返す場合は collect() は不要です。

collect::<Vec<i32>>()collect::<String>() などのように、型を指定して使います。
itertools::Itertools トレイトcollect_vec()collect_tuple() も便利です。

map()

要素数は変わらずに各要素を自由に変えます。
すべての要素に同じ処理がされます。

.map(|val| {
    let val = val * 3;
    (val + 4) > 10
})

// [1, 2, 3] -> [false, false, true]
  • 数値を受け取って3倍にするとか、
  • 文字列を受け取って先頭の3文字だけ返すとか、
  • 数値を受け取って10以上なら true, 10未満なら false を返すとか、

などができます。2文以上の式を使うこともよくあります(長くなりすぎて読みにくくなることもよくあります、、)。

Rust の map() では、返す型はすべての要素で一致させる必要があります。つまり、数値を受け取って 10以上なら true, 10未満なら "low" を返す といったことはできません。
他の言語では、型が一致しなくても大丈夫なこともあります。

filter()

要素数が変わって、各要素はそのままです。
boolean を返す式を書いて、 true になる要素を抽出します。

.filter(|&val| val % 2 == 1)

// [1, 2, 3] -> [1, 3]
  • 数値を受け取って、奇数を選ぶとか、
  • 文字列を受け取って、3文字以上のものだけを選ぶとか、
  • 数値のタプルを受け取って、和が10以上のものを選ぶとか、

などができます。

enumerate()

要素数は変わらず、各要素がタプルに変換されます。
各要素について、 (前から何番目か, 元の要素) というタプルになります。

.enumerate()

// [1, 2, 3] -> [(0, 1), (1, 2), (2, 3)]

sum()

要素数が 1 になって、数値を返します。
各要素の和を返します。

.sum()

// [1, 2, 3] -> 6

このメソッドを使う場合は collect() が不要になります。

数値を扱う型のみに使えます。気になる方は以下をご覧ください。

sorted()

要素数は変わらず、各要素も変わりません。
要素をソートします。

.sorted()

// [3, 1, 2] -> [1, 2, 3]

このメソッドを使うには itertools::Itertools トレイト が必要です。

各要素が比較可能なときに使えます。降順ソートや、自作メソッドによる特殊なソートをする場合は sorted() の前後に map() などを使うと実現できます。

fold()

要素数が変わって、各要素も変わります。
返すものを指定できる、自由度の高いメソッドです。

.fold(0, |acc, val| acc + val)
// [1, 2, 3] -> 6 (sum に相当)

.fold(Vec::<(usize, i32)>::new(), |mut acc, &val| {
    acc.push((acc.len(), val));
    acc
})
// [1, 2, 3] -> [(0, 1), (1, 2), (2, 3)] (enumerate に相当)

.fold(Vec::<i32>::new(), |mut acc, &val| {
    acc.push(val);
    acc.push(val);
    acc
})
// [1, 2, 3] -> [1, 1, 2, 2, 3, 3] (要素数を2倍にする)

他の言語では reduce と呼ばれることもあります (Rust の reduce() は別のメソッドです)。

map()filter()enumerate() も、他のメソッドも、これを使えば実現できます。
自由な反面、可読性が犠牲になるので、特別な理由がなければ map()filter() などを使いたいです。

読んでみる

一通り紹介したところで、冒頭に書いた以下のプログラムを読んでみましょう。

use itertools::Itertools;

fn main() {
    let arr = vec![0, 0, 0, 0, 0]
        .iter()
        .enumerate()
        .map(|(idx, _)| idx + 1)
        .filter(|&v| v > 2)
        .sorted()
        .collect::<Vec<_>>()
    ;

    println!("{:?}", arr); // -> [3, 4, 5]
}

コメントをつけます

use itertools::Itertools;

fn main() {
    let arr = vec![0, 0, 0, 0, 0]
        .iter()                  // イテレータに変換する
        .enumerate()             // (先頭から何番目か, 元の要素) のタプルに変換する
        .map(|(idx, _)| idx + 1) // 「先頭から何番目か」+ 1 を得る。元の要素は破棄される。
        .filter(|&v| v > 2)      // 2より大きいものを抽出する
        .sorted()                // ソートする
        .collect::<Vec<_>>()     // Vec に変換する
    ;

    println!("{:?}", arr); // -> [3, 4, 5]
}

値を追います

use itertools::Itertools;

fn main() {
    let arr = vec![0, 0, 0, 0, 0]
        .iter()                  // [0, 0, 0, 0, 0]
        .enumerate()             // [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0)]
        .map(|(idx, _)| idx + 1) // [1, 2, 3, 4, 5]
        .filter(|&v| v > 2)      // [3, 4, 5]
        .sorted()                // [3, 4, 5]
        .collect::<Vec<_>>()     // [3, 4, 5]
    ;

    println!("{:?}", arr); // -> [3, 4, 5]
}

書いてみる

少しずつでも読めるようになりましたら、次は書いてみましょう。実際に書くと理解度がとてもあがります。

  • 配列にある偶数の個数を数えるとか、
  • 配列をソートした後、ソート前の位置を示すとか、

などをしてみるといいと思います。

おまけで、(宣伝も入っているのですが、)私は AtCoder が好きなので、イテレータを使って解ける AtCoder の問題を紹介します。

https://atcoder.jp/contests/abc222/tasks/abc222_b
https://atcoder.jp/contests/abc218/tasks/abc218_b
https://atcoder.jp/contests/abc219/tasks/abc219_c (ちょっと難しい)

書けるようになりましたら、次は使えるメソッドを増やしましょう。この記事がよくまとまっていたのでリンクを貼っておきます。


ここまで読んでいただきありがとうございます。この記事がメソッドチェーン(イテレータ)を理解する助けになりましたら幸いです。

参考サイト


  1. コレクションは VecHashSet などのことです。 

9
5
2

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
9
5