2009-11-19 223 views
31

可能重复:
How do I compare two hashes?比较红宝石哈希

我有两个红宝石哈希(这基本上是型号),我试图找到它们之间的区别,一个是旧的实例另一个对象具有分配给某些属性的新值的对象。我试图确定哪些键已经改变,但似乎没有内置到哈希中的任何内容。我可以想到一些暴力解决方案,但想知道是否有一个优雅的解决方案。

理想情况下,我需要能够采取两种hashs像这样:

element1 = {:name => "Original", :description => "The original one!"} 
element2 = {:name => "Original", :description => "The new one!"} 

而且能够比较/差值他们得到的东西回来这样的:

{:description => "The new one!"} 

现在所有的我可以真正想到的是遍历一个散列中的键并将该键处的值与第二个散列中的相应键进行比较,但这似乎太过于粗暴。

任何想法?非常感谢!

回答

20

编辑:

我继续回来这段代码我在项目中使用它下面是这是基于上述皮特的代码深度嵌套的结构有用的和最新的。我通常砸在配置/初始化/ 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 
+0

为了我的目的,我并不特别在乎,因为我真的只需要知道哪些字段已更改。如果我使用AR,这不会是一个问题,但是所有内容都是通过数据层抽象到CouchDB,因此我发现自己需要重新发明轮子,可以说是为了某些功能。 尽管感谢您的建议。 – Chelsea 2009-11-19 22:02:29

+0

哪一个当然对应于你的“暴力”评论,但我觉得它很有用,而不是那么可怕或不雅。 – 2009-11-19 22:02:55

+0

这种方法不会注意到'other'哈希中的其他键也不能够告诉键没有值是'nil',用于改进版本检查http://stackoverflow.com/a/19184270/54247 – dolzenko 2013-10-04 14:52:05

11

如果所有你关心的是什么在element2的唯一,你可以这样做:

element2.to_a - element1.to_a 
+2

看起来似乎没有如果哈希包含其他哈希值,则工作 – Sam 2011-02-07 00:56:43

+1

正确,因为“相同”哈希值不等于... – 2011-02-07 04:40:53

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} 

得到确切的说,我们必须走得更远一点,记录无钥匙和无钥匙之间的区别
,它也将很好,通过只提供添加和删除来缩短长阵列