【问题标题】:Merging multi-dimensional hashes in Ruby在 Ruby 中合并多维哈希
【发布时间】:2011-07-31 16:16:01
【问题描述】:

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

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 中合并这些哈希的最佳方法是什么?

【问题讨论】:

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


    【解决方案1】:

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

    【讨论】:

    • “干净的猴子补丁”?这不是矛盾吗? :-)
    • 谢谢!我的用例的完美解决方案,只需稍作修改
    【解决方案2】:

    你可以在一行中完成:

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

    如果你想立即将merge结果放入hash_a,只需将方法merge替换为方法merge!

    如果您使用的是 rails 3 或 rails 4 框架,那就更简单了:

    merged_hash = hash_a.deep_merge(hash_b)
    

    hash_a.deep_merge!(hash_b)
    

    【讨论】:

      【解决方案3】:

      如果你把recursive_merge的第一行改成

      merged_hash = merge_to.clone
      

      它按预期工作:

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

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

      【讨论】:

        【解决方案4】:

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

        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
        

        【讨论】:

          【解决方案5】:

          为了按照票证建议将一个合并到另一个,您可以修改@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
          

          如果有重复key,只会使用merge_fromhash的内容

          【讨论】:

            【解决方案6】:

            这里是更好的递归合并解决方案,它使用refinements并具有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!,就好像它们没有被修改一样。您可以像以前一样在这些方法中使用blocks

            新功能是您可以将布尔值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::each(Hash::each_pair)here

            【讨论】:

              猜你喜欢
              • 2015-06-13
              • 1970-01-01
              • 1970-01-01
              • 2016-01-06
              • 1970-01-01
              • 2012-01-14
              • 2019-02-21
              • 2019-08-08
              相关资源
              最近更新 更多