在Ruby中合并多维哈希

时间:2011-04-07 12:49:08

标签: ruby-on-rails ruby ruby-on-rails-3

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

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

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

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

合并函数将在第一个散列中替换: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中合并这些哈希的最佳方法是什么?

6 个答案:

答案 0 :(得分:10)

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

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"}}}

答案 1 :(得分:8)

您可以在一行中完成:

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

如果你希望将结果merge输入hash_a,只需用方法merge!替换方法合并

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

merged_hash = hash_a.deep_merge(hash_b)

hash_a.deep_merge!(hash_b)

答案 2 :(得分:7)

如果将recursive_merge的第一行更改为

merged_hash = merge_to.clone

它按预期工作:

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

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

答案 3 :(得分:2)

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

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

答案 4 :(得分:0)

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

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哈希

的内容

答案 5 :(得分:0)

以下是使用优化递归合并更好的解决方案,并且 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" } } }

对于高级示例,请查看this answer

另请查看我的递归版Hash::eachHash::each_pairhere