はじめに
Enumerable#inject(reduce)
(たたみこみ演算)の使い方と Enumerable#each_with_object
との違いをまとめます。
基本の使い方
- 最後にブロックを実行した結果を返す
[1,2,3,4,5].inject{|result, item| result + item}
=> 15し
[1,2,3,4,5].inject(10){|result, item| result + item}
=> 25 # 初期値を10に設定
- 下記のようにeachを使っても同じ結果になる
result = 0
[1,2,3,4,5].each{|v| result += v}
p result
=> 15
注意点
①nilの要素があるとエラーが発生する
[1,2,nil,4,5].inject{|result, item| result + item}
=> TypeError (nil can't be coerced into Integer)
- 対処法の一例
[1,2,nil,4,5].inject{|result, item| result + item.to_i }
=> 12
[1,2,nil,4,5].compact.inject{|result, item| result + item}
=> 12
②初期値は上書きしない
init = [1,2]
ret =
[10,20.30.40].inject(init) do |result, item|
result << item *2
end
p ret
[1, 2, 20, 40, 60, 80]
p init
=> [1, 2, 20, 40, 60, 80] # 初期値も上書きされている
- 対処法
- 破壊的な変更を行わない
init = [1,2]
ret =
[10,20,30,40].inject(init) do |result, item|
result + [item * 2]
end
p ret
=> [1, 2, 20, 40, 60, 80]
p init
=> [1, 2] # 初期値は変更されない
③ruby-style-guldeではinjectよりもreduceを推奨
Prefer map over collect, find over detect, select over find_all, reduce over inject, include? over member? and size over length.
each_with_object
との比較
-
each_with_object
の引数は書き換えられることを想定-
each_with_index
の引数は実行結果として返るオブジェクト - ブロックが実行される毎に書き換えられることを想定している
-
value = [1,2]
ret = [10,20,30,40].each_with_object(value){|item, result| result << item * 2 }
p ret
=> [1, 2, 20, 40, 60, 80]
p value
=> [1, 2, 20, 40, 60, 80] # 実行結果と同じ
使い分け
-
each_with_object
- 引数の
{}
はブロックが実行される毎に書き換えられる - ループ処理の場合、ブロック内で次のループにオブジェクトを渡す処理を書く必要はない
【追記】※下記はinject
メソッドと比較するための例の引用です。実際はto_h
メソッドを使った方が良いというコメントをいただきましたので、追記します。
- 引数の
lower = 'a'..'z'
lower_to_upper = lower.each_with_object({}) do |char, hash|
hash[char] = char.upcase # ブロック内は対応づけのみで良い
end
p lower_to_upper
=> {"a"=>"A", "b"=>"B", "c"=>"C", "d"=>"D", "e"=>"E", "f"=>"F", "g"=>"G", "h"=>"H", "i"=>"I", "j"=>"J", "k"=>"K", "l"=>"L", "m"=>"M", "n"=>"N", "o"=>"O", "p"=>"P", "q"=>"Q", "r"=>"R", "s"=>"S", "t"=>"T", "u"=>"U", "v"=>"V", "w"=>"W", "x"=>"X", "y"=>"Y", "z"=>"Z"}
-
inject
- 引数の
{}
は書き換えられない - ループ処理の場合、ブロック内で次のループにオブジェクトを渡す処理を書く必要がある
- 引数の
lower = 'a'..'z'
lower_to_upper = lower.inject({}) do |hash, char|
hash[char] = char.upcase
hash # オブジェクトを渡す処理が必要になる
end
p lower_to_upper
=> {"a"=>"A", "b"=>"B", "c"=>"C", "d"=>"D", "e"=>"E", "f"=>"F", "g"=>"G", "h"=>"H", "i"=>"I", "j"=>"J", "k"=>"K", "l"=>"L", "m"=>"M", "n"=>"N", "o"=>"O", "p"=>"P", "q"=>"Q", "r"=>"R", "s"=>"S", "t"=>"T", "u"=>"U", "v"=>"V", "w"=>"W", "x"=>"X", "y"=>"Y", "z"=>"Z"}
おわりに
inject
と each_with_object
ではブロック引数の順番の違いに注意して使用したいと思います。
参考