简体   繁体   中英

Ruby array of hashes, compare 2 keys and sum another key/value

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.

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.

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"}
  ]
}
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}]

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