web-dev-qa-db-fra.com

Rechercher des paires clé/valeur au cœur d'un hachage contenant un nombre arbitraire de hachages et de tableaux imbriqués

Un service Web renvoie un hachage contenant un nombre inconnu de hachages imbriqués, dont certains contiennent un tableau, lequel à son tour contient un nombre inconnu de hachages imbriqués.

Certaines des clés ne sont pas uniques - c’est-à-dire qu’elles sont présentes dans plus d’un hachage imbriqué.

Cependant, toutes les clés qui me tiennent à cœur sont toutes uniques.

Est-ce que je peux donner une clé au hachage de niveau supérieur et récupérer sa valeur même si la paire clé-valeur est enfouie au plus profond de ce bourbier?

(Le service Web est Amazon Product Advertising API, qui modifie légèrement la structure des résultats qu'il donne en fonction du nombre de résultats et des types de recherche autorisés dans chaque catégorie de produit.)

33
steven_noble

Voici une solution récursive simple:

def nested_hash_value(obj,key)
  if obj.respond_to?(:key?) && obj.key?(key)
    obj[key]
  elsif obj.respond_to?(:each)
    r = nil
    obj.find{ |*a| r=nested_hash_value(a.last,key) }
    r
  end
end

h = { foo:[1,2,[3,4],{a:{bar:42}}] }
p nested_hash_value(h,:bar)
#=> 42
30
Phrogz

Combinant quelques réponses et commentaires ci-dessus:

class Hash
  def deep_find(key, object=self, found=nil)
    if object.respond_to?(:key?) && object.key?(key)
      return object[key]
    elsif object.is_a? Enumerable
      object.find { |*a| found = deep_find(key, a.last) }
      return found
    end
  end
end
24
barelyknown

Pas besoin de patcher un singe, utilisez simplement Hashie gem: https://github.com/intridea/hashie#deepfind

user = {
  name: { first: 'Bob', last: 'Boberts' },
  groups: [
    { name: 'Rubyists' },
    { name: 'Open source enthusiasts' }
  ]
}

user.extend Hashie::Extensions::DeepFind

user.deep_find(:name)   #=> { first: 'Bob', last: 'Boberts' }

Pour les objets Enumerable arbitraires, une autre extension est disponible, DeepLocate: https://github.com/intridea/hashie#deeplocate

22
denis.peplin

Malgré le fait que cela semble être un problème courant, je viens de passer un moment à essayer de trouver/trouver exactement ce dont j'ai besoin, ce qui, à mon avis, est identique à votre exigence. Aucun des liens dans la première réponse n'est parfait.

class Hash
  def deep_find(key)
    key?(key) ? self[key] : self.values.inject(nil) {|memo, v| memo ||= v.deep_find(key) if v.respond_to?(:deep_find) }
  end
end

Donc, étant donné:

hash = {:get_transaction_list_response => { :get_transaction_list_return => { :transaction => [ { ... 

Le suivant:

hash.deep_find(:transaction)

trouvera le tableau associé à la clé: transaction.

Ce n'est pas optimal car l'injection continuera à itérer même si memo est rempli.

9
Andy Triggs

Une variante de la solution de barelyknown: Ceci trouvera toutes les valeurs d'une clé dans un hachage plutôt que dans la première correspondance.

class Hash
  def deep_find(key, object=self, found=[])
    if object.respond_to?(:key?) && object.key?(key)
      found << object[key]
    end
    if object.is_a? Enumerable
      found << object.collect { |*a| deep_find(key, a.last) }
    end
    found.flatten.compact
  end
end

{a: [{b: 1}, {b: 2}]}.deep_find(:b) retournera [1, 2]

6
ReggieB

Ruby 2.3 introduit Hash # Dig , qui vous permet de faire:

h = { foo: {bar: {baz: 1}}}

h.Dig(:foo, :bar, :baz)           #=> 1
h.Dig(:foo, :zot)                 #=> nil
4
Chris Edwards

J'utilise le code suivant 

def search_hash(hash, key)
  return hash[key] if hash.assoc(key)
  hash.delete_if{|key, value| value.class != Hash}
  new_hash = Hash.new
  hash.each_value {|values| new_hash.merge!(values)}
  unless new_hash.empty?
    search_hash(new_hash, key)
  end
end
0
Kapil Aggarwal

Parce que Rails 5 ActionController :: Parameters n’hérite plus de Hash, j’ai dû modifier la méthode et la rendre spécifique aux paramètres.

module ActionController
  class Parameters
    def deep_find(key, object=self, found=nil)
      if object.respond_to?(:key?) && object.key?(key)
        return object[key]
      elsif object.respond_to?(:each)
        object = object.to_unsafe_h if object.is_a?(ActionController::Parameters)
        object.find { |*a| found = deep_find(key, a.last) }
        return found
      end
    end
  end
end

Si la clé est trouvée, elle renvoie la valeur de cette clé, mais ne renvoie pas d'objet ActionController :: Parameter. Par conséquent, les paramètres forts ne sont pas conservés.

0
paulz

J'ai fini par utiliser ceci pour une petite recherche, j'ai écrit:

def trie_search(str, obj=self)
  if str.length <= 1
    obj[str]
  else
    str_array = str.chars
    next_trie = obj[str_array.shift]
    next_trie ? trie_search(str_array.join, next_trie) : nil
  end
end

Remarque: ceci ne concerne que les hachages imbriqués pour le moment. Actuellement, aucun support de tableau.

0
DaniG2k