这里有一个更好的解决方案,用到了细化技术来进行递归合并,并且提供了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 后,您可以像未修改过一样使用默认的 Hash::merge 和 Hash::merge!。您可以像以前一样在这些方法中使用块。
新的变化是您可以将布尔值 recursive(第二个参数)传递给这些修改过的方法,它们将递归地合并哈希。
简单用法示例已写在此答案中。下面是一个高级示例。
这个问题中的示例很糟糕,因为它与递归合并无关。以下行将符合问题的示例:
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=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
或者
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::each
(Hash::each_pair
)的递归版本在这里。