Reputation: 133
In Ruby I have the following array of hashes:
[
{:qty => 1, :unit => 'oz', :type => 'mass'},
{:qty => 5, :unit => 'oz', :type => 'vol'},
{:qty => 4, :unit => 'oz', :type => 'mass'},
{:qty => 1, :unit => 'lbs', :type => 'mass'}
]
What I need to be able to do is compare the elements by the :unit
and :type
and then sum the :qty
when they are the same. The resulting Array should look like follows:
[
{:qty => 5, :unit => 'oz', :type => 'mass'},
{:qty => 5, :unit => 'oz', :type => 'vol'},
{:qty => 1, :unit => 'lbs', :type => 'mass'}
]
If the array has multiple hashes where the :qty
is nil
and the :unit
is empty (""
), then it would only return one of those. So to extend the above example, this:
[
{:qty => 1, :unit => 'oz', :type => 'mass'},
{:qty => nil, :unit => '', :type => 'Foo'},
{:qty => 5, :unit => 'oz', :type => 'vol'},
{:qty => 4, :unit => 'oz', :type => 'mass'},
{:qty => 1, :unit => 'lbs', :type => 'mass'},
{:qty => nil, :unit => '', :type => 'Foo'}
]
would become this:
[
{:qty => 5, :unit => 'oz', :type => 'mass'},
{:qty => nil, :unit => '', :type => 'Foo'},
{:qty => 5, :unit => 'oz', :type => 'vol'},
{:qty => 1, :unit => 'lbs', :type => 'mass'}
]
EDIT: Sorry, made a mistake in the second example... it shouldn't have the o.
Upvotes: 2
Views: 1166
Reputation: 96944
Start by using group_by
with the keys you want, then reduce
the qty
s in each value into a single hash, or instead using nil
if they are all nil
:
properties.group_by do |property|
property.values_at :type, :unit
end.map do |(type, unit), properties|
quantities = properties.map { |p| p[:qty] }
qty = quantities.all? ? quantities.reduce(:+) : nil
{ type: type, unit: unit, qty: qty }
end
#=> [{:type=>"mass", :unit=>"oz", :qty=>5},
# {:type=>"Foo", :unit=>"", :qty=>nil},
# {:type=>"vol", :unit=>"oz", :qty=>5},
# {:type=>"mass", :unit=>"lbs", :qty=>1}]
Where properties
is your second sample input data.
Upvotes: 8
Reputation: 118271
ar = [{:qty => 1, :unit => 'oz', :type => 'mass'}, {:qty => nil, :unit => '', :type => 'Foo'},
{:qty => 5, :unit => 'oz', :type => 'vol'},
{:qty => 4, :unit => 'oz', :type => 'mass'}, {:qty => 1, :unit => 'lbs', :type => 'mass'},
{:qty => nil, :unit => 'o', :type => 'Foo'}]
result = ar.each_with_object(Hash.new(0)) do |e,hsh|
if hsh.has_key?({:unit => e[:unit], :type => e[:type]})
hsh[{:unit => e[:unit], :type => e[:type]}] += e[:qty]
else
hsh[{:unit => e[:unit], :type => e[:type]}] = e[:qty]
end
end
result.map{|k,v| k[:qty] = v;k }.delete_if{|h| h[:qty].nil? and !h[:unit].empty? }
# => [{:unit=>"oz", :type=>"mass", :qty=>5},
# {:unit=>"", :type=>"Foo", :qty=>nil},
# {:unit=>"oz", :type=>"vol", :qty=>5},
# {:unit=>"lbs", :type=>"mass", :qty=>1}]
Taking @Andrew Marshall under consideration
ar = [{:qty => 1, :unit => 'oz', :type => 'mass'}, {:qty => nil, :unit => '', :type => 'Foo'},
{:qty => 5, :unit => 'oz', :type => 'vol'},
{:qty => 4, :unit => 'oz', :type => 'mass'}, {:qty => 1, :unit => 'lbs', :type => 'mass'},
{:qty => nil, :unit => 'o', :type => 'Foo'}]
result = ar.each_with_object(Hash.new(0)) do |e,hsh|
if hsh.has_key?({:unit => e[:unit], :type => e[:type]})
hsh[{:unit => e[:unit], :type => e[:type]}] += e[:qty]
else
hsh[{:unit => e[:unit], :type => e[:type]}] = e[:qty]
end
end
result.map{|k,v| k[:qty] = v;k }.delete_if{|h| h[:qty].nil? and h[:unit].empty? }
# => [{:unit=>"oz", :type=>"mass", :qty=>5},
# {:unit=>"oz", :type=>"vol", :qty=>5},
# {:unit=>"lbs", :type=>"mass", :qty=>1},
# {:unit=>"o", :type=>"Foo", :qty=>nil}]
Upvotes: -1
Reputation: 135227
You're going to want enumberable.group_by
This should get you started
items.group_by { |item| item.values_at(:unit, :type) }
Output
{
["oz", "mass"]=> [
{:qty=>1, :unit=>"oz", :type=>"mass"},
{:qty=>4, :unit=>"oz", :type=>"mass"}
],
["oz", "vol"]=>[
{:qty=>5, :unit=>"oz", :type=>"vol"}
],
["lbs", "mass"]=>[
{:qty=>1, :unit=>"lbs", :type=>"mass"}
]
}
Upvotes: 3