How to merge multiple hashes in Ruby?

35,628

Solution 1

You could do it like this:

h, h2, h3  = { a: 1 }, { b: 2 }, { c: 3 }
a  = [h, h2, h3]

p Hash[*a.map(&:to_a).flatten] #= > {:a=>1, :b=>2, :c=>3}

Edit: This is probably the correct way to do it if you have many hashes:

a.inject{|tot, new| tot.merge(new)}
# or just
a.inject(&:merge)

Solution 2

Since Ruby 2.0 on that can be accomplished more graciously:

h.merge **h1, **h2

And in case of overlapping keys - the latter ones, of course, take precedence:

h  = {}
h1 = { a: 1, b: 2 }
h2 = { a: 0, c: 3 }

h.merge **h1, **h2
# => {:a=>0, :b=>2, :c=>3}

h.merge **h2, **h1
# => {:a=>1, :c=>3, :b=>2}

Solution 3

You can just do

[*h,*h2,*h3].to_h
# => {:a=>1, :b=>2, :c=>3}

This works whether or not the keys are Symbols.

Solution 4

Ruby 2.6 allows merge to take multiple arguments:

h  = { a: 1 }
h2 = { b: 2 }
h3 = { c: 3 }
h4 = { 'c' => 4 }
h5 = {}

h.merge(h2, h3, h4, h5) # => {:a=>1, :b=>2, :c=>3, "c"=>4}

This works with Hash.merge! and Hash.update too. Docs for this here.

Also takes empty hashes and keys as symbols or strings.

Much simpler :)

Solution 5

Answer using reduce (same as inject)

hash_arr = [{foo: "bar"}, {foo2: "bar2"}, {foo2: "bar2b", foo3: "bar3"}]

hash_arr.reduce { |acc, h| (acc || {}).merge h }
# => {:foo2=>"bar2", :foo3=>"bar3", :foo=>"bar"}

Explanation

For those beginning with Ruby or functional programming, I hope this brief explanation might help understand what's happening here.

The reduce method when called on an Array object (hash_arr) will iterate through each element of the array with the returned value of the block being stored in an accumulator (acc). Effectively, the h parameter of my block will take on the value of each hash in the array, and the acc parameter will take on the value that is returned by the block through each iteration.

We use (acc || {}) to handle the initial condition where acc is nil. Note that the merge method gives priority to keys/values in the original hash. This is why the value of "bar2b" doesn't appear in my final hash.

Hope that helps!

Share:
35,628
B Seven
Author by

B Seven

Status: Hood Rails on HTTP/2: Rails HTTP/2 Rack Gems: Rack Crud Rack Routing Capybara Jasmine

Updated on July 17, 2022

Comments

  • B Seven
    B Seven almost 2 years
    h  = { a: 1 }
    h2 = { b: 2 }
    h3 = { c: 3 }
    

    Hash#merge works for 2 hashes: h.merge(h2)

    How to merge 3 hashes?

    h.merge(h2).merge(h3) works but is there a better way?

  • Some Guy
    Some Guy over 10 years
    That works for me 'as is', but not so much when it's more like h1, h2, h3 = { a: 1, b: 4}, { b: 2, c: 5}, {c: 3, a: 6}
  • hirolau
    hirolau over 10 years
    Sorry, I was to quick to answer, see my update. In you example you have the key :a two times, one will be overwritten as a hash only can have distinct keys.
  • Boris Stitnicky
    Boris Stitnicky over 10 years
    The one thing @AGS is doing right here is using #merge! instead of #merge. #merge, alias #update, is significantly faster in this case.
  • ocodo
    ocodo about 10 years
    Note that array.reduce(&:merge) is going to give you the same result as inject (inject is just an alias of reduce)
  • Arnold Roa
    Arnold Roa almost 7 years
    What ** does?
  • bwv549
    bwv549 almost 7 years
    @ArnoldRoa - it unpackages the hash, analogous to the single splat operator ('*') for arrays.
  • engineersmnky
    engineersmnky over 6 years
    Just because I happened to come across this. In reduce and inject the accumulator (or memo) is passed as the initial argument to the block rather than the final argument as this suggests (unlike each_with_object)
  • Matt
    Matt over 6 years
    Ah! Thank you @engineersmnky for noticing this. I've updated my answer! Great catch!
  • jeffdill2
    jeffdill2 about 6 years
    Thanks @jayqui, that's a very important "gotcha".
  • Tim Lowrimore
    Tim Lowrimore almost 6 years
    @CyrilDuchon-Doris Yes, that is a downside of this approach.
  • zealouscoder
    zealouscoder over 5 years
    Can someone elaborate the first solution. I didn't understand what p "Hash[*a.map(&:to_a).flatten]" does
  • Rajan Verma - Aarvy
    Rajan Verma - Aarvy over 5 years
    Giving ArgumentError: wrong number of arguments (given 2, expected 1)
  • SRack
    SRack over 5 years
    Are you using Ruby 2.6 @RajanVerma? This is introduced there; with prior versions you'll get the error you're seeing.
  • CTS_AE
    CTS_AE over 5 years
    Worked well for me since I believe most of the other methods described here don't work for Ruby v1.9
  • Brennan
    Brennan over 5 years
    @ZealousCoder I'm late, but figured I'd drop an answer. Hash[] will accept a series of arguments and build a hash by pairing them up. As such, if you generate an ordered array from the hashes (via a.map(&:to_a).flatten) and use the splat operator to pass them as args, it will work nicely. However, Hash[] also accepts an array of 2-ary arrays representing k-v pairs and I would have written it as Hash[*a.flat_map(&:to_a)]
  • tiagomenegaz
    tiagomenegaz over 5 years
    Also note if for any reason h1 and h2 are {}. It'll raise and error. h.merge(**{}, **{})
  • User_coder
    User_coder almost 5 years
    This helped me immensely. Thank you.