Rakesh Patidar
Rakesh Patidar

Reputation: 196

Merge array of hashes by some keys and sum values of other keys

I have a array like

array = [
  {"point"=>6, "score"=>4, "team"=>"Challenger"},
  {"point"=>4, "score"=>2, "team"=>"INB"},
  {"point"=>2, "score"=>2, "team"=>"Super-11"},
  {"point"=>3, "score"=>7, "team"=>"INB"}
]

I want to merge hashes by "team" and sum the values of "point" and "score". Additionally want to insert an key "qualified" in each hash if point is greater than 5. So the final result will be:

result= [
      {"point"=>6, "score"=>4, "qualified"=> "yes", "team"=>"Challenger"},
      {"point"=>7, "score"=>9, "qualified"=> "yes", "team"=>"INB"},
      {"point"=>2, "score"=>2, "qualified"=> "no", "team"=>"Super-11"}
    ]

Any help would be appreciated. Thanks!

Upvotes: 2

Views: 736

Answers (6)

alex
alex

Reputation: 3742

result = array.group_by{|i| i['team']}
               .map do |k,v|
                 points = v.map{|i| i['point']}.inject(0, :+)
                 score = v.map{|i| i['score']}.inject(0, :+)
                 {
                   'point' => points, 
                   'score' => score, 
                   'qualified' => points > 5 ? 'yes' : 'no',
                   'team' => k
                 }
               end

Upvotes: 2

Cary Swoveland
Cary Swoveland

Reputation: 110725

array.each_with_object({}) do |g,h|
  h.update(g["team"]=>g.merge("qualified"=>g["score"] > 5 ? "yes" : "no")) do |_,o,n|
    { "point"    =>o["point"]+n["point"],
      "score"    =>o["score"]+n["score"],
      "team"     =>o["team"],
      "qualified"=>(o["score"]+n["score"]) > 5 ? "yes" : "no" }
  end
end.values
  #=> [{"point"=>6, "score"=>4, "team"=>"Challenger", "qualified"=>"no"},
  #    {"point"=>7, "score"=>9, "team"=>"INB", "qualified"=>"yes"},
  #    {"point"=>2, "score"=>2, "team"=>"Super-11", "qualified"=>"no"}]

This uses the form of Hash#update (aka merge!) that employs a block to determine the values of keys (here the value of :id) that are present in both hashes being merged. See the doc for the description of the three block variables (here _, o and n).

Note that the receiver of values (at the end) is

{"Challenger"=>{"point"=>6, "score"=>4, "team"=>"Challenger", "qualified"=>"no"},
 "INB"=>{"point"=>7, "score"=>9, "team"=>"INB", "qualified"=>"yes"},
 "Super-11"=>{"point"=>2, "score"=>2, "team"=>"Super-11", "qualified"=>"no"}}

One could alternatively make a separate pass at the end to add the key "qualified':

array.each_with_object({}) do |g,h|
  h.update(g["team"]=>g) do |_,o,n|
    { "point"    =>o["point"]+n["point"],
      "score"    =>o["score"]+n["score"],
      "team"     =>o["team"] }
  end
end.values.
    map { |h| h.merge("qualified"=>(h["score"] > 5) ? "yes" : "no") }

Upvotes: 0

Oleksandr Holubenko
Oleksandr Holubenko

Reputation: 4440

One more possible solution :)

array.group_by { |item| item['team'] }.map do |_, items| 
  result = items.inject({}) { |hash, item| hash.merge(item) { |_, old, new| Integer(old) + new rescue old } }
  result.merge("qualified" => result['point'] > 5 ? "yes" : "no")
end

Upvotes: 3

wiesion
wiesion

Reputation: 2455

After doing group_by, a simple map operation which takes the first element as the mapped value, sums up point and score within it and then merges the qualified condition into it is easy enough:

array
  .group_by { |h| h["team"] }
  .map do |_, a|
    ["point", "score"].each { |k| a.first[k] = a.sum { |h| h[k] } }
    a.first.merge({"qualified": a.first["score"] > 5 ? 'yes' : 'no'})
  end

Online demo here

Upvotes: 0

iGian
iGian

Reputation: 11193

This is an alternative version. group_by is mandatory, I guess. I used a temporary hash with keys as symbol to store data during iterations.

result = array.group_by { |hash| hash['team'] }.map do |team|
  tmp_hash = {point: 0, score: 0, team: team[0], qualified: 'no'}
  team[1].each { |h| tmp_hash[:point] += h['point'] ; tmp_hash[:score] += h['score'] }
  tmp_hash[:qualified] = 'yes' if tmp_hash[:point] > 5
  tmp_hash
end

this gives as result:

# => [
#      {:point=>6, :score=>4, :team=>"Challenger", :qualified=>"yes"},
#      {:point=>7, :score=>9, :team=>"INB", :qualified=>"yes"},
#      {:point=>2, :score=>2, :team=>"Super-11", :qualified=>"no"}
#    ]

Upvotes: 1

Fabio
Fabio

Reputation: 32455

Combination of group_by and map should help

result = 
    array.group_by {|item| item['team'] }
         .map do |team, items|
             total_points = items.map{|item| item['point']}.reduce(0, :+)
             total_score = items.map{|item| item['score']}.reduce(0, :+)
             qualified = points > 5
             {
                 'point' => total_points, 
                 'score' => total_score, 
                 'qualified' => qualified ,
                 'team' => team
             }
          end

Upvotes: 2

Related Questions