ちょっとわかりにくいけど非常に便利なinjectメソッド
ruby の inject で配列の合計や最小要素を簡単に求められるんだけど、慣れていないと理解しにくい。そこで、できるだけわかりやすく説明したいと思う。
injectの使用例
Ruby初心者が配列の和を求めたいと思ったとき、最初に思いつくのは以下のようなコードだろう。
sum = 0 [1, 2, 3, 4, 5].each do |i| sum += i end sum # => 15
変数sumを用意し、eachで配列の中身を順にsumに足していく。Rubyを知っている人なら誰でも理解できると思う。
これをinjectを使って次のように書ける。
[1, 2, 3, 4, 5].inject(0) { |sum, i| sum + i } # => 15
簡潔だが、injectを知らないといまいちピンとこないのではないだろうか。ちなみに、Ruby1.8.7以降であれば更に簡単に書ける。
sum = [1, 2, 3, 4, 5].inject(:+) # => 15
こちらの方がわかりやすいかもしれない。どちらも原理は簡単だ。
injectの原理
Cなどの言語をやったことがあれば、以下の2つが等価だと聞いたことはあるだろう。
式1; while (式2) { 文 式3; }
for (式1; 式2; 式3) { 文 }
式1が初期化、式2が継続条件、式3が再初期化となる。eachとinjectの関係は、このwhileとforの関係に似ている。そう、まさに上で例として上げた配列の和を求める3つのコードが等価なのだ。
injectにはいくつかの書き方が存在し、その基本となるのがこれ。
ary.inject(init) { |result, item| ... }
これは以下のコードと同じである。
result = init ary.each do |item| result = ... end
これを理解できれば、和を求めるときは「result + item」を、最大値を求めるときは「(item > result) ? item : result」を 「...」の部分に書けばいいということがわかるのではないだろうか。
もちろん、「result = result + item」は「result += item」と書けるので、最初の2つの例が等価という意味もわかってもらえるだろう。
injectの他の書き方
3つ目の例ではinjectに演算子のシンボルだけを渡している。このように、injectにはいくつか書き方がある。
まずは初期値を省略した場合。
ary.inject { |result, item| ... }
これは、初期値として配列の先頭の値が代入され、2回目のループから開始されると考えればいい。
次に、ブロックなしで演算子のシンボルと初期値をinjectの引数に与えた場合。
ary.inject(init, :[sym]) # [sym] は + や * などの演算子
これは以下と等価になる。
result = init ary.each do |item| result = result [sym] item end
そしてもう一つが、3つ目の例で挙げた、演算子のシンボルのみをinjectの引数に与えた場合。
ary.inject(:[sym]) # [sym] は + や * などの演算子
これは、ary.inject(init, :[sym]) のinitとして配列の先頭の値が代入され、2回目のループから開始されたときと同じになる。
とりあえず、配列の和を求めたいときは inject(:+) と覚えておいて損はないだろう。
injectの応用例
文字列の長さの最小値、最大値や合計を求める
例えば、配列に入ってる文字列の中で一番長い文字列の長さがほしい場合、injectを使えばこのように書ける。
ary = ["one", "two", "three", "four", "five"] ary.inject(0) { |max, s| s.length > max ? s.length : max} # => 5
一応、これは下のようにも書ける。
ary.max_by { |s| s.length }.length # => 5
ブロックに更にlengthメソッドをつけるのが気持ち悪いので個人的にはinjectが好みだが、まあこの辺は人によるだろうか。
文字列の長さの合計もinjectで一発だ。
ary = ["one", "two", "three", "four", "five"] ary.inject(0) { |sum, s| sum + s.length } # => 19
まとめ
以上、injectメソッドについていろいろ説明してみた。あまりわかりやすい説明ではなかったように思うが、とりあえずfor文が使えたらinjectも使えるはずだということだけは言っておきたかった。
今までinjectを知らなかったり、知っていてもわかりにくいという理由で使っていなかった人も、これからは積極的にinjectを使うことをオススメする。