简体   繁体   中英

Merge an array of hashes in Ruby and count the same key

I have some data in this format

[{
    "_id" => "20",
    "value" => 1
}, {
    "_id" => "19",
    "value" => 1
}, {
    "_id" => nil,
    "value" => 8
}, {
    "_id" => "27",
    "value" => 1
}, {
    "_id" => "25",
    "value" => 3
}, {
    "_id" => "28",
    "value" => 1
}]

I want to merge the same values with "_id" key and sum the "value" values.

Desire output

[{
    "_id" => "20",
    "value" => 1
}, {
    "_id" => "19",
    "value" => 2
}, {
    "_id" => nil,
    "value" => 8
}, ...]

There is an elegant way to do this?

I have tried with two loops but I think that is not the best way to do it.

As with most things in Ruby, a trip to the Enumerable documentation turns up the group_by method which can help group things together by some arbitrary criteria. Combine that with something that does the sums and you get this:

v.group_by do |e|
  e['_id']
end.map do |id, list|
  {
    '_id' => id,
    'value' => list.inject(0) { |sum, e| sum + e['value'] }
  }
end

# => [{"_id"=>"20", "value"=>1}, {"_id"=>"19", "value"=>2}, {"_id"=>nil, "value"=>28},
#     {"_id"=>"27", "value"=>1}, {"_id"=>"25", "value"=>3}, {"_id"=>"28", "value"=>1},
#     {"_id"=>"23", "value"=>1}, {"_id"=>"16", "value"=>1}, {"_id"=>"18", "value"=>2},
#     {"_id"=>"22", "value"=>2}]
arr = [{ "_id" => "20", "value" => 1 },
       { "_id" => "19", "value" => 1 },
       { "_id" =>  nil, "value" => 8 },
       { "_id" => "20", "value" => 1 },
       { "_id" => "25", "value" => 3 },
       { "_id" => "19", "value" => 1 },
      ]

h = arr.each_with_object(Hash.new(0)) { |g,h| h[g["_id"]] += g["value"] }
  #=> {"20"=>2, "19"=>2, nil=>8, "25"=>3}

If you instead want to return an array of hashes with unique values for "_id" and the values of "value" updated, you could first compute h above, then

arr.uniq { |g| g["_id"] }.map { |g| g.update("_id"=>h[g["_id"]]) }
  #=> [{"_id"=>"20", "value"=>2}, {"_id"=>" 19", "value"=>2},
  #    {"_id"=>nil, "value"=>8}, {"_id"=>"25", "value"=>3}]  

This uses the methods Array#uniq with a block, Enumerable#map and Hash#update (aka merge! ).

Alternatively, you could write the following.

arr.each_with_object({}) { |g,h|
  h.update(g["_id"]=>g) { |_,o,n| o.merge("value"=>o["value"]+n["value"]) } }.values
  #=> [{"_id"=>"20", "value"=>2}, {"_id"=>" 19", "value"=>2},
  #    {"_id"=>nil, "value"=>8}, {"_id"=>"25", "value"=>3}]  

Again, I've used Hash#update , but this time I have employed a block to determine the values of keys that are present in both hashes being merged. See also Enumerable#each_with_object and Hash#merge . Note that, as arguments, (k=>v) is shorthand for ({ k=>v }) .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM