How to Merge Two Hashes Without Overwritten Duplicate Keys in Ruby

How can I merge two hashes without overwritten duplicate keys in Ruby?

If you have two hashes, options and defaults, and you want to merge defaults into options without overwriting existing keys, what you really want to do is the reverse: merge options into defaults:

options = defaults.merge(options)

Or, if you're using Rails you can do:

options.reverse_merge!(defaults)

In ruby how can we merge two given hash into single hash and replace duplicate key value with greater of both the value

hash1 = { "a" => "123", "b" => "432" }

hash2 = { "a" => "439", "c" => "987" }

Code

h = hash1.merge(hash2) do |k, f, s|
f.to_i > s.to_i ? f : s
end
p h

Output

{"a"=>"439", "b"=>"432", "c"=>"987"}

Ruby Merge Two Hashes With Selective Overwrite

p a.merge(b) { |k,v1,v2| v1.is_a?(String) ? v1 : v1 | v2 }
# => {"word"=>"phileas", "character"=>1, "location"=>1, "adjective"=>1, "noun"=>1}

It actually makes more sense to check if your key is of type Fixnum, so you are sure it supports the logical OR operator, and leave other values unchanged:

p a.merge(b) { |k,v1,v2| v1.is_a?(Fixnum) ? v1 | v2 : v1 }
# => {"word"=>"phileas", "character"=>1, "location"=>1, "adjective"=>1, "noun"=>1}

Merge nested hash without overwritting in Ruby

For Rails there is the deep_merge function for ActiveSupport that does exactly what you ask for.

You can implement the same for yourself as follows:

class ::Hash
def deep_merge(second)
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
self.merge(second, &merger)
end
end

Now,

h1 = {"a"=>"text"}
h2 = {"b"=>{"x"=>"hola"}}
h3 = {"b"=>{"y"=>"pto"}}
h1.deep_merge(h2).deep_merge(h3)
# => {"a"=>"text", "b"=>{"x"=>"hola", "y"=>"pto"}}

Ruby. Merging a nested hash without overwriting

If you're sure that all the duplicate keys have values that are Hashes, you can use a recursive Hash#merge with block :

def deep_merge(h1,h2)
h1.merge(h2){|k,v1,v2| deep_merge(v1,v2) }
end

With your example :

{["X", 1, 2, 3]=>
{["X", "O", 2, 3]=>
{["X", "O", "X", 3]=>["X", "O", "X", "O"],
["X", "O", 2, "X"]=>["X", "O", "O", "X"]}}}

NOTE: This method doesn't work in the general case, and shouldn't be used for anything else than the structure defined in the question. It will fail for deep_merge({a:1},{a:2}).

If you don't have information about the nested keys and values :

def deep_merge(h1,h2)
h1.merge(h2){|k,v1,v2| v1.is_a?(Hash) && v2.is_a?(Hash) ? deep_merge(v1,v2) : v2}
end

In case of a conflict with values that aren't both Hashes, the second value will overwrite the first one.

Both methods return a new hash, and do not modify either h1 or h2.

NOTE: This method is available in Rails.

Ruby - Merge two hashes and maintain ordered keys

(a.values + b.values).uniq.map.with_index{|v, i| [i.to_s, v]}.to_h
# => {"0"=>"name", "1"=>"email", "2"=>"source", "3"=>"info", "4"=>"extra"}

ruby merge two hash by same key and same values

Use Hash#merge

a = {:ip=>"192.168.2.1", :b=>2}
b = {:ip=>"192.168.2.1", :c=>4}
newhash = a.merge(b)
#=> {:ip=>"192.168.2.1", :b=>2, :c=>4}

Preserving key value pairs for duplicate keys when merging two Arrays to a hash

There are three standard ways of doing this.

a1 = ["farmer_joe", "farmer_judy", "farmer_crazy_eyes", "farmer_joe"]
a2 = ["pigs", "chickens", "elephants", "cows"]
pairs = a1.zip(a2) # or [a1,a2].transpose
#=> [["farmer_joe", "pigs"], ["farmer_judy", "chickens"],
# ["farmer_crazy_eyes", "elephants"], ["farmer_joe", "cows"]]

1. Use Hash.new to create a hash with a default value of an empty array

pairs.each_with_object(Hash.new { |h,k| h[k]=[] }) { |(f,l),h| h[f] << l }
# => {"farmer_joe"=>["pigs", "cows"], "farmer_judy"=>["chickens"],
# "farmer_crazy_eyes"=>["elephants"]}

A variant of this (which tends to be slightly faster) is:

pairs.each_with_object({}) { |(f,l),h| (h[f] ||= []) << l }

2. Use the form of Hash#update (aka merge!) that takes a block to determine the values of keys present in both hashes being merged

pairs.each_with_object({}) { |(f,l),h| h.update(f=>[l]) { |_,o,n| o+n } }
#=> {"farmer_joe"=>["pigs", "cows"], "farmer_judy"=>["chickens"],
# "farmer_crazy_eyes"=>["elephants"]}

3. Use Enumerable#group_by

h = pairs.group_by(&:first)
#=> {"farmer_joe"=>[["farmer_joe", "pigs"], ["farmer_joe", "cows"]],
# "farmer_judy"=>[["farmer_judy", "chickens"]],
# "farmer_crazy_eyes"=>[["farmer_crazy_eyes", "elephants"]]}
h.keys.each { |k| h[k] = h[k].map(&:last) }
h
#=> {"farmer_joe"=>["pigs", "cows"], "farmer_judy"=>["chickens"],
# "farmer_crazy_eyes"=>["elephants"]}

There are many alternative to the last two lines, one being:

h.merge(h) { |*_,v| v.map(&:last) }

Merge multiple hashes after new key

Using merge! is fine and well understood. However, as you are setting a single key on your base_options hash, you can also simple use the hash accessor, i.e.

base_options[:checks] = document_check.merge!(identity_check, dummy_check)

Note that this will also change the document_hash object as merge! modified the receiver. If this is not desired, you can also use merge and return a new Hash. Thus could look like:

base_options[:checks] = document_check.merge(identity_check, dummy_check)

or equivalently

base_options[:checks] = {}.merge!(document_check, identity_check, dummy_check)

The latter option is slightly slower but might better show your intended behavior and is thus easier to understand to readers of your code.



Related Topics



Leave a reply



Submit