Вот еще лучшее решение для рекурсивного слияния , которое использует уточнения и имеет метод взрыва наряду с поддержкой блоков . Этот код работает на pure 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::merge
и Hash::merge!
, как если бы они не были изменены. Вы можете использовать blocks с этими методами, как и раньше.
Новым является то, что вы можете передать логический 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
) здесь .