ruby の inject をわかりやすく説明してみる
ruby の inject って慣れないと少し理解しづらいよなーと思ったので、極力わかりやすい説明をしてみるテスト。
わかりやすいかもしれない説明
さて、1 から 10 までの合計を求めるこんな↓コードがあった場合
sum = 0 (1..10).each {|i| sum = sum + i } p sum # => 55
inject を使ってこのよう↓に書けます。
p (1..10).inject(0) {|sum, i| sum + i }
each と inject でどのように書き変わってるかを図で示すとこんな↓感じ。
injectの引数 0 は、ブロックローカルな sum 変数の初期値になってます。で、ブロックの実行結果の値が sum に代入されて、2回目以降のループを実行します。ループしている間の、各変数とブロックの中身はこんな↓感じ。
sum | i | ブロックの中身(sum + i) | の実行結果 |
---|---|---|---|
0 | 1 | 0 + 1 | = 1 |
1 | 2 | 1 + 2 | = 3 |
3 | 3 | 3 + 3 | = 6 |
6 | 4 | 6 + 4 | =10 |
10 | 5 | 10 + 5 | =15 |
15 | 6 | 15 + 6 | =21 |
21 | 7 | 21 + 7 | =28 |
28 | 8 | 28 + 8 | =36 |
36 | 9 | 36 + 9 | =45 |
45 | 10 | 45 + 10 | =55 |
最後に実行されたブロックの結果が、inject の戻り値となります。
よくあるパターン
んじゃ、inject で書き換える事ができる他のパターンを見てみます
フィボナッチ数列を求める
こんな↓コードがあった場合
fib = [1, 1] (0..10).each {|i| fib << fib[i] + fib[i+1]} p fib # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]
inject を使ってこのよう↓に書けます。
p (0..10).inject([1, 1]) {|fib, i| fib << fib[i] + fib[i+1] }
あるデータの度数分布を求める
こんな↓コードがあった場合
data = [:A, :B, :A, :C, :E, :A, :D, :B, :B, :C, :E] h = Hash.new(0) data.each {|key| h[key] += 1 } p h # => {:A=>3, :B=>3, :C=>2, :E=>2, :D=>1}
inject を使ってこのよう↓に書けます。
data = [:A, :B, :A, :C, :E, :A, :D, :B, :B, :C, :E] p data.inject(Hash.new(0)) {|h, key| h[key] += 1; h }
上の例の場合、ブロックの実行結果でハッシュ(h)を返したいので、セミコロンで区切って h を返しています。
まとめ
どの例でも、ループで計算した結果を保持しとく変数が、inject では、ブロックローカルな変数で済んでるのでスッキリした感じがしますね。そんなわけで、皆で inject厨になりましょう。