考虑存储在哈希中的“人”。两个例子是:
fred = {:person => {:name => "Fred", :spouse => "Wilma", :children => {:child => {:name => "Pebbles"}}}}
slate = {:person => {:name => "Mr. Slate", :spouse => "Mrs. Slate"}}
如果“person”没有任何子节点,则“children”元素不存在。所以,对于Slate先生,我们可以检查他是否有父母:
slate_has_children = !slate[:person][:children].nil?
那么,如果我们不知道“slate”是一个“人”哈希呢?考虑:
dino = {:pet => {:name => "Dino"}}
我们不能再轻易检查孩子:
dino_has_children = !dino[:person][:children].nil?
NoMethodError: undefined method `[]' for nil:NilClass
那么,你如何检查哈希的结构,特别是如果它被深深嵌套(甚至比这里提供的例子更深)?也许更好的问题是:做这件事的“红宝石方式”是什么?
答案 0 :(得分:78)
最明显的方法是简单地检查每一步:
has_children = slate[:person] && slate[:person][:children]
使用.nil?实际上只有在使用false作为占位符值时才需要,实际上这种情况很少见。通常,您只需测试它是否存在。
更新:如果您使用的是Ruby 2.3或更高版本,则会有一个内置的
dig
方法,可以执行此答案中描述的内容。
如果没有,您还可以定义自己的哈希“挖掘”方法,这可以大大简化这一点:
class Hash
def dig(*path)
path.inject(self) do |location, key|
location.respond_to?(:keys) ? location[key] : nil
end
end
end
此方法将检查方法的每一步,并避免绊倒对nil的调用。对于浅层结构,实用程序有些限制,但对于深度嵌套的结构,我发现它非常有用:
has_children = slate.dig(:person, :children)
您也可以使其更加健壮,例如,测试是否实际填充了:children条目:
children = slate.dig(:person, :children)
has_children = children && !children.empty?
答案 1 :(得分:20)
使用Ruby 2.3,我们将支持安全导航操作员: https://www.ruby-lang.org/en/news/2015/11/11/ruby-2-3-0-preview1-released/
has_children
现在可以写成:
has_children = slate[:person]&.[](:children)
dig
也被添加:
has_children = slate.dig(:person, :children)
答案 2 :(得分:13)
另一种选择:
dino.fetch(:person, {})[:children]
答案 3 :(得分:4)
您可以使用andand
gem:
require 'andand'
fred[:person].andand[:children].nil? #=> false
dino[:person].andand[:children].nil? #=> true
您可以在http://andand.rubyforge.org/找到进一步的解释。
答案 4 :(得分:2)
可以使用默认值为{}的哈希 - 空哈希。例如,
dino = Hash.new({})
dino[:pet] = {:name => "Dino"}
dino_has_children = !dino[:person][:children].nil? #=> false
这也适用于已创建的哈希:
dino = {:pet=>{:name=>"Dino"}}
dino.default = {}
dino_has_children = !dino[:person][:children].nil? #=> false
或者你可以为nil类定义[]方法
class NilClass
def [](* args)
nil
end
end
nil[:a] #=> nil
答案 5 :(得分:2)
传统上,你真的必须做这样的事情:
structure[:a] && structure[:a][:b]
然而,Ruby 2.3增加了一个使这种方式更优雅的功能:
structure.dig :a, :b # nil if it misses anywhere along the way
有一个名为ruby_dig
的宝石会为你补丁。
答案 6 :(得分:1)
dino_has_children = !dino.fetch(person, {})[:children].nil?
请注意,在rails中你也可以这样做:
dino_has_children = !dino[person].try(:[], :children).nil? #
答案 7 :(得分:1)
这里有一种方法可以深入检查散列中的任何虚假值以及任何嵌套的散列,而无需猴子修补Ruby Hash类(请不要在Ruby类上使用猴子补丁,这是你不应该做的事情,EVER)。
(假设Rails,尽管您可以轻松地将其修改为在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
答案 8 :(得分:1)
在此简化上述答案:
创建一个Recursive Hash方法,其值不能为nil,如下所示。
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]
=> {}
如果键不存在值,则不介意创建空哈希值:)
答案 9 :(得分:1)
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
这会将所有哈希变成一个然后变成任何一个?如果任何键与子字符串匹配,则返回true" children"存在。 这也可能有所帮助。
答案 10 :(得分:0)
您可以尝试使用
dino.default = {}
或者例如:
empty_hash = {}
empty_hash.default = empty_hash
dino.default = empty_hash
这样你就可以打电话了
empty_hash[:a][:b][:c][:d][:e] # and so on...
dino[:person][:children] # at worst it returns {}
答案 11 :(得分:0)
鉴于
x = {:a => {:b => 'c'}}
y = {}
您可以像这样检查 x 和 y :
(x[:a] || {})[:b] # 'c'
(y[:a] || {})[:b] # nil
答案 12 :(得分:0)
请问@tadman的答案。
对于那些想要穿孔(并且坚持使用ruby< 2.3)的人来说,这种方法的速度提高了2.5倍:
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
如果您使用RubyInline,此方法的速度提高了16倍:
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
答案 13 :(得分:0)
您还可以定义一个模块来对括号方法进行别名,并使用Ruby语法来读取/写入嵌套元素。
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
然后你可以这样做:
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"}}
答案 14 :(得分:0)
我不知道它是什么“ Ruby”(!),但是我编写的KeyDial gem基本上可以使您做到这一点,而无需更改原始语法:
has_kids = !dino[:person][:children].nil?
成为:
has_kids = !dino.dial[:person][:children].call.nil?
这使用一些技巧来中介访问密钥。在call
处,它将尝试在dig
上的dino
之前的键上输入,如果遇到错误(将这样),则返回nil。 nil?
当然会返回true。
答案 15 :(得分:0)
您可以使用&
和key?
的组合,将O(1)与dig
的O(n)进行比较,这将确保没有{{ 1}}
NoMethodError: undefined method `[]' for nil:NilClass