Ruby风格:如何检查是否存在嵌套的哈希元素
考虑存储在散列中的“人”。 两个例子是:
fred = {:person => {:name => "Fred", :spouse => "Wilma", :children => {:child => {:name => "Pebbles"}}}} slate = {:person => {:name => "Mr. Slate", :spouse => "Mrs. Slate"}}
如果“人”没有孩子,则“孩子”元素不存在。 那么,对于斯莱特先生,我们可以检查他是否有父母:
slate_has_children = !slate[:person][:children].nil?
那么,如果我们不知道“石板”是一个“人”哈希呢? 考虑:
dino = {:pet => {:name => "Dino"}}
我们再也不能轻易地检查孩子了:
dino_has_children = !dino[:person][:children].nil? NoMethodError: undefined method `[]' for nil:NilClass
那么,你将如何检查一个散列的结构,特别是如果它深深地嵌套(甚至比这里提供的例子更深)呢? 也许更好的问题是:做这个“Ruby方式”是什么?
最明显的方法是简单地检查每个步骤:
has_children = slate[:person] && slate[:person][:children]
使用.nil? 只有当你使用false作为占位符值时才是必需的,实际上这很less见。 一般来说你可以简单地testing它的存在
更新 :如果您使用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
这种方法将检查每一步的方法,避免绊倒调用为零。 对于浅层结构,效用是有限的,但是对于深层嵌套结构,我觉得这是非常宝贵的:
has_children = slate.dig(:person, :children)
你也可以使这个更健壮,例如,testing:children条目是否实际上被填充:
children = slate.dig(:person, :children) has_children = children && !children.empty?
在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)
另一种select:
dino.fetch(:person, {})[:children]
你可以使用和gem:
require 'andand' fred[:person].andand[:children].nil? #=> false dino[:person].andand[:children].nil? #=> true
人们可以使用哈希与默认值{} – 空散列。 例如,
dino = Hash.new({}) dino[:pet] = {:name => "Dino"} dino_has_children = !dino[:person][:children].nil? #=> false
这与已经创build的哈希一起工作:
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
传统上,你真的不得不这样做:
structure[:a] && structure[:a][:b]
但是,Ruby 2.3增加了一个使得这种方式更加优雅的特性:
structure.dig :a, :b # nil if it misses anywhere along the way
有一个名为ruby_dig
的gem会为你补上这个。
dino_has_children = !dino.fetch(person, {})[:children].nil?
请注意,在rails中,你也可以这样做:
dino_has_children = !dino[person].try(:[], :children).nil? #
这里有一种方法可以深入检查散列中的任何falsy值,以及没有猴子修补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
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
这将所有的哈希变成一个,然后呢? 如果存在任何与子string“children”匹配的键,则返回true。 这也可能有帮助。
你可以尝试玩
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 {}
特定
x = {:a => {:b => 'c'}} y = {}
你可以像这样检查x和y :
(x[:a] || {})[:b] # 'c' (y[:a] || {})[:b] # nil
简化上面的答案:
创build一个recursionHash方法,其值不能为零,如下所示。
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] => {}
如果你不介意创build空的哈希如果不存在:)
Thks @tadman的答案。
对于那些想要perfs(并与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
您还可以定义一个模块来为括号方法别名,并使用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"}}