0

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?

1
  • You should not change the question after you've posted it. It renders answers and comments meaningless. You can add to your question, it which case it's generally a good idea to identify it as an edit. You'll see I did that in my answer. Commented May 8, 2015 at 5:46

3 Answers 3

2

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}
Sign up to request clarification or add additional context in comments.

1 Comment

The output is a Hash, not an array of hashes.
0

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.

1 Comment

hi @Cary Swoveland how to merge array of hash like this : [{236=>1}, {236=>1}, {237=>1}]
0
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 }
}

4 Comments

No need to delete you can just edit my answer or comment the fix. Or ill just write one.
Too true. Too true. Nice spot.
Sorry. Yeah. You are right. (Making all kinds of mistakes :p)
Ok just tested. Noticed that to_a gives an array of aarry, of course, so i decided to flatten and use each slice, but you could also map first or flatten on the array of array. Or you could just adjust the block variables. Anyway, this seems to work.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.