LoginSignup
4
4

More than 5 years have passed since last update.

Hashを回してHashを返す処理の比較

Last updated at Posted at 2015-12-21

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]
4
4
0

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
4
4