ちょっとわかりにくいけど非常に便利な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で書ける。

(0..5).inject([1, 1]) { |fib, i| fib << fib[i] + fib[i+1] } # => [1, 1, 2, 3, 5, 8, 13, 21]

このように、resultに配列やハッシュを使うと応用範囲がかなり広がる。

まとめ

以上、injectメソッドについていろいろ説明してみた。あまりわかりやすい説明ではなかったように思うが、とりあえずfor文が使えたらinjectも使えるはずだということだけは言っておきたかった。

今までinjectを知らなかったり、知っていてもわかりにくいという理由で使っていなかった人も、これからは積極的にinjectを使うことをオススメする。