Comparando hashes de ruby

Duplicar Possível:
Como faço para comparar dois hashes?

Eu tenho dois hashes ruby ​​(que são essencialmente modelos) e estou tentando encontrar as diferenças entre eles, um é uma instância antiga de um object onde o outro tem novos valores atribuídos a alguns atributos. Estou tentando determinar quais chaves foram alteradas, mas parece que não há nada embutido no Hash para isso. Eu posso pensar em algumas soluções brutas de força, mas queria saber se há talvez uma solução elegante lá fora.

Idealmente eu preciso ser capaz de pegar dois hashs assim:

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

E ser capaz de comparar / diferenciar e obter algo assim:

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

Neste momento, tudo o que posso realmente imaginar é percorrer as teclas em um hash e comparar o valor dessa chave com a chave correspondente no segundo hash, mas isso parece muito brutal forçado.

Alguma ideia? Muito obrigado!

Aqui está uma versão ligeiramente modificada de 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 

Ele recorre aos hashes para uma esquerda e direita mais eficiente

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

retorna

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

ao invés de

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

Ótima idéia colin

aqui está como aplicar o diff aos hashes originais

  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 

Então, para fazer a esquerda parece a direita você corre

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

para obter

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

para obter a exatidão, teríamos que ir um pouco mais longe e registrar uma diferença entre zero e nenhuma chave
e também seria bom para encurtar longas matrizes apenas fornecendo adiciona e remove

Editar:

Eu continuo voltando a este código para usá-lo em projetos em que estou. Aqui está o mais recente que é útil para estruturas profundamente aninhadas e baseado no código de Pete acima. Eu costumo soltá-lo em config / initializers / core_ext.rb (em um projeto 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 

Aqui está uma pequena demonstração:

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

Resposta mais antiga:

Eu encontrei o método diff do Rails para não me dizer o que estava no lado esquerdo e no lado direito (o que é muito mais útil). Houve uma chamada de plugin “Riff”, que desapareceu desde então, o que permite que você diferencie dois objects ActiveRecord. Essencialmente:

 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 

Se tudo o que importa é o que é único em element2, você pode simplesmente fazer:

 element2.to_a - element1.to_a