Сравнение рубиновых хэшей
Возможный дубликат:
Как сравнить два хэша?
У меня есть два рубиновых хэша (которые по существу являются моделями), и я пытаюсь найти различия между ними, один из них - старый экземпляр объекта, где другой имеет новые значения, назначенные некоторым атрибутам. Я пытаюсь определить, какие ключи изменились, но для этого не существует ничего встроенного в Hash. Я могу думать о нескольких грубых силовых решениях, но мне было интересно, есть ли там элегантное решение.
В идеале мне нужно иметь два байта:
element1 = {:name => "Original", :description => "The original one!"}
element2 = {:name => "Original", :description => "The new one!"}
И сможете сравнивать/различать их и получать что-то вроде этого:
{:description => "The new one!"}
В настоящее время все, что я действительно могу представить, это итерация через ключи в одном хеше и сравнение значения с этим ключом с соответствующим ключом во втором хеше, но это кажется слишком грубым принудительным.
Любые идеи? Большое спасибо!
Ответы
Ответ 1
здесь немного измененная версия от colin's.
class Hash
def diff(other)
(self.keys + other.keys).uniq.inject({}) do |memo, key|
unless self[key] == other[key]
if self[key].kind_of?(Hash) && other[key].kind_of?(Hash)
memo[key] = self[key].diff(other[key])
else
memo[key] = [self[key], other[key]]
end
end
memo
end
end
end
Он повторяет хеши для более эффективного левого и правого
{a: {c: 1, b: 2}, b: 2}.diff({a: {c: 2, b: 2}})
возвращает
{:a=>{:c=>[1, 2]}, :b=>[2, nil]}
вместо
{:a=>[{:c=>1, :b=>2}, {:c=>2, :b=>2}], :b=>[2, nil]}
Отличная идея colin
вот как применить diff к исходным хэшам
def apply_diff!(changes, direction = :right)
path = [[self, changes]]
pos, local_changes = path.pop
while local_changes
local_changes.each_pair {|key, change|
if change.kind_of?(Array)
pos[key] = (direction == :right) ? change[1] : change[0]
else
path.push([pos[key], change])
end
}
pos, local_changes = path.pop
end
self
end
def apply_diff(changes, direction = :right)
cloned = self.clone
path = [[cloned, changes]]
pos, local_changes = path.pop
while local_changes
local_changes.each_pair {|key, change|
if change.kind_of?(Array)
pos[key] = (direction == :right) ? change[1] : change[0]
else
pos[key] = pos[key].clone
path.push([pos[key], change])
end
}
pos, local_changes = path.pop
end
cloned
end
чтобы левый выглядел так, как будто вы запускаете
{a: {c: 1, b: 2}, b: 2}.apply_diff({:a=>{:c=>[1, 2]}, :b=>[2, nil]})
чтобы получить
{a: {c: 2, b: 2}, b: nil}
чтобы получить точный результат, нам нужно идти немного дальше и записывать разницу между nil и no key
и было бы неплохо сократить длинные массивы, просто добавив и удаляя
Ответ 2
Edit:
Я продолжаю возвращаться к этому коду, чтобы использовать его в проектах, в которых я нахожусь. Здесь последнее, что полезно для глубоко вложенных структур и основано на коде Пит выше. Я обычно отбрасываю его в config/initializers/core_ext.rb(в проекте Rails):
class Hash
def deep_diff(other)
(self.keys + other.keys).uniq.inject({}) do |memo, key|
left = self[key]
right = other[key]
next memo if left == right
if left.respond_to?(:deep_diff) && right.respond_to?(:deep_diff)
memo[key] = left.deep_diff(right)
else
memo[key] = [left, right]
end
memo
end
end
end
class Array
def deep_diff(array)
largest = [self.count, array.count].max
memo = {}
0.upto(largest - 1) do |index|
left = self[index]
right = array[index]
next if left == right
if left.respond_to?(:deep_diff) && right.respond_to?(:deep_diff)
memo[index] = left.deep_diff(right)
else
memo[index] = [left, right]
end
end
memo
end
end
Вот небольшая демонстрация:
> {a: [{b: "c", d: "e"}, {b: "c", f: "g"}]}.deep_diff({a: [{b: "c", d: "e"}, {b: "d", f: "g"}]})
=> {:a=>{1=>{:b=>["c", "d"]}}}
Более старый ответ:
Я нашел метод Rails Hash diff, чтобы на самом деле не сказать мне, что было с левой и правой сторон (что гораздо полезнее). Появился плагин "Riff", который с тех пор исчез, что позволит вам разделить два объекта ActiveRecord. По существу:
class Hash
def diff(other)
self.keys.inject({}) do |memo, key|
unless self[key] == other[key]
memo[key] = [self[key], other[key]]
end
memo
end
end
end
Ответ 3
Если вам все равно, что уникально в element2, вы можете просто сделать:
element2.to_a - element1.to_a