Ruby:合并嵌套哈希

我想合并一个嵌套的哈希。

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

做到这一点的最佳方法是什么?

user1223862 asked 2020-02-20T16:03:44Z
9个解决方案
52 votes

对于Rails 3.0.0+或更高版本,ActiveSupport的deep_merge函数可以完全满足您的要求。

xlembouras answered 2020-02-20T16:03:55Z
46 votes

我在这里找到了更通用的深度合并算法,并按如下方式使用它:

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)
Jon M answered 2020-02-20T16:04:15Z
35 votes

为了补充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)
Dan answered 2020-02-20T16:04:37Z
10 votes

出于多样性的考虑-只有在您希望以相同的方式合并哈希中的所有键时,这才起作用-您可以这样做:

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

当您将一个块传递给Hash#merge时,2756110584570708708993是要合并的密钥,其中ab中都存在该密钥,其中xa[k]的值,而a[k]y998的值。块的结果成为值 密钥k的哈希值。

我认为在您的特定情况下,nkm的答案更好。

Russell answered 2020-02-20T16:05:07Z
6 votes

回答您的问题有点晚了,但我不久前写了一个相当丰富的深度合并实用程序,该实用程序现在由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和array不被认为是可合并的)-使用bang(!)语法会导致源被覆盖。使用non-bang方法将在无法合并的实体为dest时保留dest的内部值。 找到了。 它将把包含在:x中的数组加在一起,因为它知道如何合并数组。 它处理包含任何数据结构的哈希的任意深度合并。

现在,Daniel的github存储库上有更多文档。

Steve Midgley answered 2020-02-20T16:05:45Z
4 votes

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

# @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

附言 公开使用,WTFPL或任何许可

akostadinov answered 2020-02-20T16:06:10Z
1 votes

这是使用细化并具有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

执行Hash::each后,您可以使用默认的Hash::each_pairtrue,就好像它们没有被修改一样。 您可以像以前一样通过这些方法使用块。

新事物是,您可以将布尔值Hash::each(第二个参数)传递给这些修改的方法,它们将递归合并哈希。


在此答案中写有简单用法的示例。 这是一个高级示例。

这个问题中的示例很糟糕,因为它与递归合并无关。 下一行将遇到问题的示例:

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

我们将把书从第二个房间的架子上移到第一个房间的架子上的同一行。 首先,我们将在不设置Hash::each标志的情况下执行此操作,即与未修改的Hash::each_pair标志相同:

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

如您所见,没有Hash::each迫使我们扔掉了珍贵的书。

现在,我们将做同样的事情,但是将Hash::each标志设置为true。 您可以将Hash::each_pair或仅true作为第二个参数传递:

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_pair)的递归版本。

MOPO3OB answered 2020-02-20T16:07:30Z
0 votes

我认为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)
koendc answered 2020-02-20T16:07:52Z
-1 votes
a[:book] = a[:book] + b[:book]

要么

a[:book] <<  b[:book].first
nkm answered 2020-02-20T16:08:15Z
translate from https://stackoverflow.com:/questions/9381553/ruby-merge-nested-hash