2009-11-19 4 views
31

가능한 중복 해시 :
How do I compare two hashes?비교 루비

나는 두 개의 루비 해시를 (기본적으로 모델있는)와 그들 사이의 차이점을 찾기 위해 노력하고 있습니다를, 하나는 이전 인스턴스이다 다른 속성은 일부 속성에 새로운 값이 할당 된 객체의 속성입니다. 어떤 키가 변경되었는지 확인하려고하지만 해시에 내장 된 키가없는 것 같습니다. 몇 가지 짐승 같은 솔루션을 생각할 수 있지만 거기에 우아한 해결책이 있는지 궁금해하고 있습니다.

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

그리고 다음과 같이 다시 뭔가를/비교를 diff를 얻을 수있을 :

이상적으로 나는과 같이 두 hashs을 할 수 있어야 지금

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

모든 I를 실제로 한 해시의 키를 반복하고 그 키의 값을 두 번째 해시의 해당 키와 비교한다고 생각할 수 있습니다.하지만 이는 너무 무차별 적으로 보입니다.

아이디어가 있으십니까? 고마워요!

답변

20

편집 :

이 난에있어 프로젝트에서 사용하기 위해 다시이 코드에 계속오고 여기에 중첩 구조에 대한 유용하고 위의 피트의 코드를 기반으로하는 최신입니다.. 나는 보통 (레일 프로젝트) 설정/초기화/core_ext.rb에 드롭 :

> {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"]}}} 

이전 응답 : 나는 레일을 발견

'여기

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 

작은 데모입니다 Hash diff 메소드는 실제로 왼쪽과 오른쪽에 무엇이 있었는지 알려주지 않습니다 (훨씬 더 유용합니다). 플러그인 호출 "Riff"가 있었는데, 그 후 사라져서 두 개의 ActiveRecord 객체를 비교할 수 있습니다. 기본적으로 : 당신이 걱정하는 모든 element2에 독특한 무엇 인 경우

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

+0

물론 "brute force"에 해당하는 댓글이 있지만 유용하고 너무 끔찍하지는 않습니다. –

+0

이 방법은 개선 된 버전 확인을 위해 키 부재가 값이 'nil'이 아니라는 것을 '기타'해시에 추가 키가 있음을 알 수 없으며 http://stackoverflow.com/a/19184270/54247 – dolzenko

11

, 당신은 다만 할 수 있습니다

여기
element2.to_a - element1.to_a 
+2

보이지 않습니다 해시에 다른 해시가 포함되어있는 경우 작동합니다. – Sam

+1

"동일한"해시가 같은 것으로 계산되지 않기 때문에 참입니다. –

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]} 

에 대한 해시로 재귀 좋은 생각 콜린

여기

그래서 당신은

{a: {c: 2, b: 2}, b: nil} 

얻을 얻을

{a: {c: 1, b: 2}, b: 2}.apply_diff({:a=>{:c=>[1, 2]}, :b=>[2, nil]}) 

를 실행할 권리 등의 왼쪽 모양을 만들기 위해 원래의 해시

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 

에은 diff를 적용하는 방법입니다 정확히 우리는 조금 더 멀리 가야하고 아무 것도없고 아무 것도없는 키의 차이를 기록해야합니다.
그리고 그냥 추가 및 제거를 제공하여 긴 배열을 단축하는 것이 좋을 것입니다.