結論
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
には与えられたオブジェクトが入ります。
上記では<<
などで配列に追加をしていないため、そのまま[ ]
が出力されています。
最後に
言い回しや解釈の間違っているところがございましたら、ご教示いただけますと幸です。
ご覧いただき、ありがとうございました。