2009-11-19 6 views
31

Possible en double:
How do I compare two hashes?comparaison ruby ​​hash

J'ai deux hash rubis (qui sont essentiellement des modèles) et essaie de trouver les différences entre eux, on est une ancienne instance d'un objet où l'autre a de nouvelles valeurs assignées à certains attributs. J'essaie de déterminer quelles clés ont changé, mais il ne semble pas y avoir quelque chose de construit dans le Hash pour cela. Je peux penser à quelques solutions brutales, mais je me demandais s'il y avait peut-être une solution élégante là-bas.

Idéalement je dois être en mesure de prendre deux hashs comme ceci:

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

et être en mesure de comparer/diff entre eux et obtenir quelque chose comme ceci:

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

En ce moment, tout ce que je peut vraiment penser à itérer à travers les clés dans un hachage et à comparer la valeur à cette clé à la clé correspondante dans le deuxième hachage, mais cela semble trop brutal forcé.

Des idées? Merci beaucoup!

Répondre

20

Edit:

Je reviens à ce code pour l'utiliser dans des projets que je suis ici est la dernière qui est utile pour les structures profondément imbriquées et basé sur le code de Pete ci-dessus.. Je laisse tomber généralement dans config/initializers/core_ext.rb (dans un projet 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 

Voici une petite démo:

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

réponse Older:

J'ai trouvé Rails' Hash diff méthode de ne pas me dire ce qui était sur le côté gauche et le côté droit (ce qui est beaucoup plus utile). Il y avait un appel de plugin "Riff", qui a depuis disparu, ce qui vous permettrait de différencier deux objets ActiveRecord. Pour l'essentiel:

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

Pour mon but, je ne m'inquiète pas particulièrement car j'ai vraiment juste besoin de savoir quels champs ont changé. Si j'utilisais AR, ce ne serait pas un problème, mais tout est en train d'être extrait à travers une couche de données vers CouchDB, donc je me retrouve à devoir réinventer la roue, pour ainsi dire, pour certaines fonctionnalités. Merci pour la suggestion cependant. – Chelsea

+0

Ce qui, bien sûr, correspond à votre commentaire "force brute", mais je trouve que c'est utile et pas si terrible ou inélégant. –

+0

Cette méthode ne remarquera pas de clés supplémentaires dans le hachage 'other' et ne pourra pas non plus dire que l'absence de la clé est' nil', pour une vérification de la version améliorée http://stackoverflow.com/a/19184270/54247 – dolzenko

11

Si tout ce que vous aimez est ce qui est unique dans element2, vous pouvez juste faire:

element2.to_a - element1.to_a 
+2

travailler si hash contient d'autres hashs – Sam

+1

Vrai, car les hashes "identiques" ne comptent pas comme égal ... –

34

est ici une version légèrement modifiée de 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 

Il récursif dans les hash pour plus efficace gauche et à droite

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

retours

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

au lieu de

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

Grande idée colin

ici est de savoir comment appliquer la diff aux hash d'origine

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 

afin de rendre le regarder à gauche comme à droite vous exécutez

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

pour obtenir

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

pour obtenir Exact Nous devrons aller un peu plus loin et enregistrer une différence entre zéro et aucune clé
et il serait également agréable de raccourcir les tableaux longs en fournissant simplement des ajouts et des suppressions

Questions connexes