inject
とeach_with_object
の違いが分からない。
どちらもEnumerableなオブジェクトの要素を使って何かしらのオブジェクトを得る。
でもやっぱり違いがあったのでメモ。
イメージで説明
-
inject
は、各要素が力を合わせて一つのオブジェクトを作る -
each_with_object
は、ターゲットとなるオブジェクトに対して、各要素を作用させる
挙動で説明
配列の各要素を2乗する処理を書く。表面上は同じ挙動をする。
でもdo
の後のresult
は、同じresult
でも意味合いが違っていて、
-
inject
の場合はresultにはブロック内で最後に評価した値が入る -
each_with_object
の場合は、resultは常にeach_with_object
の引数として渡されたオブジェクトを指す
# inject
[1, 2, 3].inject [] do |result, i|
result << i*i
end
# each_with_object
[1, 2, 3].each_with_object [] do |i, result|
result << i*i
end
injectを使いたい場合の例
たとえば配列の和を求める場合
sum = [1, 2, 3].inject 0 do |s, i|
s + i
end
(この場合はsum = [ 1, 2, 3].inject :+
でいいけど敢えてこの形で。)
ここでeach_with_object
を使おうとしてしまうと、
sum = [ 1, 2, 3].each_with_object 0 do |i, s|
s += i
end
とすればよさげだけど、s
は破壊的に変更できない0
(Fixnum)を指しているので、
sum
に入るのは0
となってしまう。
each_with_objectを使いたい場合の例
たとえば[["Alice", 50], ["Bob", 40], ["Charlie", 70]]
から、{"Alice" => 50, "Bob" => 40, "Charlie" => 70}
を得たい場合、
ret = [["Alice", 50], ["Bob", 40], ["Charlie", 70]].each_with_object({}) do |(key, value), hash|
hash[key] = value
end
こんな感じでかける。
ここでinject
を使おうとすると、できないわけではないけど、
作成中のハッシュをブロックの最後に評価しないといけないので1行余分に必要になる。
ret = [["Alice", 50], ["Bob", 40], ["Charlie", 70]].inject({}) do |hash, (key, value)|
hash[key] = value
hash # ブロックの最後の評価を作成中のハッシュにしないといけない
end
参考:
プログラミングは素晴らしい - [プログラミング] each、each_with_object、inject、map