Possible Duplicate:
How do I compare two hashes?
I have two ruby hashes (which are essentially models) and am trying to find the differences between them, one is an old instance of an object where the other has new values assigned to some attributes. I'm trying to determine which keys have changed, but there doesn't seem to be anything built into the Hash for this. I can think of a few brute forceish solutions, but was wondering if there is perhaps an elegant solution out there.
Ideally I need to be able to take two hashs like so:
element1 = {:name => "Original", :description => "The original one!"}
element2 = {:name => "Original", :description => "The new one!"}
And be able to compare/diff them and get something back like this:
{:description => "The new one!"}
Right now all I can really think of is iterating through the keys in one hash and comparing the value at that key to the corresponding key in the second hash, but that seems too brute forced.
Any ideas? Thanks a lot!
here is a slightly modified version from 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
It recurses into the hashes for more efficient left and right
{a: {c: 1, b: 2}, b: 2}.diff({a: {c: 2, b: 2}})
returns
{:a=>{:c=>[1, 2]}, :b=>[2, nil]}
instead of
{:a=>[{:c=>1, :b=>2}, {:c=>2, :b=>2}], :b=>[2, nil]}
Great idea colin
here is how to apply the diff to the original hashes
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
so to make the left look like the right you run
{a: {c: 1, b: 2}, b: 2}.apply_diff({:a=>{:c=>[1, 2]}, :b=>[2, nil]})
to get
{a: {c: 2, b: 2}, b: nil}
to get exact we would have to go a little farther and record a difference between between nil and no key
and it would also be nice to shorten long arrays by just providing adds and removes
Edit:
I keep coming back to this code to use it in projects I'm in. Here's the latest which is useful for deeply nested structures and based on Pete's code above. I usually drop it in config/initializers/core_ext.rb (in a Rails project):
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
Here's a small demo:
> {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"]}}}
Older response:
I have found Rails' Hash diff method to not actually tell me what was on the left side and right side (which is far more useful). There was a plugin call "Riff", that has since disappeared, which would let you diff two ActiveRecord objects. Essentially:
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
If all you care about is what's unique in element2, you can just do:
element2.to_a - element1.to_a