結論
inject
- 最後に行われたブロックの実行結果を返す。
- 要素同士を作用させたいときに使用。
- 要素のたたみ込みを行う。
each_with_object
- 指定されたオブジェクトを返す。
- 配列やハッシュで使用。
- 各要素に処理を行なった、新しいオブジェクトを返す。
解説
まず、2つのメソッドを使用し、連続する1〜10の数値で配列を作ってみます。
# inject
(1..10).inject([]) { |ary, num| ary << num } # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# each_with_object
(1..10).each_with_object([]) { |num, ary| ary << num } # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
この2つは、どちらも[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]を返します。
次に、この2つに条件をつけてみます。
条件は偶数の値のみを取得し配列にします。
偶数であることの条件分岐なので、ifとeven?を使用します。
# inject
(1..10).inject([]) { |ary, num| ary << num if num.even? } # => NoMethodError (undefined method `<<' for nil:NilClass)
# each_with_object
(1..10).each_with_object([]) { |num, ary| ary << num if num.even? } # => [2, 4, 6, 8, 10]
すると、injectの方はNoMethodErrorとなってしまいます。
エラー内容は、nilには<<メソッドが定義されてない。
つまり、inject内の変数aryがnilになってしまっています。
これはそれぞれのメソッドの挙動の違いによるものです。
inject
リファレンスマニュアルを確認してみると、
最初に初期値 init と self の最初の要素を引数にブロックを実行します。 2 回目以降のループでは、前のブロックの実行結果と self の次の要素を引数に順次ブロックを実行します。そうして最後の要素まで繰り返し、最後のブロックの実行結果を返します。
とあります。
**初期値 initとは、injectの後ろについている()内の値、今回は空の配列[]**になります。
**selfとはinjectが作用しているもの、今回は1〜10までの数値(1..10)**になります。
先程の例、
(1..10).inject([]) { |ary, num| ary << num }
で見てみると、
まず、aryに初期値である**[]が、numに(1〜10)の最初の値1**が入り、ブロック({}内)のary << numが実行されます。
# |ary, num|
ary = []
num = 1
ary << num # => [1]
次に、先程の実行結果**[1]がaryに入り、numには(1〜10)の次の値2**が入り、ブロック内のary << numつまり、[1] << 2が実行されます。
# |ary, num|
ary = [1]
num = 2
ary << num # => [1, 2]
次は、また先程と同じように実行結果**[1, 2]がaryに入り、numには(1〜10)の3番目の値3**が入り、またブロックary << num ([1, 2] << 3)が実行されます。
# |ary, num|
ary = [1, 2]
num = 3
ary << num # => [1, 2, 3]
この挙動が(1〜10)の10まで繰り返され、最後の実行結果が返されます。
# |ary, num|
ary = [1, 2, 3, 4, 5, 6, 7, 8, 9]
num = 10
ary << num # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] この実行結果が返る
繰り返しに実行結果が使用されている。
それぞれのブロック内の変数をそのまま出力してみます。
# inject
(1..3).inject([]) { |ary, num| p "#{ary} - #{num}:" }
# "[] - 1:"
# "[] - 1: - 2:"
# "[] - 1: - 2: - 3:"
# "[] - 1: - 2: - 3: - 4:"
# "[] - 1: - 2: - 3: - 4: - 5:"
# => "[] - 1: - 2: - 3: - 4: - 5:"
"ary - num:"
"[ ] - 1:"
"[ ] - 1: - 2:"
"[ ] - 1: - 2: - 3:"
"実行結果 - 要素:"
上記を見ると、{ }の実行結果が次のaryに渡されていることがわかります。
each_with_object
リファレンスマニュアルを確認してみると、
与えられた任意のオブジェクトと要素をブロックに渡し繰り返し、最初に与えられたオブジェクトを返します。
とあります。
**与えられた任意のオブジェクトとは、each_with_objectの後ろについている()内の値、今回は空の配列[]**になります。
先程の例、
(1..10).each_with_object([]) { |num, ary| ary << num }
で見てみると、
まず、numに(1..10)の最初の値**1が、aryに配列オブジェクト[]が入り、ブロック内のary << numを実行。配列オブジェクトは[1]**になります。
# |num, ary|
num = 1
ary = []
ary << num
ary # => [1]
次に、numに(1..10)の次の値**2が、aryに配列オブジェクト[1]が入り、ブロック内のary << numを実行。配列オブジェクトは[1, 2]**になります。
# |num, ary|
num = 2
ary = [1]
ary << num
ary # => [1, 2]
次は、また同じようにnumに(1..10)の3番目の値**3が、aryに配列オブジェクト[1, 2]が入り、ブロック内のary << numを実行。配列オブジェクトは[1, 2, 3]**になります。
# |num, ary|
num = 3
ary = [1, 2]
ary << num
ary # => [1, 2, 3]
この挙動が(1〜10)の10まで繰り返され、最後のオブジェクトが返されます。
# |num, ary|
num = 10
ary = [1, 2, 3, 4, 5, 6, 7, 8, 9]
ary ,<< num
ary # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] この配列オブジェクトが返る
繰り返しに( )で与えられたオブジェクトが使用されている。
それぞれのブロック内の変数をそのまま出力してみます。
# each_with_object
(1..3).each_with_object([]) { |num, ary| p "#{ary} - #{num}" }
# "[] - 1"
# "[] - 2"
# "[] - 3"
# => []
"ary - num:"
"[ ] - 1:"
"[ ] - 2:"
"[ ] - 3:"
"指定されたオブジェクト - 要素:"
injectとは違い、aryには与えられたオブジェクトが入ります。
上記では<<などで配列に追加をしていないため、そのまま[ ]が出力されています。
最後に
言い回しや解釈の間違っているところがございましたら、ご教示いただけますと幸です。
ご覧いただき、ありがとうございました。