在Ruby中合并多维哈希

我有两个哈希,其结构与此类似:

hash_a = { :a => { :b => { :c => "d" } } } hash_b = { :a => { :b => { :x => "y" } } } 

我想将这些合并在一起以产生以下哈希:

 { :a => { :b => { :c => "d", :x => "y" } } } 

merge函数将在第一个哈希值中替换:a的值,其值为:第二个哈希值中的a。 所以,我编写了自己的递归合并函数,如下所示:

 def recursive_merge( merge_from, merge_to ) merged_hash = merge_to first_key = merge_from.keys[0] if merge_to.has_key?(first_key) merged_hash[first_key] = recursive_merge( merge_from[first_key], merge_to[first_key] ) else merged_hash[first_key] = merge_from[first_key] end merged_hash end 

但是我遇到了运行时错误: can't add a new key into hash during iteration 。 在Ruby中合并这些哈希的最佳方法是什么?

如果你将recursive_merge的第一行更改为

 merged_hash = merge_to.clone 

它按预期工作:

 recursive_merge(hash_a, hash_b) -> {:a=>{:b=>{:c=>"d", :x=>"y"}}} 

在您浏览哈希时更改哈希是很麻烦的,您需要一个“工作区”来累积结果。

Ruby现有的Hash#merge允许使用块forms来解决重复项,这使得它非常简单。 我添加了将树的“叶子”中的多个冲突值合并为数组的function; 你可以选择选择一个或另一个。

 hash_a = { :a => { :b => { :c => "d", :z => 'foo' } } } hash_b = { :a => { :b => { :x => "y", :z => 'bar' } } } def recurse_merge(a,b) a.merge(b) do |_,x,y| (x.is_a?(Hash) && y.is_a?(Hash)) ? recurse_merge(x,y) : [*x,*y] end end p recurse_merge( hash_a, hash_b ) #=> {:a=>{:b=>{:c=>"d", :z=>["foo", "bar"], :x=>"y"}}} 

或者,作为一个干净的猴子补丁:

 class Hash def merge_recursive(o) merge(o) do |_,x,y| if x.respond_to?(:merge_recursive) && y.is_a?(Hash) x.merge_recursive(y) else [*x,*y] end end end end p hash_a.merge_recursive hash_b #=> {:a=>{:b=>{:c=>"d", :z=>["foo", "bar"], :x=>"y"}}} 

你可以在一行中完成:

 merged_hash = hash_a.merge(hash_b){|k,hha,hhb| hha.merge(hhb){|l,hhha,hhhb| hhha.merge(hhhb)}} 

如果你想merge结果merge到hash_a中,只需用方法合并替换方法merge!

如果您使用rails 3或rails 4框架,则更容易:

 merged_hash = hash_a.deep_merge(hash_b) 

要么

 hash_a.deep_merge!(hash_b) 

试试这个猴子修补解决方案:

 class Hash def recursive_merge(hash = nil) return self unless hash.is_a?(Hash) base = self hash.each do |key, v| if base[key].is_a?(Hash) && hash[key].is_a?(Hash) base[key].recursive_merge(hash[key]) else base[key]= hash[key] end end base end end 

为了将其中一个合并到另一个作为建议的故障单,您可以修改@Phrogzfunction

 def recurse_merge( merge_from, merge_to ) merge_from.merge(merge_to) do |_,x,y| (x.is_a?(Hash) && y.is_a?(Hash)) ? recurse_merge(x,y) : x end end 

如果存在重复键,它将仅使用merge_from哈希的内容

对于使用细化的 递归合并而言 ,这是更好的解决方案,并且具有bang方法块支持 。 这段代码适用于 Ruby。

 module HashRecursive refine Hash do def merge(other_hash, recursive=false, &block) if recursive block_actual = Proc.new {|key, oldval, newval| newval = block.call(key, oldval, newval) if block_given? [oldval, newval].all? {|v| v.is_a?(Hash)} ? oldval.merge(newval, &block_actual) : newval } self.merge(other_hash, &block_actual) else super(other_hash, &block) end end def merge!(other_hash, recursive=false, &block) if recursive self.replace(self.merge(other_hash, recursive, &block)) else super(other_hash, &block) end end end end using HashRecursive 

using HashRecursive后执行你可以使用默认的Hash::mergeHash::merge! 好像他们没有被修改过。 您可以像以前一样使用这些方法的

新的事情是你可以将boolean recursive (第二个参数)传递给这些修改过的方法,它们将以递归方式合并哈希值。


回答问题的示例用法。 这非常简单:

 hash_a = { :a => { :b => { :c => "d" } } } hash_b = { :a => { :b => { :x => "y" } } } puts hash_a.merge(hash_b) # Won't override hash_a # output: { :a => { :b => { :x => "y" } } } puts hash_a # hash_a is unchanged # output: { :a => { :b => { :c => "d" } } } hash_a.merge!(hash_b, recursive=true) # Will override hash_a puts hash_a # hash_a was changed # output: { :a => { :b => { :c => "d", :x => "y" } } } 

有关高级示例,请查看此答案 。

另外在这里看看我的Hash::eachHash::each_pair )的递归版本。