How could I merge two hashes that results in no new keys, meaning the merge would merge keys that exist in both hashes?
For example, I want the following:
h = {:foo => "bar"}
j = {:foo => "baz", :extra => "value"}
puts h.merge(j) # {:foo => "baz"}
I'm looking for a really clean way of doing this as my current implementation is pretty messy.
You could remove keys that weren't in the first hash from the second hash, then merge:
h.merge j.select { |k| h.keys.include? k }
Unlike my edited-out alternative, this is safe if you decide to change it to a merge! or update.
Yjerem's answer works in Ruby 1.9, but not in 1.8.x. In 1.8.x the Hash#select method returns an array. Hash#reject returns a hash.
h.reject { |k,v| !j.keys.include? k }
If you want to keep only key-value pairs that have identical values, you can do this:
h.reject { |k,v| j[k] != h[k] }
The edge case there is nils. If you are storing nils in your Hash then you have to do this:
h.reject { |k,v| !j.has_key? k or j[k] != h[k] }
If you're using activesupport (part of rails), you can take advantage of 2 extra methods on Hash:
Hash#slicetakes the desired keys as separate arguments (not an array of keys) and returns a new hash with just the keys you asked for.Hash#excepttakes the same arguments asslice, but returns a new hash with keys that were not in the arguments.
First load activesupport:
require 'active_support/core_ext'
Merge only entries from j whose keys are already in h (i.e. modify, but don't add any or remove entries in h):
h.merge(j.slice(*h.keys))
Example:
ignore_new = ->(h, j) { h.merge(j.slice(* h.keys)) }
ignore_new.({a: 1, b: 2, c: 3}, {b: 10, c: 11, d: 12})
# => {:a=>1, :b=>10, :c=>11}
Get the leftovers from j that weren't in h:
j.except(*h.keys)
Bonus:
If you want true intersection, meaning you want a result that only has keys that are in common between the 2 hashes, do this:
h.merge(j).slice(* ( h.keys & j.keys ) )
Example:
intersect = ->(h, j) { h.merge(j).slice(* (h.keys & j.keys) ) }
intersect.({a: 1, b: 2, c: 3}, {b: 10, c: 11, d: 12})
# => {:b=>10, :c=>11}
and leftovers from h that weren't in j:
h.except(*j.keys)
You may also want to use activesupport's HashWithIndifferentAccess if you want string & symbol key-access to be considered equivalent.
Note that none of the above examples change the original hashes; new hashes are returned instead.
[h].inject({}) { |m,e| e.merge(j) { |k,o,n| m[k] = n }; m}
or
[{}].inject(h) { |m,e| m.merge(j) { |k,o,n| e[k] = n }; e}
or (probably the best, but not technically a single expression) ...
t = {}; h.merge(j) { |k,o,n| t[k] = n }; t
The more customized way of doing this is:
h = {"foo"=> "bar"}
j = {"foo" => "baz", "extra" => "value"}
k = h.merge(j)
result: {"foo"=>"baz", "extra"=>"value"}
Here the key "foo" in the second hash is overriding the "foo" in first hash.But if you want to keep the old value i.e bar or if you want keep the new value i.e "baz"? You can do something like this:
k = h.merge(j){|key, old, new| old}
result: {"foo"=>"bar", "extra"=>"value"}
k = h.merge(j){|key, old, new| new}
result: {"foo"=>"baz", "extra"=>"value"}
来源:https://stackoverflow.com/questions/4825069/how-to-merge-two-hashes-with-no-new-keys