Ответ 1
Для rails 3.0.0+ или более поздней версии существует deep_merge функция ActiveSupport что делает именно то, что вы просите.
Я хотел бы объединить вложенный хэш.
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"}]}
Каков способ гнезда для этого?
Для rails 3.0.0+ или более поздней версии существует deep_merge функция ActiveSupport что делает именно то, что вы просите.
Я нашел более общий алгоритм глубокого слияния здесь и использовал его так:
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 и 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)
Для разнообразия - и это будет работать только в том случае, если вы хотите одинаково объединить все ключи в своем хеше - вы можете сделать это:
a.merge(b) { |k, x, y| x + y }
Когда вы передаете блок в Hash#merge
, k
- это ключ, который объединяется, где ключ существует как в a
, так и b
, x
- это значение a[k]
и y
- значение b[k]
. Результатом блока станет значение в объединенном хеше для ключа k
.
Я думаю, что в вашем конкретном случае, nkm answer лучше.
Немного поздно ответить на ваш вопрос, но я написал довольно богатую утилиту глубокого слияния, которая сейчас поддерживается Даниэлем Делео в 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, когда найдена неустановившаяся сущность. Он добавит массивы, содержащиеся в: x вместе, потому что он знает, как объединить массивы. Он обрабатывает произвольно глубокое слияние хешей, содержащих любые структуры данных.
Теперь еще много документов о датском github repo.
Все ответы выглядят слишком сложными. Вот что я в конце концов придумал:
# @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 или любую другую лицензию
a[:book] = a[:book] + b[:book]
или
a[:book] << b[:book].first
Я думаю, что ответ Джона М - лучший, но он терпит неудачу, когда вы сливаетесь в хэш с нулевым значением /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)
Вот еще лучшее решение для рекурсивного слияния, которое использует уточнения и имеет метод 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::merge
и Hash::merge!
, как если бы они не были изменены. Вы можете использовать блоки с этими методами, как и раньше.
Новое дело в том, что вы можете передать boolean 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
) здесь.