How to merge multiple hashes in Ruby?
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 Symbol
s.
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!
B Seven
Status: Hood Rails on HTTP/2: Rails HTTP/2 Rack Gems: Rack Crud Rack Routing Capybara Jasmine
Updated on July 17, 2022Comments
-
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 over 10 yearsThat 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 over 10 yearsSorry, 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 over 10 yearsThe one thing @AGS is doing right here is using
#merge!
instead of#merge
.#merge
, alias#update
, is significantly faster in this case. -
ocodo about 10 yearsNote that
array.reduce(&:merge)
is going to give you the same result asinject
(inject
is just an alias ofreduce
) -
Arnold Roa almost 7 yearsWhat
**
does? -
bwv549 almost 7 years@ArnoldRoa - it unpackages the hash, analogous to the single splat operator ('*') for arrays.
-
engineersmnky over 6 yearsJust because I happened to come across this. In
reduce
andinject
the accumulator (or memo) is passed as the initial argument to the block rather than the final argument as this suggests (unlikeeach_with_object
) -
Matt over 6 yearsAh! Thank you @engineersmnky for noticing this. I've updated my answer! Great catch!
-
jeffdill2 about 6 yearsThanks @jayqui, that's a very important "gotcha".
-
Tim Lowrimore almost 6 years@CyrilDuchon-Doris Yes, that is a downside of this approach.
-
zealouscoder over 5 yearsCan someone elaborate the first solution. I didn't understand what p "Hash[*a.map(&:to_a).flatten]" does
-
Rajan Verma - Aarvy over 5 yearsGiving
ArgumentError: wrong number of arguments (given 2, expected 1)
-
SRack over 5 yearsAre you using Ruby 2.6 @RajanVerma? This is introduced there; with prior versions you'll get the error you're seeing.
-
CTS_AE over 5 yearsWorked well for me since I believe most of the other methods described here don't work for Ruby v1.9
-
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 (viaa.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 asHash[*a.flat_map(&:to_a)]
-
tiagomenegaz over 5 yearsAlso note if for any reason
h1
andh2
are{}
. It'll raise and error.h.merge(**{}, **{})
-
User_coder almost 5 yearsThis helped me immensely. Thank you.