2009-11-19 12 views
31

Duplicar posible:
How do I compare two hashes?rubí Al comparar los hashes

Tengo dos hashes rubí (que son esencialmente modelos) y estoy tratando de encontrar las diferencias entre ellos, uno es una instancia antigua de un objeto donde el otro tiene nuevos valores asignados a algunos atributos. Estoy tratando de determinar qué teclas han cambiado, pero no parece haber nada incorporado en el hash para esto. Puedo pensar en algunas soluciones de fuerza bruta, pero me preguntaba si existe quizás una solución elegante.

Lo ideal sería que tengo que ser capaz de tomar dos hashs así:

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

y ser capaz de comparar/diff ellos y obtener algo a cambio como éste:

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

En este momento todo lo realmente puedo pensar en iterar a través de las teclas en un hash y comparar el valor de esa tecla con la clave correspondiente en el segundo hash, pero eso parece demasiado forzado.

¿Alguna idea? ¡Muchas gracias!

Respuesta

20

Editar:

la que vuelven a este código para utilizarlo en proyectos en los que estoy aquí es la última que es útil para estructuras anidadas y basado en el código de Pete anteriormente.. Por lo general cae en config/inicializadores/core_ext.rb (en un proyecto 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 

He aquí una pequeña demostración:

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

Mayor respuesta:

he encontrado rieles Método Hash diff para no decirme qué había en el lado izquierdo y el derecho (lo cual es mucho más útil). Hubo un complemento llamado "Riff", que ha desaparecido desde entonces, lo que le permitiría diferenciar dos objetos ActiveRecord. En esencia:

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

Para mi propósito, no me importa especialmente ya que solo necesito saber qué campos han cambiado. Si estuviera usando AR esto no sería un problema, pero todo se está abstrayendo a través de una capa de datos para CouchDB, así que me veo en la necesidad de reinventar la rueda, por así decirlo, para algunas funcionalidades. Gracias por la sugerencia. – Chelsea

+0

Lo que por supuesto corresponde a tu comentario de "fuerza bruta", pero creo que es útil y no tan horrible o poco elegante. –

+0

Este método no notará las claves adicionales en 'hash' other' tampoco podría decir la ausencia de la clave del valor que es 'nil', para una versión mejorada comprobar http://stackoverflow.com/a/19184270/54247 – dolzenko

11

Si todo lo que importa es lo que es único en elemento2, sólo puede hacer:

element2.to_a - element1.to_a 
+2

Doesnt seem para trabajar si el hash contiene otros hashes – Sam

+1

Verdadero, porque los hashes "idénticos" no cuentan como iguales ... –

34

aquí es una versión ligeramente modificada del colin de.

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 

misma recursivamente en los hashes de izquierda y derecha más eficiente

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

vuelve

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

en lugar de

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

Gran idea colin

aquí es cómo aplicar el diff a los hashes originales

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 

así para hacer la mirada izquierda como la derecha se ejecuta

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

para obtener

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

para obtener exacto, tendríamos que ir un poco más lejos y registrar una diferencia entre cero y ninguna clave
y también sería bueno acortar matrices largas simplemente proporcionando agregaciones y elimina

Cuestiones relacionadas