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