mateo
mateo

Reputation: 319

How to merge hash of hashes and set default value if value don't exists

I need to merge values of hash a into out with sort keys in a.

a = {"X"=>{12=>1, 11=>4}, "Y"=>{11=>5}, "Z"=>{12=>5}}

out = [
  {"X": [4, 1]},
  {"Y": [5, 0]},
  {"Z": [0, 5]},
]

Upvotes: 0

Views: 1975

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110725

Code

def modify_values(g)
  sorted_keys = g.reduce([]) {|arr,(_,v)| arr | v.keys}.sort
  g.each_with_object({}) {|(k,v),h| h[k] = Hash.new(0).merge(v).values_at(*sorted_keys)}
end

Example

g = {"X"=>{12=>1, 11=>4}, "Y"=>{11=>5}, "Z"=>{12=>5}}
modify_values(g)
  #=> {"X"=>[4, 1], "Y"=>[5, 0], "Z"=>[0, 5]}

Explanation

The steps are as follows (for the hash a in the example). First obtain an array of the unique keys from g's values (see Enumerable#reduce and Array#|), then sort that array.

b = a.reduce([]) {|arr,(_,v)| arr | v.keys}
  #=> [12, 11] 
sorted_keys = b.sort
  #=> [11, 12]

The first key-value pair of a, together with an empty hash, is passed to each_with_object's block. The block variables are computed using parallel assignment:

(k,v),h = [["X", {12=>1, 11=>4}], {}] 
k #=> "X" 
v #=> {12=>1, 11=>4} 
h #=> {} 

The block calculation is then performed. First an empty hash with a default value 0 is created:

f = Hash.new(0)
  #=> {}

The hash v is then merged into f. The result is hash with the same key-value pairs as v but with a default value of 0. The significance of the default value is that if f does not have a key k, f[k] returns the default value. See Hash::new.

 g = f.merge(v)
   #=> {12=>1, 11=>4} 
 g.default
   #=> 0 (yup)

Then extract the values corresponding to sorted_keys:

 h[k] = g.values_at(*sorted_keys)
   #=> {12=>1, 11=>4}.values_at(11, 12) 
   #=> [4, 1]

When a's next key-value pair is passed to the block, the calculations are as follows.

(k,v),h = [["Y", {11=>5}], {"X"=>[4, 1]}] # Note `h` has been updated 
k #=> "Y" 
v #=> {11=>5} 
h #=> {"X"=>[4, 1]}
f = Hash.new(0)
  #=> {} 
g = f.merge(v)
  #=> {11=>5} 
h[k] = g.values_at(*sorted_keys)
  #=> {11=>5}.values_at(11, 12) 
  #=> [5, 0] (Note h[12] equals h's default value)

and now

h #=> {"X"=>[4, 1], "Y"=>[5, 0]} 

The calculation for the third key-value pair of a is similar.

Upvotes: 1

spickermann
spickermann

Reputation: 106972

I would do something like this:

a = {"X"=>{12=>1, 11=>4}, "Y"=>{11=>5}, "Z"=>{12=>5}}

sorted_keys = a.values.flat_map(&:keys).uniq.sort
#=> [11, 12]
a.map { |k, v| { k => v.values_at(*sorted_keys).map(&:to_i) } }
#=> [ { "X" => [4, 1] }, { "Y" => [5, 0] }, { "Z" => [0, 5] }]

Upvotes: 2

Related Questions