Comparing ruby hashes [duplicate]
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 endend
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 endendclass 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 endend
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 endend
If all you care about is what's unique in element2, you can just do:
element2.to_a - element1.to_a