我有两个ruby哈希值(基本上是模型)并且我试图找到它们之间的差异,一个是对象的旧实例,其中另一个具有分配给某些属性的新值。我正在尝试确定哪些键已经更改,但似乎没有任何内置于Hash中的内容。我可以想到一些蛮力的解决方案,但想知道是否有一个优雅的解决方案。
理想情况下,我需要能够采用两个哈希值:
element1 = {:name => "Original", :description => "The original one!"}
element2 = {:name => "Original", :description => "The new one!"}
能够比较/区分它们并得到像这样的东西:
{:description => "The new one!"}
现在,我真正想到的是在一个哈希中迭代密钥并将该密钥的值与第二个哈希中的相应密钥进行比较,但这似乎太强暴了。
有什么想法吗?非常感谢!
答案 0 :(得分:34)
这是来自科林的略微修改版本。
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]}
好主意科林
这里是如何将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之间的差异 通过提供添加和删除
来缩短长数组也是很好的答案 1 :(得分:20)
编辑:
我一直回到这个代码,在我所在的项目中使用它。这是最新的,它对深层嵌套结构很有用,并且基于上面的Pete代码。我通常将它放在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
答案 2 :(得分:11)
如果您关心的只是element2中的独特之处,您可以这样做:
element2.to_a - element1.to_a