Ruby の inject(reduce) メソッドを使いこなす

目次

Rubyのイテレータメソッドinject(reduce)の使い方を紹介してみようと思います。

文中では便宜上、injectで表記しますがreduceも等価なメソッドなので文中のinjectreduceに置き換え可能です

injectの基本的な使い方

Rubyのinjectはこんなふうに使えます。

enum.inject { |memo, item| block }
enum.inject(init) { |memo, item| block }

Sum(合計)を出す

これだけではわかりにくいと思うので合計を出す処理を書いてみましょう。

injectを使わないパターン

まずはinjectを使わないパターン。

sum = 0
(1..10).each { |i| sum += i }
puts sum # => 55

injectを使うパターン

これをinjectで書き直すとこう。

(1..10).inject(0) { |sum, i| sum + i }
# => 55

この場合、sumの初期値をinjectの引数の0で初期化しています。ただinjectのデフォルト引数設定は0なので下記のように省略することができます。

(1..10).inject { |sum, i| sum + i }
# => 55

さらにinjectにはシンボルで演算子を渡してうまいようにやってくれます。

(1..10).inject(:+)
# => 55

これが一番シンプルでエレガント。

Ruby 2.4+ の場合

Ruby 2.4 以上をお使いの場合、Array#sumを使って書くことができます。

(1..10).sum
# => 55

応用編1: 配列内の要素数をハッシュに

Hash.new(0)で初期化してinject.

[:great, :good, :bad, :good, :good, :bad, :awesome, :great].inject(Hash.new(0)) { |hash, key| hash[key] += 1; hash}
# => {:great=>2, :good=>3, :bad=>2, :awesome=>1}

each_with_object を使う

上記の例だとhash[key] += 1; hashとやや冗長な書き方となっていますが、each_with_object使うともう少しシンプルに書けます。

[:great, :good, :bad, :good, :good, :bad, :awesome, :great].each_with_object(Hash.new(0)) { |key, hash| hash[key] += 1}
# => {:great=>2, :good=>3, :bad=>2, :awesome=>1}

応用編2: フィボナッチ数列

inject を利用したフィボナッチ数列ロジック。

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

応用編3: ループを止める

途中でループを止めて処理を終えたい場合、breakを使えば可能。

(1..5).inject([]) do |ary, number|
  break ary if number == 4
  ary << number
end
# => [1, 2, 3]

番外編: each_with_index

each_with_indexでもinjectは使える。

(1..10).each_with_index.inject(0) { |sum, (value, index)| sum + value }
# => 55

(1..10).each_with_index.inject(0) { |sum, (value, index)| sum + value + index}
# => 100

参考