group_byメソッドを使用した時、返り値のHashを処理してHashで返す処理を行いたいケースがあった。
その処理方法を「each」「map」「reduce」「each_with_object」で比較。
前提
users = [
{id: 1, name: 'suna', group: 1, score: 100},
{id: 2, name: 'kawa', group: 1, score: 10},
{id: 3, name: 'kazu', group: 2, score: 10},
{id: 4, name: 'ya', group: 2, score: 30}
]
grouped_users = users.group_by {|item|
item[:group]
}
each
each_result = {}
grouped_users.each do |k, v|
each_result[k] = []
each_result[k] = v.max_by {|item| item[:score]}
end
最初に変数宣言しないといけない。
each&tap
tap_each_result = {}.tap {|item|
grouped_users.each do |k, v|
item[k] = []
item[k] = v.max_by {|item| item[:score]}
end
}
1つ前の処理の初期化処理を無くすためにtapを使ったパターン
map
map_result = grouped_users.map do |(k, v)|
[k, v.max_by {|users| users[:score]}]
end.to_h
返り値がArrayであるため、to_hメソッドなどでHashにしなければならない。
reduce
reduce_result = \
grouped_users.reduce({}) do |result, (k, v)|
result[k] = v.max_by {|users| users[:score]}
result
end
returnを書かないと結果が返らない。
tap&reduce
reduce_tap_result = \
grouped_users.reduce({}) do |result, (k, v)|
result.tap {|result| result[k] = v.max_by {|users| users[:score]} }
end
tapでreturn処理を消しているが、その分ブロックは増える。
each_with_object
each_with_object_result = \
grouped_users.each_with_object({}) do |(k, v), h|
h[k] = v.max_by {|user| user[:score]}
end
Hashで返す。ブロック1つで済む。シンプル。
(だが、メソッドとその引数宣言などが長い & reduceと比較して、パイプの中の変数宣言が順番が異なるのが気になる)
テスト
p each_with_object_result == each_result \
&& each_with_object_result == tap_each_result \
&& each_with_object_result == map_result \
&& each_with_object_result == reduce_tap_result \
&& each_with_object_result == reduce_tap_result \
# true
追記(2015/12/24)
Structでラップする方法を教えてもらったので追加。
ユーザー情報を構造体として扱うのなら、Structのメソッドで扱いやすくなるので良さげ。
user = Struct.new("User", :id, :name, :group, :score)
users = [
user.new(1, 'suna', 1, 100),
user.new(1, 'kawa', 1, 10),
user.new(1, 'kazu', 2, 10),
user.new(1, 'ya', 2, 30)
]
Hash[*users
.group_by(&:group)
.map { |k, v| [k, v.max_by(&:score)] }
.tap {|item| p item}
.flatten]