0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Ruby】inject と each_with_object の違い

Posted at

結論

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つに条件をつけてみます。

条件は偶数の値のみを取得し配列にします。
偶数であることの条件分岐なので、ifeven?を使用します。

# 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内の変数arynilになってしまっています。

これはそれぞれのメソッドの挙動の違いによるものです。

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には与えられたオブジェクトが入ります。
上記では<<などで配列に追加をしていないため、そのまま[ ]が出力されています。

最後に

言い回しや解釈の間違っているところがございましたら、ご教示いただけますと幸です。
ご覧いただき、ありがとうございました。

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?