Reputation: 135
You have an array of objects which contain a key-value reference.
[{booking_ref: 'w578383', foo: 'bar', price1: 500, price2: 30],
{booking_ref: 'w578383', foo: 'bar', price1: 600, price2: 40},
{booking_ref: 'r123523', foo: 'bar', price1: 699, price2: 4}]
I want to:
collapse out of groupings back to an array of objects which contain a key-value reference. That would now be:
[{booking_ref: 'w578383', foo: 'bar', price1: 1100, price2: 70},
{booking_ref: 'r123523', foo: 'bar', price1: 699, price2: 4}]
I am thinking:
objects.group_by(&:booking_ref).each {|group|
group.merge {|key, value1, value2| value1 + value2 if key == price1 || price2}
}
Does that work and if so how do I then return them back out of the group_by state?
Upvotes: 0
Views: 106
Reputation: 2310
First, group_by
is fine, then use a map
to iterate the hash and then reduce the value from a list to a sum using inject
objects.group_by(&:booking_ref)
.map{|ref, list| {booking_ref: ref,
price1: => list.inject(0){|sum,h| sum + h.price1},
price2: => list.inject(0){|sum,h| sum + h.price2}
}
}
Upvotes: 0
Reputation: 110725
Whenever you can use Enumerable#group_by you can use some form of Hash#merge or Hash#update (aka merge!
), and vice-versa. Others have used group_by
, so here's a hash-merge answer.
Letting the variable bookings
equal your array of hashes, you can write the following.
keys_to_aggregate = [:price1, :price2]
bookings.each_with_object({}) { |g,h| h.update(g[:booking_ref]=>g) { |_,o,n|
keys_to_aggregate.reduce(o) { |f,k| f.merge(k=>o[k] + n[k]) } } }.values
#=> [{:booking_ref=>"w578383", :foo=>"bar", :price1=>1100, :price2=>70},
# {:booking_ref=>"r123523", :foo=>"bar", :price1=>699, :price2=>4}]
Note that before Hash#values at the end of the expression is evaluated we have the following.
bookings.each_with_object({}) { |g,h| h.update(g[:booking_ref]=>g) { |_,o,n|
keys_to_aggregate.reduce(o) { |f,k| f.merge(k=>o[k] + n[k]) } } }
#=> {"w578383"=>{:booking_ref=>"w578383", :foo=>"bar", :price1=>1100, :price2=>70},
# "r123523"=>{:booking_ref=>"r123523", :foo=>"bar", :price1=>699, :price2=>4}}
This uses the form of Hash#update
that employs a block to determine the values of keys that are present in both hashes being merged. See the doc for details, particularly the definitions of the value-determining block's three variables (k
, o
and n
). (I've substituted _
for k
[the key] to signify that it is not used in the block calculation.)
Upvotes: 1
Reputation: 54253
With hash objects, you could calculate the sums and merge them back to the first hash in each group :
bookings = [
{booking_ref: 'w578383', foo: 'bar', price1: 500, price2: 30},
{booking_ref: 'w578383', foo: 'bar', price1: 600, price2: 40},
{booking_ref: 'r123523', foo: 'bar', price1: 699, price2: 4}
]
grouped_bookings = bookings.group_by{ |h| h[:booking_ref] }.map do |ref, hs|
sums = hs.each_with_object(Hash.new(0)) do |h, sum|
%i(price1 price2).each do |price|
sum[price] += h[price].to_i
end
end
hs.first.merge(sums)
end
p grouped_bookings
# [{:booking_ref=>"w578383", :foo=>"bar", :price1=>1100, :price2=>70},
# {:booking_ref=>"r123523", :foo=>"bar", :price1=>699, :price2=>4}]
Upvotes: 1
Reputation: 5690
Taking an object-oriented approach here, you can get this looking quite neat and elegant. The key is to define the +
method on the object. Full example below:
class Booking
attr_accessor :booking_ref, :foo, :price1, :price2
def initialize(params={})
params.each { |key, value| send "#{key}=", value }
end
# add up prices, and return new Booking object
def +(other)
new_obj = self.dup
new_obj.price1 += other.price1
new_obj.price2 += other.price2
new_obj
end
end
# set up example bookings
bookings = [
Booking.new(booking_ref: 'w578383', foo: 'bar', price1: 500, price2: 30),
Booking.new(booking_ref: 'w578383', foo: 'bar', price1: 600, price2: 40),
Booking.new(booking_ref: 'r123523', foo: 'bar', price1: 699, price2: 4)
]
# grouping becomes very simple - potentially a one-liner
bookings.group_by(&:booking_ref).map { |_, values| values.reduce(&:+) }
# => [
#<Booking:... @booking_ref="w578383", @foo="bar", @price1=1100, @price2=70>,
#<Booking:... @booking_ref="r123523", @foo="bar", @price1=699, @price2=4>
]
Upvotes: 0