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
Project Euler 1:Find the Sum of All the Multiples of 3 or 5 Below 1000
Errno::Econnrefused: Connection Refused - Connect(2) for Action Mailer
Is There a Built-In Binary-Search in Ruby
Typing 'Rails Console' Doesn't Start
Irb History Not Working with Ruby 2.3.0
Mocha Mock Carries to Another Test
Why Do I Get Uninitialized Constant Devise Name Error When Running Webrick Server
Running into Smtp Error When Trying to Send Email in Ror App
How to Dynamically Create a Local Variable in Ruby
Store the Day of the Week and Time
Supporting Ruby 1.9's Hash Syntax in Ruby 1.8
How to Debug in Rubymine 4.5 Using Ruby 1.9.3
Using Ruby Convert Numbers to Words
Problems with the Rails Console, Rvm and Readline