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.)
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
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
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
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.
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]
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
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
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.
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.