Considérons une "personne" stockée dans un hachage. Deux exemples sont:
fred = {:person => {:name => "Fred", :spouse => "Wilma", :children => {:child => {:name => "Pebbles"}}}}
slate = {:person => {:name => "Mr. Slate", :spouse => "Mrs. Slate"}}
Si la "personne" n'a pas d'enfants, l'élément "enfants" n'est pas présent. Donc, pour M. Slate, on peut vérifier s’il a des parents:
slate_has_children = !slate[:person][:children].nil?
Alors, que se passe-t-il si nous ne savons pas que "l'ardoise" est un hachage "de personne"? Considérer:
dino = {:pet => {:name => "Dino"}}
Nous ne pouvons plus facilement vérifier s'il y a des enfants:
dino_has_children = !dino[:person][:children].nil?
NoMethodError: undefined method `[]' for nil:NilClass
Alors, comment vérifier la structure d'un hachage, surtout s'il est imbriqué profondément (même plus profond que les exemples fournis ici)? Peut-être une meilleure question est: quelle est la "manière Ruby" de faire cela?
La manière la plus évidente de le faire est simplement de vérifier chaque étape:
has_children = slate[:person] && slate[:person][:children]
Utilisation de .nil? n’est vraiment nécessaire que lorsque vous utilisez false comme valeur d’espace réservé, ce qui est rare en pratique. Généralement, vous pouvez simplement tester son existence.
Update: Si vous utilisez Ruby 2.3 ou une version ultérieure, une méthode
Dig
intégrée fait ce qui est décrit dans cette réponse.
Sinon, vous pouvez également définir votre propre méthode de «hachage» de hachage, ce qui peut considérablement simplifier cette opération:
class Hash
def Dig(*path)
path.inject(self) do |location, key|
location.respond_to?(:keys) ? location[key] : nil
end
end
end
Cette méthode permet de vérifier chaque étape du processus et d’éviter de déclencher un appel nul. Pour les structures peu profondes, l'utilité est quelque peu limitée, mais pour les structures profondément imbriquées, je la trouve inestimable:
has_children = slate.Dig(:person, :children)
Vous pouvez également rendre cela plus robuste, par exemple, en testant si l'entrée: children est réellement remplie:
children = slate.Dig(:person, :children)
has_children = children && !children.empty?
Avec Ruby 2.3, l’opérateur de navigation sécurisée sera pris en charge: https://www.Ruby-lang.org/fr/news/2015/11/11/Ruby-2-3-0-preview1 -released/
has_children
pourrait maintenant être écrit comme suit:
has_children = slate[:person]&.[](:children)
Dig
est également ajouté:
has_children = slate.Dig(:person, :children)
Une autre alternative:
dino.fetch(:person, {})[:children]
Vous pouvez utiliser la gemme andand
:
require 'andand'
fred[:person].andand[:children].nil? #=> false
dino[:person].andand[:children].nil? #=> true
Vous pouvez trouver plus d'explications sur http://andand.rubyforge.org/ .
Traditionnellement, il fallait vraiment faire quelque chose comme ça:
structure[:a] && structure[:a][:b]
Cependant, Ruby 2.3 a ajouté une fonctionnalité qui le rend plus gracieux:
structure.Dig :a, :b # nil if it misses anywhere along the way
Il existe une gemme appelée Ruby_Dig
qui corrigera cela pour vous.
On pourrait utiliser un hash avec la valeur par défaut {} - hash vide. Par exemple,
dino = Hash.new({})
dino[:pet] = {:name => "Dino"}
dino_has_children = !dino[:person][:children].nil? #=> false
Cela fonctionne aussi avec Hash déjà créé:
dino = {:pet=>{:name=>"Dino"}}
dino.default = {}
dino_has_children = !dino[:person][:children].nil? #=> false
Ou vous pouvez définir la méthode [] pour la classe nil
class NilClass
def [](* args)
nil
end
end
nil[:a] #=> nil
dino_has_children = !dino.fetch(person, {})[:children].nil?
Notez que dans Rails, vous pouvez également faire:
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
Cela va aplatir tous les hachages en un seul et ensuite? renvoie true si une clé correspondant à la sous-chaîne "enfants" existe ..__ Cela pourrait également aider.
Voici un moyen de vérifier en profondeur toutes les valeurs de fausseté dans le hachage et les hachages imbriqués sans que singe ne corrige la classe Ruby Hash (VEUILLEZ ne pas appliquer de correctif pour singe sur les classes Ruby; .
(En supposant que Rails, vous pouvez facilement le modifier pour qu’il fonctionne en dehors de Rails)
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
Simplifier les réponses ci-dessus ici:
Créez une méthode de hachage récursif dont la valeur ne peut pas être nulle, comme suit.
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]
=> {}
Si cela ne vous dérange pas de créer des hachages vides si la valeur n'existe pas pour la clé :)
Thks @tadman pour la réponse.
Pour ceux qui veulent des perfs (et sont bloqués avec Ruby <2.3), cette méthode est 2,5 fois plus rapide
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
et si vous utilisez RubyInline , cette méthode est 16 fois plus rapide:
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
Vous pouvez utiliser une combinaison de &
et key?
c'est O(1) par rapport à Dig
qui est O(n) et ceci s'assurera que la personne est accédée sans NoMethodError: undefined method `[]' for nil:NilClass
fred[:person]&.key?(:children) //=>true
slate[:person]&.key?(:children)
Vous pouvez également définir un module pour aliaser les méthodes entre crochets et utiliser la syntaxe Ruby pour lire/écrire des éléments imbriqués.
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
Ensuite, vous pouvez faire:
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"}}
Je ne sais pas comment "Ruby" c'est (!), Mais le KeyDial gem que j'ai écrit vous permet de le faire sans changer la syntaxe d'origine:
has_kids = !dino[:person][:children].nil?
devient:
has_kids = !dino.dial[:person][:children].call.nil?
Cela utilise quelques astuces pour intermédiaires les appels d'accès clé. À call
, il tentera de Dig
les clés précédentes sur dino
et, si elle rencontre une erreur (comme il le fera), renvoie nil. nil?
retourne bien sûr true.
Vous pouvez essayer de jouer avec
dino.default = {}
Ou par exemple:
empty_hash = {}
empty_hash.default = empty_hash
dino.default = empty_hash
De cette façon, vous pouvez appeler
empty_hash[:a][:b][:c][:d][:e] # and so on...
dino[:person][:children] # at worst it returns {}
Donné
x = {:a => {:b => 'c'}}
y = {}
vous pouvez vérifier x et y comme ceci:
(x[:a] || {})[:b] # 'c'
(y[:a] || {})[:b] # nil