Reputation: 1960
For the array of hashes:
array = [{id: 1, name: 'name', count: 2},
{id: 1, name: 'name', count: 1},
{id: 1, name: 'new name', count: 1}]
I'd like the result:
[{id: 1, name: 'name', count: 3},
{id: 1, name: 'new name', count: 1}]
I've achieved this with:
grouped_data = array.each_with_object(Hash.new(0)) do |row, sum|
sum["#{row.fetch(:id)} #{row.fetch(:name)}"] += row.fetch(:count).to_i
end
result = array.uniq.each do |row|
row[:count] = grouped_data["#{row.fetch(:id)} #{row.fetch(:name)}"]
row
end
Is there a more elegant way to achieve the desired result?
Upvotes: 1
Views: 74
Reputation: 11183
Another option, where you initialise the hash in map_with_object:
array
.group_by { |h| h[:name] }.values
.map{ |e| e.map.with_object({name: e[0][:name], id: e[0][:id], count: 0}){ |h, nh| nh[:count] += h[:count] } }
#=> [{:name=>"name", :id=>1, :count=>3}, {:name=>"new name", :id=>1, :count=>1}]
Upvotes: 0
Reputation: 110675
This is one way it would commonly be done.
array.each_with_object({}) {|g,h| h.update(g[:name]=>g) {|_,o,n|
o.merge(count: o[:count] + n[:count]) } }.values
#=> [{:id=>1, :name=>"name", :count=>1},
# {:id=>1, :name=>"new name", :count=>1}]
This uses the form of Hash#update (aka merge!
) that employs a block to determine the values of keys that are present in both hashes being merged.
See the doc for definitions of the the three block variables, _
, o
and n
. The first of these holds the the common key. It's common practice to use _
for block variables that are not used in the block calculation.
Note that the receiver of values
is as follows.
array.each_with_object({}) {|g,h| h.update(g[:name]=>g) {|_,o,n|
o.merge(count: o[:count] + n[:count]) } }
#=> {"name"=>{:id=>1, :name=>"name", :count=>2},
# "new name"=>{:id=>1, :name=>"new name", :count=>1}}
Upvotes: 3
Reputation: 5213
Here's a slightly different take, which as a bonus produces the expected result if the count
attribute for any element is greater than 1:
> array.group_by { |e| [e[:id], e[:name]] }.values
.map { |group| group.first.merge(count: group.sum { |e| e[:count] }) }
#> [{:id=>1, :name=>"name", :count=>2}, {:id=>1, :name=>"new name", :count=>1}]
> array = [{:id=>1, :name=>"name", :count=>2},
{:id=>1, :name=>"name", :count=>3},
{:id=>1, :name=>"new name", :count=>1}]
> array.group_by { |e| [e[:id], e[:name]] }.values
.map { |group| group.first.merge(count: group.sum { |e| e[:count] }) }
#> [{:id=>1, :name=>"name", :count=>5}, {:id=>1, :name=>"new name", :count=>1}]
Upvotes: 2
Reputation: 1836
You can group and map:
array
.group_by { |el| [el[:id], el[:name]] }
.map { |k,v| v.first.merge(count: v.length) }
Upvotes: 2