I have some questions about an array of hash in ruby.
Example :
a = [{236=>1}, {236=>1}, {237=>1}]
I want get result like this :
a = [{236=>2}, {237=>1}]
How do I merge an array of values based on their key in ruby?
I have some questions about an array of hash in ruby.
Example :
a = [{236=>1}, {236=>1}, {237=>1}]
I want get result like this :
a = [{236=>2}, {237=>1}]
How do I merge an array of values based on their key in ruby?
I find this way readable:
a = [{236=>1}, {236=>1}, {237=>1}]
merge_with_addition = -> x,y {x.merge(y){|_,old,new|old+new}}
p a.reduce(&merge_with_addition) #=> {236=>2, 237=>1}
There are several ways to do that. Here's one that uses the form of the method Hash#update (aka merge!) that uses a block to determine the values of keys present in both hashes being merged:
a.each_with_object({}) { |g,h|
h.update(g["order_detail_id"]=>g) { |_,oh,ng|
{ "order_detail_id" =>g["order_detail_id"],
"quantity"=>oh["quantity"]+ng["quantity"] } } }.values
#=> [{"order_detail_id"=>236, "quantity"=>2},
# {"order_detail_id"=>237, "quantity"=>1}]
Another approach uses Enumerable#group_by:
a.group_by { |h| h["order_detail_id"] }.map { |id, a|
{ "order_detail_id"=>id, "quantity"=>a.reduce(0) { |t,g|
t + g["quantity"] } } }
#=> [{"order_detail_id"=>236, "quantity"=>2},
# {"order_detail_id"=>237, "quantity"=>1}]
Edit: monoy has asked how one can merge:
a = [{236=>1}, {236=>1}, {237=>1}]
When you have hashes with a single key-value pair, it's often easiest to first convert them to arrays, to have easy access to the key and value:
b = a.map(&:to_a)
#=> [[[236, 1]], [[236, 1]], [[237, 1]]]
Unfortunately, this has three levels of arrays, where we want just two. We could write:
b = a.map(&:to_a).flatten(1)
#=> [[236, 1], [236, 1], [237, 1]]
but an easier way is:
b = a.flat_map(&:to_a)
#=> [[236, 1], [236, 1], [237, 1]]
Again, we can merge the arrays several different ways. This is one which uses a hash with a default value of zero:
b.each_with_object(Hash.new(0)) { |(k,v),h| h[k] += v }
#=> {236=>2, 237=>1}
We have:
enum = b.each_with_object(Hash.new(0))
#=> #<Enumerator: [[236, 1], [236, 1], [237, 1]]:each_with_object({})>
We can convert this enumerator to an array to see what values each will pass into the block:
enum.to_a
#=> [[[236, 1], {}], [[236, 1], {}], [[237, 1], {}]]
We can use Enumerator#next to obtain each value of the enumerator, and assign the block variables to that value. The first passed to the block is:
(k,v),h = enum.next
#=> [[236, 1], {}]
k #=> 236
v #=> 1
h #=> {}
so in the block we execute:
h[k] += v
which evaluates to:
h[236] += 1
which means:
h[236] = h[236] + 1
The hash h is presently empty, so it doesn't have a key 236. Therefore, h[236] on the right side of the expression returns the hash's default value of zero:
h[236] = 0 + 1
so now:
h #=> {236=>1}
Now pass the next element into the block:
(k,v),h = enum.next
#=> [[236, 1], {236=>1}]
k #=> 236
v #=> 1
h #=> {236=>1}
so now the default value is not used when we execute the expression in the block:
h[k] += v
#=> h[k] = h[k] + v
#=> h[236] = h[236] + 1
#=> h[236] = 1 + 1
so now:
h #=> {236=>2}
The third and last element of the enumerator is now passed to the block:
(k,v),h = enum.next
#=> [[237, 1], {236=>2}]
k #=> 237
v #=> 1
h #=> {236=>2}
h[k] += v
#=> h[k] = h[k] + v
#=> h[237] = h[237] + 1
#=> h[236] = 0 + 1 # the default value of 0 applies
h #=> {236=>2, 237=>1}
and we are finished.
array = [{234 => 1}, {235 => 1}, {234 => 1}]
hashes = array.map(&:to_a).flatten
.each_slice(2).with_object(Hash.new(0)) { |(k, v), h|
h[k] += v
}.each_with_object([]) { |(k, v), a|
a << { k => v }
}