Ruby:合并嵌套哈希
我想合并一个嵌套哈希Ruby:合并嵌套哈希,ruby,hash,merge,nested,Ruby,Hash,Merge,Nested,我想合并一个嵌套哈希 a = {:book=> [{:title=>"Hamlet", :author=>"William Shakespeare" }]} b = {:book=> [{:title=>"Pride and Prejudice", :author=>"Jane Austen" }]} 我希望合并为: {:book=> [{:title=>"Hamlet",
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"}]}
实现这一目标的最佳方法是什么
a[:book] = a[:book] + b[:book]
或
a[:book]为了多样性,只有当您想以相同的方式合并散列中的所有键时,才可以这样做:
a.merge(b) { |k, x, y| x + y }
将块传递给Hash#merge
时,k
是要合并的键,其中键同时存在于a
和b
中,x
是a[k]
的值,y
是b[k]
的值。块的结果成为键k
的合并哈希中的值
不过,我认为在您的具体案例中,nkm的答案更好。我找到了一种更通用的深度合并算法,并像这样使用它:
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)
对于rails 3.0.0+或更高版本,有一个函数可以完全满足您的要求。回答您的问题有点晚,但我不久前编写了一个相当丰富的深度合并实用程序,现在由Daniel Deleo在Github上维护:
它将完全按照您的需要合并阵列。根据文档中的第一个示例:
如果你有两个这样的散列:
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}
room1 = {
:shelf => {
:row2 => [
{
:title => "The Great Gatsby",
:author => "F. Scott Fitzgerald"
}
],
:row3 => [
{
:title => "Catastrophe Theory",
:author => "V. I. Arnol'd"
}
]
}
}
它不会合并:y(因为int和array不可合并)-使用bang(!)语法会导致源代码被覆盖。。当发现不可合并的实体时,使用非bang方法将保留dest的内部值。它将把:x中包含的数组添加到一起,因为它知道如何合并数组。它处理包含任何数据结构的哈希的任意深度合并
现在Daniel的github repo上有更多文档了。我认为Jon M的答案是最好的,但是当你在一个散列中合并一个nil/未定义的值时,它失败了。
此更新解决了以下问题:
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)
在我看来,所有的答案都过于复杂了。以下是我最终得出的结论:
# @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或任何许可证来添加Jon M和koendc的答案,下面的代码将处理散列的合并,和:nil如上所述,但它也将合并两个散列中存在的任何数组(使用相同的密钥):
对于递归合并,这里有一个更好的解决方案,它使用细化,并具有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
执行使用HashRecursive
后,您可以使用默认的哈希::合并
和哈希::合并代码>就好像它们没有被修改过一样。您可以像以前一样使用这些方法使用块
新的事情是,您可以将booleanrecursive
(第二个参数)传递给这些修改过的方法,它们将递归地合并散列
关于简单用法的示例写在。下面是一个高级示例
这个问题中的示例很糟糕,因为它与递归合并无关。下面的行将符合问题的示例:
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
输出将告诉我们,第一个房间中的搁板如下所示:
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}
room1 = {
:shelf => {
:row2 => [
{
:title => "The Great Gatsby",
:author => "F. Scott Fitzgerald"
}
],
:row3 => [
{
:title => "Catastrophe Theory",
:author => "V. I. Arnol'd"
}
]
}
}
正如你所看到的,没有递归
迫使我们扔掉了珍贵的书籍
现在我们将做同样的事情,但是将recursive
标志设置为true。您可以作为第二个参数传递recursive=true
或只传递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
或
就这样。还可以看看我的递归版本的Hash::each
(Hash::each_pair
)。NoMethodError:undefined方法“+”for{:color=>“red”}:Hash看起来您正试图将此答案与包含其他键的哈希一起使用-{:color=>“red”}
不在您的示例中。正如我在回答中所说的,只有当你想以相同的方式合并散列中的所有键时,这才有效。也许你可以将你正在使用的散列全部添加到问题中?这实际上是一个非常方便的技巧!不知道Hash#merge
采用了可选块。如果将键的散列合并到数组中,您也可以这样做来合并空列表:a.merge(b){| k,x,y | x+(y?y:[])}
这不起作用。它将用新阵列替换现有阵列。它似乎适用于Rails 4:否。虽然Rails 3.2.18确实有deep\u merge
方法,但它只接受版本4.0中的块。2@mirelonOP没有要求阻塞,所以答案是正确的。然而,问题不是关于Rails,是吗?因此,这是一个有点令人恼火的答案,因为这个答案的得票最多。这在这种特殊情况下会起作用,但考虑到这个问题的标题及其在搜索结果中的位置,我认为我们需要一个通用的递归合并解决方案。我没有看到nil
值的问题。举一个有问题的例子。nil.deep_merge({'one'=>'two'})引发'nil'的'no method'deep_merge'。注意:对于散列中的数组,它只覆盖现有数组值。请用@Dan回答,以获得更精确的阵列处理。我建议标记或接受。
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