Styl Ruby: jak sprawdzić, czy zagnieżdżony element hash istnieje
Rozważmy "osobę" przechowywaną w hashu. Dwa przykłady to:
fred = {:person => {:name => "Fred", :spouse => "Wilma", :children => {:child => {:name => "Pebbles"}}}}
slate = {:person => {:name => "Mr. Slate", :spouse => "Mrs. Slate"}}
Jeśli "osoba" nie ma dzieci, element "dzieci" nie jest obecny. Więc dla Pana Slate ' a możemy sprawdzić, czy ma rodziców:
slate_has_children = !slate[:person][:children].nil?
Co jeśli nie wiemy, że "slate" to skrót od "person"? Consider:
dino = {:pet => {:name => "Dino"}}
Nie możemy już łatwo sprawdzić dzieci:
dino_has_children = !dino[:person][:children].nil?
NoMethodError: undefined method `[]' for nil:NilClass
Więc jak sprawdzić strukturę hasha, zwłaszcza jeśli jest zagnieżdżony głęboko (nawet głębiej niż przykłady podane tutaj)? Może lepszym pytaniem jest: jaki jest "rubinowy sposób", aby to zrobić?
14 answers
Najbardziej oczywistym sposobem, aby to zrobić, jest po prostu sprawdzić każdy krok drogi:
has_children = slate[:person] && slate[:person][:children]
Użycie .zero? jest wymagane tylko wtedy, gdy używasz false jako wartości zastępczej, a w praktyce jest to rzadkie. Ogólnie można po prostu sprawdzić, że istnieje.
Update : Jeśli używasz Rubiego 2.3 lub nowszego, jest wbudowany
dig
metoda, która robi to, co jest opisane w tej odpowiedzi.
Jeśli nie, możesz również zdefiniować własną metodę Hash "dig", która może uprość to znacznie:
class Hash
def dig(*path)
path.inject(self) do |location, key|
location.respond_to?(:keys) ? location[key] : nil
end
end
end
Ta metoda będzie sprawdzać każdy krok drogi i uniknąć potknięcia się na połączenia do zera. Dla płytkich struktur użyteczność jest nieco ograniczona, ale dla głęboko zagnieżdżonych struktur uważam, że jest nieoceniona: {]}
has_children = slate.dig(:person, :children)
Możesz również uczynić to bardziej solidnym, na przykład, testując, czy wpis: children jest rzeczywiście wypełniony:
children = slate.dig(:person, :children)
has_children = children && !children.empty?
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-24 19:31:25
W Ruby 2.3 będziemy mieli wsparcie dla operatora bezpiecznej nawigacji: https://www.ruby-lang.org/en/news/2015/11/11/ruby-2-3-0-preview1-released/
has_children
teraz Można zapisać jako:
has_children = slate[:person]&.[](:children)
dig
dodaje się również:
has_children = slate.dig(:person, :children)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-13 10:59:01
Inna alternatywa:
dino.fetch(:person, {})[:children]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-13 21:23:38
Możesz użyć andand
gem:
require 'andand'
fred[:person].andand[:children].nil? #=> false
dino[:person].andand[:children].nil? #=> true
Dalsze wyjaśnienia można znaleźć na stronie http://andand.rubyforge.org/.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-30 16:47:14
Można użyć hash z domyślną wartością {} - empty hash. Na przykład,
dino = Hash.new({})
dino[:pet] = {:name => "Dino"}
dino_has_children = !dino[:person][:children].nil? #=> false
To działa również z już utworzonym Hashem:
dino = {:pet=>{:name=>"Dino"}}
dino.default = {}
dino_has_children = !dino[:person][:children].nil? #=> false
Lub można zdefiniować metodę [] dla klasy nil
class NilClass
def [](* args)
nil
end
end
nil[:a] #=> nil
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-30 15:33:42
Tradycyjnie, naprawdę trzeba było zrobić coś takiego:
structure[:a] && structure[:a][:b]
Jednak Ruby 2.3 dodał funkcję, która czyni ten sposób bardziej wdzięcznym:
structure.dig :a, :b # nil if it misses anywhere along the way
Jest klejnot o nazwie ruby_dig
, który naprawi to za Ciebie.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-06 20:56:37
dino_has_children = !dino.fetch(person, {})[:children].nil?
Zauważ, że w rails możesz również zrobić:
dino_has_children = !dino[person].try(:[], :children).nil? #
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-30 15:59:17
Oto sposób, w jaki możesz zrobić głębokie sprawdzenie wszelkich fałszywych wartości w hash i zagnieżdżonych hashach bez Małpiego łatania klasy Ruby Hash (proszę nie małpuj łatania klas Ruby, czegoś takiego nigdy nie powinieneś robić).
(zakładając Rails, chociaż można łatwo zmodyfikować to tak, aby działało poza 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-04 18:45:58
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
To spłaszczy wszystkie hasze w jeden, a potem każdy? zwraca true, jeśli istnieje jakikolwiek klucz pasujący do podłańcucha "dzieci".
To również może pomóc.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-15 21:22:14
Upraszczając powyższe odpowiedzi tutaj:
Utwórz rekurencyjną metodę Hash, której wartość nie może być zerowa, jak poniżej.
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]
=> {}
Jeśli nie masz nic przeciwko stworzeniu pustych hashów, jeśli wartość nie istnieje dla klucza:)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-03 14:47:45
Możesz spróbować grać z
dino.default = {}
Lub na przykład:
empty_hash = {}
empty_hash.default = empty_hash
dino.default = empty_hash
W ten sposób możesz zadzwonić
empty_hash[:a][:b][:c][:d][:e] # and so on...
dino[:person][:children] # at worst it returns {}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-30 15:47:45
Given
x = {:a => {:b => 'c'}}
y = {}
Możesz sprawdzić x i y TAK:
(x[:a] || {})[:b] # 'c'
(y[:a] || {})[:b] # nil
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-15 11:01:50
Thks @ tadman za odpowiedź.
Dla tych, którzy chcą perfs (i utknęli z Rubim
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
A jeśli używasz RubyInline , ta metoda jest 16X szybsza:
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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-10 23:42:42
Możesz również zdefiniować moduł do aliasów metod nawiasów i użyć składni Ruby do odczytu/zapisu zagnieżdżonych elementów.
UPDATE: zamiast nadpisywać Accesory wsporników, Zażądaj wystąpienia skrótu, aby rozszerzyć moduł.
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
Wtedy możesz zrobić:
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"}}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-29 08:29:10