メソッドチェーンってなに
メソッドチェーンとは、その名前の通り、メソッドをつなげたものです。
例えば次のようなものです。
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()
は要素に対するイテレータ(反復子)を生成しています。上に書いた中間表現のことです。これ単体ではなにも起こりません。
具体的には、処理中は Iter
や IntoIter
のような型になっています。
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 (ちょっと難しい)
書けるようになりましたら、次は使えるメソッドを増やしましょう。この記事がよくまとまっていたのでリンクを貼っておきます。
ここまで読んでいただきありがとうございます。この記事がメソッドチェーン(イテレータ)を理解する助けになりましたら幸いです。
参考サイト
-
コレクションは
Vec
やHashSet
などのことです。 ↩