Ruby. Merging a nested hash without overwriting

2019-05-19 08:43发布

问题:

I have a nested hash:

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

I want to merge a given nested hash:

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

such that:

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

What's the best way?

The hashes I'll be merging will have an equivalent key at an arbitrary depth of nested-ness. The value of the last nested hash will always be different from all the other hashes.

回答1:

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.