it-swarm.com.de

Finden Sie Schlüssel/Wert-Paare tief in einem Hash, der eine beliebige Anzahl verschachtelter Hashes und Arrays enthält

Ein Webdienst gibt einen Hash zurück, der eine unbekannte Anzahl von geschachtelten Hashes enthält, von denen einige ein Array enthalten, das wiederum eine unbekannte Anzahl von geschachtelten Hashwerten enthält.

Einige der Schlüssel sind nicht eindeutig - d. H. Sind in mehr als einem der verschachtelten Hashwerte vorhanden.

Alle Schlüssel, die mir wichtig sind, sind jedoch alle einzigartig.

Gibt es eine Möglichkeit, dem Top-Level-Hash einen Schlüssel zu geben und seinen Wert zurückzuerhalten, selbst wenn das Schlüssel-Wert-Paar tief in diesem Morast steckt?

(Der Webservice ist die Amazon Product Advertising API, die die Ergebnisstruktur in Abhängigkeit von der Anzahl der Ergebnisse und den in jeder Produktkategorie zulässigen Suchtypen geringfügig ändert.)

33
steven_noble

Hier ist eine einfache rekursive Lösung:

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

Einige der Antworten und Kommentare oben zusammenfassen:

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

Kein Affen-Patching, einfach Hashie-Edelstein verwenden: 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' }

Für beliebige Enumerable-Objekte gibt es eine weitere Erweiterung, DeepLocate: https://github.com/intridea/hashie#deeplocate

22
denis.peplin

Obwohl dies scheinbar ein häufiges Problem zu sein scheint, habe ich gerade versucht, genau das zu finden, was ich brauche. Keiner der Links in der ersten Antwort ist genau richtig.

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

So gegeben:

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

Folgende:

hash.deep_find(:transaction)

findet das mit dem Transaktionsschlüssel: verknüpfte Array.

Dies ist nicht optimal, da die Injektion auch dann wiederholt wird, wenn memo aufgefüllt wird.

9
Andy Triggs

Eine Variation der Lösung von Barely known: Hier werden alle Werte eines Schlüssels in einem Hash und nicht in der ersten Übereinstimmung gefunden.

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) wird [1, 2] zurückgeben

6
ReggieB

Ruby 2.3 führt Hash # Dig ein, mit dem Sie Folgendes tun können:

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

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

Ich verwende den folgenden Code 

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

Da Rails 5 ActionController :: Parameters nicht mehr von Hash erbt, musste ich die Methode ändern und sie für Parameter spezifisch machen.

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

Wenn der Schlüssel gefunden wird, gibt er den Wert dieses Schlüssels zurück, es wird jedoch kein ActionController :: Parameter-Objekt zurückgegeben.

0
paulz

Am Ende habe ich dies für eine kleine Suche benutzt, die ich geschrieben habe:

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

Hinweis: Dies ist im Moment nur für verschachtelte Hashes. Derzeit keine Array-Unterstützung.

0
DaniG2k