2009-11-19 226 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} 

得到確切的說,我們必須走得更遠一點,記錄無鑰匙和無鑰匙之間的區別
,它也將很好,通過只提供添加和刪除來縮短長陣列