Reputation: 6030
If I want to recursively merge 2 hashes, I can do so with the following function:
def recursive_merge(a,b)
a.merge(b) {|key,a_item,b_item| recursive_merge(a_item,b_item) }
end
This works great, in that I can now do:
aHash = recursive_merge(aHash,newHash)
But I'd like to add this as a self-updating style method similar to merge!
. I can add in the returning function:
class Hash
def recursive_merge(newHash)
self.merge { |key,a_item,b_item| a_item.recursive_merge(b_item) }
end
end
But am not sure how to re-create the bang
function that updates the original object without association.
class Hash
def recursive_merge!(newHash)
self.merge { |key,a_item,b_item| a_item.recursive_merge(b_item) }
# How do I set "self" to this new hash?
end
end
edit example as per comments.
h={:a=>{:b => "1"}
h.recursive_merge!({:a=>{:c=>"2"})
=> {:a=>{:b=>"1", :c="2"}}
The regular merge results in :b=>"1"
being overwritten by :c="2"
Upvotes: 2
Views: 2437
Reputation: 772
Use merge!
rather than attempt to update self
. I don't believe it makes sense to use merge! anywhere but at the top level, so I wouldn't call the bang version recursively. Instead, use merge! at the top level, and call the non-bang method recursively.
It may also be wise to check both values being merged are indeed hashes, otherwise you may get an exception if you attempt to recursive_merge on a non-hash object.
#!/usr/bin/env ruby
class Hash
def recursive_merge(other)
self.merge(other) { |key, value1, value2| value1.is_a?(Hash) && value2.is_a?(Hash) ? value1.recursive_merge(value2) : value2}
end
def recursive_merge!(other)
self.merge!(other) { |key, value1, value2| value1.is_a?(Hash) && value2.is_a?(Hash) ? value1.recursive_merge(value2) : value2}
end
end
h1 = { a: { b:1, c:2 }, d:1 }
h2 = { a: { b:2, d:4 }, d:2 }
h3 = { d: { b:1, c:2 } }
p h1.recursive_merge(h2) # => {:a=>{:b=>2, :c=>2, :d=>4}, :d=>2}
p h1.recursive_merge(h3) # => {:a=>{:b=>1, :c=>2}, :d=>{:b=>1, :c=>2}}
p h1.recursive_merge!(h2) # => {:a=>{:b=>2, :c=>2, :d=>4}, :d=>2}
p h1 # => {:a=>{:b=>2, :c=>2, :d=>4}, :d=>2}
If you have a specific reason to fully merge in place, possibly for speed, you can experiment with making the second function call itself recursively, rather than delegate the recursion to the first function. Be aware that may produce unintended side effects if the hashes store shared objects.
Example:
h1 = { a:1, b:2 }
h2 = { a:5, c:9 }
h3 = { a:h1, b:h2 }
h4 = { a:h2, c:h1 }
p h3.recursive_merge!(h4)
# Making recursive calls to recursive_merge
# => {:a=>{:a=>5, :b=>2, :c=>9}, :b=>{:a=>5, :c=>9}, :c=>{:a=>1, :b=>2}}
# Making recursive calls to recursive_merge!
# => {:a=>{:a=>5, :b=>2, :c=>9}, :b=>{:a=>5, :c=>9}, :c=>{:a=>5, :b=>2, :c=>9}}
As you can see, the second (shared) copy of h1 stored under the key :c is updated to reflect the merge of h1 and h2 under the key :a. This may be surprising and unwanted. Hence why I recommend using recursive_merge
for the recursion, and not recursive_merge!
.
Upvotes: 1