0

I have read something about hash merge with a block and this is working fine for simple, non-nested hashes in plain ruby. The following code results in {1=>2, 2=>4, 4=>6} as expected:

a = {1 => 1, 2 => 2, 4 => 3}
b = {1 => 1, 2 => 2, 4 => 3}
a.merge(b) { |key, value_a, value_b | value_a + value_b }

But the merge is not working for a nested hash structure, I get a NoMethodError (undefined method '+' for {1=>1, 2=>2}:Hash)

a = { "2018" => {1 => 1, 2 => 2, 4 => 3} }
b = { "2019" => {1 => 1, 2 => 2, 4 => 3} }
c = a.merge(b) { |key, value_a, value_b | value_a + value_b }

I have read about each_with_object and I am unsure how to use it. Is there a smart way to accomplish the merge of the values of the sub-hash? What do you think is the easiest way?

Christian
  • 4,902
  • 4
  • 24
  • 42
  • if you are on Rails, try `a.deep_merge(b) { |key, value_a, value_b| value_a + value_b }` – Fabio Feb 02 '19 at 01:02
  • Possible duplicate of [Ruby: merge nested hash](https://stackoverflow.com/questions/9381553/ruby-merge-nested-hash) – Fabio Feb 02 '19 at 01:04
  • I am using plain ruby :( – Christian Feb 02 '19 at 01:15
  • 2
    What would be the expected output for `a.merge(b)` in the second case, it seems there's nothing to merge there as both keys don't coincide. – Sebastián Palma Feb 02 '19 at 02:05
  • 1
    [Enumerable#each_with_object](http://ruby-doc.org/core-2.5.1/Enumerable.html#method-i-each_with_object) just saves a couple of steps. If, for example, you wrote `def doit(arr); a = []; arr.each { |n| a << 2*n if n.odd? }; a; end`, you could eliminate the first and last statements by writing `def doit(arr); arr.each_with_object([]) { |n,a| a << 2*n if n.odd? }; end`. – Cary Swoveland Feb 02 '19 at 18:57

2 Answers2

3

You can use Hash#deep_merge from active support to do this.

require 'active_support/all'

a = { k1: { k2: 1 } }
b = { k1: { k2: 2 } }

a.deep_merge(b) { |k, v1, v2| v1 + v2 }
# => { l1: { k2: 3 } }
max pleaner
  • 26,189
  • 9
  • 66
  • 118
2

Nested Hash, nested Hash#merge?

I changed the key of b to "2018"

a = { "2018" => {1 => 1, 2 => 2, 4 => 3} }
b = { "2018" => {1 => 1, 2 => 2, 4 => 3} }
c = a.merge(b) { |k, v1, v2| v1.merge(v2) { |kk, aa, bb | aa + bb } }

#=> {"2018"=>{1=>2, 2=>4, 4=>6}}

For your original values:

a = { "2018" => {1 => 1, 2 => 2, 4 => 3} }
b = { "2019" => {1 => 1, 2 => 2, 4 => 3} }

The result is

#=> {"2018"=>{1=>1, 2=>2, 4=>3}, "2019"=>{1=>1, 2=>2, 4=>3}}
iGian
  • 11,023
  • 3
  • 21
  • 36
  • 1
    Great simple answer! – Christian Feb 02 '19 at 15:10
  • 1
    Good answer. Readers: this uses the form of [Hash#merge](http://ruby-doc.org/core-2.6.1/Hash.html#method-i-merge) that employs a block to determine the values of keys that are present in both hashes being merged. As seen, this is done at two levels. If `c = { "2018" => {1 => 1, 2 => 2, 4 => 9} }`, with Ruby v2.6 (see linked doc) one can write `a.merge(b, c) { |k, v1, v2| v1.merge(v2) { |kk, aa, bb | aa + bb } } #=> {"2018"=>{1=>3, 2=>6, 4=>15}} `. – Cary Swoveland Feb 02 '19 at 19:08