Betrachten Sie eine "Person", die in einem Hash gespeichert ist. Zwei Beispiele sind:
fred = {:person => {:name => "Fred", :spouse => "Wilma", :children => {:child => {:name => "Pebbles"}}}}
slate = {:person => {:name => "Mr. Slate", :spouse => "Mrs. Slate"}}
Wenn die "Person" keine Kinder hat, ist das Element "Kinder" nicht vorhanden. Für Herrn Slate können wir also prüfen, ob er Eltern hat:
slate_has_children = !slate[:person][:children].nil?
Was ist, wenn wir nicht wissen, dass "Schiefer" ein Personenhash ist? Erwägen:
dino = {:pet => {:name => "Dino"}}
Wir können nicht mehr leicht nach Kindern suchen:
dino_has_children = !dino[:person][:children].nil?
NoMethodError: undefined method `[]' for nil:NilClass
Wie würden Sie also die Struktur eines Hashes überprüfen, insbesondere wenn er tief verschachtelt ist (sogar tiefer als die hier angegebenen Beispiele)? Vielleicht ist eine bessere Frage: Was ist der "Ruby-Weg", um dies zu tun?
Der naheliegendste Weg, dies zu tun, besteht darin, einfach jeden Schritt des Weges zu überprüfen:
has_children = slate[:person] && slate[:person][:children]
Verwendung von .nil? ist wirklich nur erforderlich, wenn Sie false als Platzhalterwert verwenden. In der Praxis ist dies selten. Im Allgemeinen können Sie einfach testen, dass es existiert.
Update: Wenn Sie Ruby 2.3 oder höher verwenden, gibt es eine integrierte
Dig
-Methode, die die in dieser Antwort beschriebenen Schritte ausführt.
Wenn nicht, können Sie auch eine eigene Hash-Dig-Methode definieren, die dies erheblich vereinfachen kann:
class Hash
def Dig(*path)
path.inject(self) do |location, key|
location.respond_to?(:keys) ? location[key] : nil
end
end
end
Diese Methode prüft jeden Schritt des Weges und vermeidet Stolpern bei Anrufen von Null. Für flache Strukturen ist der Nutzen etwas eingeschränkt, aber für tief verschachtelte Strukturen finde ich es von unschätzbarem Wert:
has_children = slate.Dig(:person, :children)
Sie können dies auch robuster gestalten, indem Sie beispielsweise prüfen, ob der Eintrag: children tatsächlich gefüllt ist:
children = slate.Dig(:person, :children)
has_children = children && !children.empty?
Mit Ruby 2.3 haben wir Unterstützung für den sicheren Navigationsoperator: https://www.Ruby-lang.org/de/news/2015/11/11/Ruby-2-3-0-preview1 -veröffentlicht/
has_children
könnte jetzt geschrieben werden als:
has_children = slate[:person]&.[](:children)
Dig
wird auch hinzugefügt:
has_children = slate.Dig(:person, :children)
Eine andere Alternative:
dino.fetch(:person, {})[:children]
Sie können den andand
gem verwenden:
require 'andand'
fred[:person].andand[:children].nil? #=> false
dino[:person].andand[:children].nil? #=> true
Weitere Erklärungen finden Sie unter http://andand.rubyforge.org/ .
Traditionell musste man so etwas tun:
structure[:a] && structure[:a][:b]
Ruby 2.3 fügte jedoch eine Funktion hinzu, die diesen Weg eleganter gestaltet:
structure.Dig :a, :b # nil if it misses anywhere along the way
Es gibt einen Edelstein namens Ruby_Dig
, der dies für Sie zurückpatcht.
Man könnte Hash mit dem Standardwert {} verwenden - leerer Hash. Zum Beispiel,
dino = Hash.new({})
dino[:pet] = {:name => "Dino"}
dino_has_children = !dino[:person][:children].nil? #=> false
Das funktioniert auch mit bereits erstelltem Hash:
dino = {:pet=>{:name=>"Dino"}}
dino.default = {}
dino_has_children = !dino[:person][:children].nil? #=> false
Oder Sie können die Methode [] für die nil-Klasse definieren
class NilClass
def [](* args)
nil
end
end
nil[:a] #=> nil
dino_has_children = !dino.fetch(person, {})[:children].nil?
Beachten Sie, dass Sie in Rails auch Folgendes tun können:
dino_has_children = !dino[person].try(:[], :children).nil? #
def flatten_hash(hash)
hash.each_with_object({}) do |(k, v), h|
if v.is_a? Hash
flatten_hash(v).map do |h_k, h_v|
h["#{k}_#{h_k}"] = h_v
end
else
h[k] = v
end
end
end
irb(main):012:0> fred = {:person => {:name => "Fred", :spouse => "Wilma", :children => {:child => {:name => "Pebbles"}}}}
=> {:person=>{:name=>"Fred", :spouse=>"Wilma", :children=>{:child=>{:name=>"Pebbles"}}}}
irb(main):013:0> slate = {:person => {:name => "Mr. Slate", :spouse => "Mrs. Slate"}}
=> {:person=>{:name=>"Mr. Slate", :spouse=>"Mrs. Slate"}}
irb(main):014:0> flatten_hash(fred).keys.any? { |k| k.include?("children") }
=> true
irb(main):015:0> flatten_hash(slate).keys.any? { |k| k.include?("children") }
=> false
Dadurch werden alle Hashes in eine und dann in eine beliebige abgeflacht. Gibt "true" zurück, wenn ein Schlüssel vorhanden ist, der mit der untergeordneten Zeichenfolge "children" übereinstimmt. Dies kann auch hilfreich sein.
Hier können Sie eine gründliche Prüfung auf falsche Werte im Hash und alle verschachtelten Hashes durchführen, ohne dass der Affe die Ruby-Hash-Klasse patchen muss (BITTE machen Sie keinen Affen-Patch für die Ruby-Klassen. Dies sollten Sie jedoch niemals tun.) .
(Angenommen, Rails, obwohl Sie dies leicht ändern könnten, um außerhalb von Rails zu arbeiten.)
def deep_all_present?(hash)
fail ArgumentError, 'deep_all_present? only accepts Hashes' unless hash.is_a? Hash
hash.each do |key, value|
return false if key.blank? || value.blank?
return deep_all_present?(value) if value.is_a? Hash
end
true
end
Vereinfachung der obigen Antworten hier:
Erstellen Sie eine rekursive Hash-Methode, deren Wert nicht wie folgt null sein kann.
def recursive_hash
Hash.new {|key, value| key[value] = recursive_hash}
end
> slate = recursive_hash
> slate[:person][:name] = "Mr. Slate"
> slate[:person][:spouse] = "Mrs. Slate"
> slate
=> {:person=>{:name=>"Mr. Slate", :spouse=>"Mrs. Slate"}}
slate[:person][:state][:city]
=> {}
Wenn es Ihnen nichts ausmacht, leere Hashes zu erstellen, wenn der Wert für den Schlüssel nicht vorhanden ist :)
Thks @tadman für die Antwort.
Für diejenigen, die Perfs wollen (und mit Ruby <2.3 hängen bleiben), ist diese Methode 2,5x schneller:
unless Hash.method_defined? :Dig
class Hash
def Dig(*path)
val, index, len = self, 0, path.length
index += 1 while(index < len && val = val[path[index]])
val
end
end
end
und wenn Sie RubyInline verwenden, ist diese Methode 16x schneller:
unless Hash.method_defined? :Dig
require 'inline'
class Hash
inline do |builder|
builder.c_raw '
VALUE Dig(int argc, VALUE *argv, VALUE self) {
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
self = rb_hash_aref(self, *argv);
if (NIL_P(self) || !--argc) return self;
++argv;
return Dig(argc, argv, self);
}'
end
end
end
Sie können eine Kombination aus &
und key?
verwenden, es ist O(1) im Vergleich zu Dig
, das O(n) ist stellt sicher, dass auf die Person zugegriffen wird, ohne dass NoMethodError: undefined method `[]' for nil:NilClass
fred[:person]&.key?(:children) //=>true
slate[:person]&.key?(:children)
Sie können auch ein Modul definieren, das die Klammermethoden als Alias bezeichnet, und die Ruby-Syntax verwenden, um geschachtelte Elemente zu lesen/schreiben.
module Nesty
def []=(*keys,value)
key = keys.pop
if keys.empty?
super(key, value)
else
if self[*keys].is_a? Hash
self[*keys][key] = value
else
self[*keys] = { key => value}
end
end
end
def [](*keys)
self.Dig(*keys)
end
end
class Hash
def nesty
self.extend Nesty
self
end
end
Dann können Sie tun:
irb> a = {}.nesty
=> {}
irb> a[:a, :b, :c] = "value"
=> "value"
irb> a
=> {:a=>{:b=>{:c=>"value"}}}
irb> a[:a,:b,:c]
=> "value"
irb> a[:a,:b]
=> {:c=>"value"}
irb> a[:a,:d] = "another value"
=> "another value"
irb> a
=> {:a=>{:b=>{:c=>"value"}, :d=>"another value"}}
Ich weiß nicht, wie "Ruby" es ist (!), Aber das von mir geschriebene KeyDial gem lässt Sie dies grundsätzlich tun, ohne Ihre ursprüngliche Syntax zu ändern:
has_kids = !dino[:person][:children].nil?
wird:
has_kids = !dino.dial[:person][:children].call.nil?
Dies verwendet einige Tricksereien, um die wichtigsten Zugriffsaufrufe zu vermitteln. Bei call
wird versucht, Dig
die vorherigen Schlüssel von dino
zu __zugeben, und wenn ein Fehler auftritt (wie gewünscht), wird nil zurückgegeben. nil?
gibt dann natürlich true zurück.
Sie können versuchen mit zu spielen
dino.default = {}
Oder zum Beispiel:
empty_hash = {}
empty_hash.default = empty_hash
dino.default = empty_hash
So kannst du anrufen
empty_hash[:a][:b][:c][:d][:e] # and so on...
dino[:person][:children] # at worst it returns {}
Gegeben
x = {:a => {:b => 'c'}}
y = {}
sie könnten x und y wie folgt überprüfen:
(x[:a] || {})[:b] # 'c'
(y[:a] || {})[:b] # nil