Jaugar Chang
Jaugar Chang

Reputation: 3196

How to merge old hash with new hash, that values should be null when keys not in new hash

Assuming I got a hash of old properties from an object:

old = {a: 1, b: 2, c: nil}

Then I got a hash of new properties from user input (all keys must exists in old hash):

new = {a: 2}

Finally I want get a hash of properties to update to the object like this:

upd = {a: 2, b: nil, c: nil} 
# upd = {a: 2, b: nil} would be better

Now I use map to implement this:

upd = Hash[old.map{|x| [ x[0],new[x[0]] ] } ]

I tried merge but couldn't get what I want:

old.merge(new){|k,old,new| new} #=> {:a=>2, :b=>2, :c=>nil}

But I think there must be some better way of doing this. Any help will be appreciated.

Upvotes: 0

Views: 1864

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110725

This is one way:

old.merge(old) { |k,_,_| new[k] }
 #=> {a: 2, b: nil, c: nil}

This uses the form of the method Hash#merge that takes a block. The purpose of the block is to resolve values for keys that are present in both hashes involved in the merge.

Here I am merging old with itself, so the value for every key in old is determined by the block.

An array [key, old_value, new_value] is passed to the block. In this case old_value and new_value are the same, but as I will not be using that value in the block, I have replaced the two corresponding block variables with the placeholder _.

In the block I compute the value for key k to be new[k], which is nil if new does not have a key k.

This approach could also be implemented with Enumerable#reduce (a.k.a inject):

old.reduce(old) { |h,(k,_)| h[k] = new[k]; h }

Upvotes: 2

Amit Kumar Gupta
Amit Kumar Gupta

Reputation: 18587

old.inject(new.dup) { |h, (k,v)| h[k] = nil unless (v.nil? || new.has_key?(k)); h }
# => {a: 2, b: nil}

Basically, it says to start with a copy of new. Then build up the resulting hash h by iterating over the (k,v) pairs in old.

The only extra things we want in addition to what's in new is when there was a key in old with a non-nil value, but now the key is not present in new and so we want to indicate that by having the key in the result, but with a nil value. So if (k,v) is a par in old, and h is the result we're building, then we want to add h[k] = nil if v was not nil, but k wasn't even a key in new. Since there's a bunch of negations in there, it's simpler to express it with an unless, resulting in the form above.

Upvotes: 1

Cristian Lupascu
Cristian Lupascu

Reputation: 40566

I don't think there's an out-of-the-box way to do what you want, so your current solution seems OK.

You could avoid the [0], by doing either:

upd = Hash[old.map{|k, v| [k, new[k]] }]

or

upd = Hash[old.keys.map{|k| [k, new[k]] }]

One caveat eith the current solution: if a key exists in new, but does not exist in old, it will not be present in upd. You may be aware of this, but I thought it should be pointed out.

Upvotes: 2

Related Questions