Stary
Stary

Reputation: 188

Merge two hashes and return common data

I have two hashes:

h1 = { "a" => 100, "b" => 200 }
h2 = { "b" => 254, "c" => 300 }

I need to return hash such as:

h3 = { 
  "a" => { "h1" => 100, "h2" => nil},
  "b" => { "h1" => 200, "h2" => 254},
  "c" => { "h1" => nil, "h2" => 300}
}

I am trying to use:

h1.merge(h2) { |k, val, old| { 'h1' => val, 'h2' => old } }

but it return only:

{"a"=>100, "b"=>{"h1"=>200, "h2"=>254}, "c"=>300}

without nil. Any ideas?

Upvotes: 2

Views: 68

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110665

Here is another way that is somewhat general in terms of the numbers of hashes and hash labels:

h1 = { "a" => 100, "b" => 200 }
h2 = { "b" => 254, "c" => 300 }
h3 = { "c" => 111, "b" => 222 }
a = [h1,h2,h3]
labels = {h1=>"h1", h2=>"h2", h3=>"h3"}

h.reduce([]) { |keys,h| keys | h.keys }
 .each_with_object({}) { |k,h|
   h[k] = a.each_with_object({}) { |f,g| g[labels[f]] = f[k] } }
  #=> {"a"=>{"h1"=>100, "h2"=>nil, "h3"=>nil},
  #    "b"=>{"h1"=>200, "h2"=>254, "h3"=>222},
  #    "c"=>{"h1"=>nil, "h2"=>300, "h3"=>111}} 

The steps:

keys = h.reduce([]) { |keys,h| keys | h.keys }
  #=> ["a", "b", "c"]
enum = keys.each_with_object({})
  #=> #<Enumerator: ["a", "b", "c"]:each_with_object({})> 
k,h = enum.next
  #=> ["a", {}] 
h[k] = a.each_with_object({}) { |f,g| g[labels[f]] = f[k] }
  #=> {"h1"=>100, "h2"=>nil, "h3"=>nil} 
k,h = enum.next
  #=> ["b", {"a"=>{"h1"=>100, "h2"=>nil, "h3"=>nil}}] 
h[k] = a.each_with_object({}) { |f,g| g[labels[f]] = f[k] }
  #=> {"h1"=>200, "h2"=>254, "h3"=>222} 
k,h = enum.next
  #=> ["c", {"a"=>{"h1"=>100, "h2"=>nil, "h3"=>nil},
  #    "b"=>{"h1"=>200, "h2"=>254, "h3"=>222}}] 
h[k] = a.each_with_object({}) { |f,g| g[labels[f]] = f[k] }
  #=> {"h1"=>nil, "h2"=>300, "h3"=>111} 
h
  #=> {"a"=>{"h1"=>100, "h2"=>nil, "h3"=>nil},
  #    "b"=>{"h1"=>200, "h2"=>254, "h3"=>222},
  #    "c"=>{"h1"=>nil, "h2"=>300, "h3"=>111}} 

Upvotes: 1

Stefan
Stefan

Reputation: 114138

The block is only invoked for duplicate keys. You can either ensure that both hashes contain all keys:

h1 = { "a" => 100, "b" => 200, "c" => nil }
h2 = { "a" => nil, "b" => 254, "c" => 300 }

h1.merge(h2) { |k, val, old| { 'h1' => val, 'h2' => old } }
#=> {"a"=>{"h1"=>100, "h2"=>nil},
#    "b"=>{"h1"=>200, "h2"=>254},
#    "c"=>{"h1"=>nil, "h2"=>300}}

Or build a new hash yourself, e.g. using the keys from both hashes:

h1 = { "a" => 100, "b" => 200 }
h2 = { "b" => 254, "c" => 300 }

(h1.keys | h2.keys).map { |k| [k, { 'h1' => h1[k], 'h2' => h2[k] }] }.to_h
#=> {"a"=>{"h1"=>100, "h2"=>nil},
#    "b"=>{"h1"=>200, "h2"=>254},
#    "c"=>{"h1"=>nil, "h2"=>300}}

Upvotes: 3

shivam
shivam

Reputation: 16506

If you read the official document for merge (here) it say:

the value for each duplicate key is determined by calling the block with the key, its value in hsh and its value in other_hash.

This means for keys "a" and "c" , the block is never called (as they are not duplicate keys), as a result you are missing nil in your result hash.

You can try this instead:

h3 = {}
(h1.keys + h2.keys).uniq.each{|a| h3[a] = {"h1" => h1[a], "h2" => h2[a]}}
h3
# => {"a"=>{"h1"=>100, "h2"=>nil}, "b"=>{"h1"=>200, "h2"=>254}, "c"=>{"h1"=>nil, "h2"=>300}}

Upvotes: 2

Related Questions