2009-11-19 16 views
31

Eventuali duplicati:
How do I compare two hashes?Confrontando rubino hash

Ho due hash rubino (che sono essenzialmente modelli) e sto cercando di trovare le differenze tra di loro, uno è un vecchio esempio di un oggetto in cui l'altro ha nuovi valori assegnati ad alcuni attributi. Sto provando a determinare quali chiavi sono cambiate, ma sembra che non ci sia nulla di integrato nell'Hash per questo. Posso pensare ad alcune brute soluzioni di forza, ma mi chiedevo se c'è forse una soluzione elegante là fuori.

Idealmente ho bisogno di essere in grado di prendere due hashs in questo modo:

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

ed essere in grado di confrontare/li diff e ottenere qualcosa di simile:

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

In questo momento tutto quello che ho può davvero pensare di iterare attraverso le chiavi in ​​un hash e confrontando il valore di quella chiave con la chiave corrispondente nel secondo hash, ma sembra forzato troppo brutale.

Qualche idea? Molte grazie!

risposta

20

Edit:

continuo a tornare a questo codice da utilizzare in progetti che sono qui è l'ultima che è utile per le strutture profondamente nidificate e sulla base del codice di Pete sopra.. Io di solito cadere in config/inizializzatori/core_ext.rb (in un progetto 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 

Ecco una piccola 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"]}}} 

più vecchio di risposta:

ho trovato Rails' Hash diff metodo per non effettivamente dirmi cosa c'era sul lato sinistro e sul lato destro (che è molto più utile). C'era un plugin chiamato "Riff", che da allora è scomparso, il che permetterebbe di diffare due oggetti ActiveRecord. In sostanza:

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

Per il mio scopo non mi interessa particolarmente poiché ho solo bisogno di sapere quali campi sono stati modificati. Se stavo usando AR questo non sarebbe un problema, ma tutto è stato sottratto attraverso un livello dati a CouchDB, quindi mi trovo a dover reinventare la ruota, per così dire, per alcune funzionalità. Grazie comunque per il suggerimento. – Chelsea

+0

Che naturalmente corrisponde al tuo commento sulla "forza bruta", ma ritengo che sia utile e non così orribile o inelegante. –

+0

Questo metodo non noterà chiavi aggiuntive nell'hash 'other' nè sarebbe in grado di dire che l'assenza della chiave dal valore è' nil', per il controllo della versione migliorata http://stackoverflow.com/a/19184270/54247 – dolzenko

11

Se tutto ciò che interessa è ciò che è unico in elemento2, si può solo fare:

element2.to_a - element1.to_a 
+2

Non sembra funzionare se hash contiene altri hashes – Sam

+1

Vero, perché gli "identici" hash non contano come uguali ... –

34

qui è una versione leggermente modificata da Colin.

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 

E recurses nelle hash per una più efficiente a destra ea sinistra

{a: {c: 1, b: 2}, b: 2}.diff({a: {c: 2, b: 2}}) 

rendimenti

{:a=>{:c=>[1, 2]}, :b=>[2, nil]} 

invece di

{:a=>[{:c=>1, :b=>2}, {:c=>2, :b=>2}], :b=>[2, nil]} 

Grande idea colin

01.235.

ecco come applicare il diff per gli hash originali

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 

in modo da rendere il look sinistra come a destra si esegue

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

per ottenere

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

per ottenere esatto dovremmo andare un po 'più lontano e registrare una differenza tra tra zero e nessuna chiave
e sarebbe anche bello accorciare i lunghi array semplicemente fornendo aggiunge e rimuove