Ruby:合并嵌套哈希

时间:2012-02-21 16:48:33

标签: ruby hash merge nested

我想合并嵌套哈希。

a = {:book=>
    [{:title=>"Hamlet",
      :author=>"William Shakespeare"
      }]}

b = {:book=>
    [{:title=>"Pride and Prejudice",
      :author=>"Jane Austen"
      }]}

我希望合并为:

{:book=>
   [{:title=>"Hamlet",
      :author=>"William Shakespeare"},
    {:title=>"Pride and Prejudice",
      :author=>"Jane Austen"}]}

完成此任务的巢方式是什么?

9 个答案:

答案 0 :(得分:51)

对于rails 3.0.0及更高版本,deep_mergeActiveSupport功能完全符合您的要求。

答案 1 :(得分:46)

我找到了一个更通用的深度合并算法here,并使用它如下:

class ::Hash
    def deep_merge(second)
        merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
        self.merge(second, &merger)
    end
end

a.deep_merge(b)

答案 2 :(得分:32)

要添加Jon M和koendc的答案,下面的代码将处理哈希的合并,并且:nil如上所述,但它也将联合两个哈希中存在的任何数组(使用相同的键):

class ::Hash
    def deep_merge(second)
        merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : Array === v1 && Array === v2 ? v1 | v2 : [:undefined, nil, :nil].include?(v2) ? v1 : v2 }
        self.merge(second.to_h, &merger)
    end
end

a.deep_merge(b)

答案 3 :(得分:10)

出于各种原因 - 这只有在你想以同样的方式合并哈希中的所有键时才会起作用 - 你可以这样做:

a.merge(b) { |k, x, y| x + y }

当您将阻止传递给Hash#merge时,k是要合并的密钥,其中密钥存在于ab中,xa[k]y的值是b[k]的值。块的结果成为键k的合并散列中的值。

我认为在你的具体情况下,nkm的答案会更好。

答案 4 :(得分:6)

回答你的问题有点晚了,但我写了一段相当丰富的深度合并实用程序,现在由Daniel Deleo在Github上维护:https://github.com/danielsdeleo/deep_merge

它将完全按照您的意愿合并您的数组。从文档中的第一个示例:

所以如果你有两个像这样的哈希:

   source = {:x => [1,2,3], :y => 2}
   dest =   {:x => [4,5,'6'], :y => [7,8,9]}
   dest.deep_merge!(source)
   Results: {:x => [1,2,3,4,5,'6'], :y => 2}

它不会合并:y(因为int和数组不被认为是可合并的) - 使用bang(!)语法会导致源覆盖..使用非爆炸方法将使dest的内部值保持不变找到了不可合并的实体。它将添加包含在x中的数组,因为它知道如何合并数组。它处理包含任何数据结构的哈希的任意深度合并。

现在有更多关于Daniel的github回购的文档..

答案 5 :(得分:4)

所有答案都让我觉得过于复杂。这就是我最终想出的:

# @param tgt [Hash] target hash that we will be **altering**
# @param src [Hash] read from this source hash
# @return the modified target hash
# @note this one does not merge Arrays
def self.deep_merge!(tgt_hash, src_hash)
  tgt_hash.merge!(src_hash) { |key, oldval, newval|
    if oldval.kind_of?(Hash) && newval.kind_of?(Hash)
      deep_merge!(oldval, newval)
    else
      newval
    end
  }
end

P.S。使用公共,WTFPL或任何许可证

答案 6 :(得分:1)

以下是使用优化递归合并更好的解决方案,并且 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(第二个参数)传递给这些修改过的方法,它们会以递归方式合并哈希值。

用于简单用法的

示例写在this answer。这是一个高级示例。

这个问题的例子很糟糕,因为它与递归合并无关。以下行符合问题的例子:

a.merge!(b) {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}

让我举一个更好的例子来展示上面代码的强大功能。想象一下两个房间,每个房间都有一个书架。每个书架上有3排,每个书架目前有2本书。代码:

room1   =   {
    :shelf  =>  {
        :row1   =>  [
            {
                :title  =>  "Hamlet",
                :author =>  "William Shakespeare"
            }
        ],
        :row2   =>  [
            {
                :title  =>  "Pride and Prejudice",
                :author =>  "Jane Austen"
            }
        ]
    }
}

room2   =   {
    :shelf  =>  {
        :row2   =>  [
            {
                :title  =>  "The Great Gatsby",
                :author =>  "F. Scott Fitzgerald"
            }
        ],
        :row3   =>  [
            {
                :title  =>  "Catastrophe Theory",
                :author =>  "V. I. Arnol'd"
            }
        ]
    }
}

我们打算将书籍从第二个房间的书架移到第一个房间的书架上的相同行。首先,我们将在不设置recursive标记的情况下执行此操作,即与使用未修改的Hash::merge!相同:

room1.merge!(room2) {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}
puts room1

输出会告诉我们第一个房间的架子看起来像这样:

room1   =   {
    :shelf  =>  {
        :row2   =>  [
            {
                :title  =>  "The Great Gatsby",
                :author =>  "F. Scott Fitzgerald"
            }
        ],
        :row3   =>  [
            {
                :title  =>  "Catastrophe Theory",
                :author =>  "V. I. Arnol'd"
            }
        ]
    }
}

正如你所看到的,没有recursive迫使我们扔掉我们珍贵的书籍。

现在我们将做同样的事情,但将recursive标志设置为 true 。您可以作为第二个参数传递recursive=truetrue

room1.merge!(room2, true) {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}
puts room1

现在输出会告诉我们实际上我们移动了我们的书:

room1   =   {
    :shelf  =>  {
        :row1   =>  [
            {
                :title  =>  "Hamlet",
                :author =>  "William Shakespeare"
            }
        ],
        :row2   =>  [
            {
                :title  =>  "Pride and Prejudice",
                :author =>  "Jane Austen"
            },
            {
                :title  =>  "The Great Gatsby",
                :author =>  "F. Scott Fitzgerald"
            }
        ],
        :row3   =>  [
            {
                :title  =>  "Catastrophe Theory",
                :author =>  "V. I. Arnol'd"
            }
        ]
    }
}

最后一次执行可以改写如下:

room1 = room1.merge(room2, recursive=true) do |k, v1, v2|
    if v1.is_a?(Array) && v2.is_a?(Array)
        v1+v2
    else
        v2
    end
end
puts room1

block = Proc.new {|k,v1,v2| [v1, v2].all? {|v| v.is_a?(Array)} ? v1+v2 : v2}
room1.merge!(room2, recursive=true, &block)
puts room1

那就是它。另请查看我的递归版Hash::eachHash::each_pairhere

答案 7 :(得分:0)

我认为Jon M的答案是最好的,但是当你使用nil / undefined值合并哈希时,它会失败。 此更新解决了此问题:

class ::Hash
    def deep_merge(second)
        merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : [:undefined, nil, :nil].include?(v2) ? v1 : v2 }
        self.merge(second, &merger)
    end
end

a.deep_merge(b)

答案 8 :(得分:-1)

a[:book] = a[:book] + b[:book]

或者

a[:book] <<  b[:book].first