我有以下哈希:
hash = {'name' => { 'Mike' => { 'age' => 10, 'gender' => 'm' } } }
我可以通过以下方式访问年龄:
hash['name']['Mike']['age']
Hash#fetch
方法呢?如何从嵌套哈希中检索密钥?
正如 Sergio 提到的,做到这一点的方法(无需为自己创建一些东西)将是通过一系列
fetch
方法:
hash.fetch('name').fetch('Mike').fetch('age')
编辑:现在有一种内置方法,请参阅这个答案。
据我所知,没有内置方法。我当前的项目中有这个
class Hash
def fetch_path(*parts)
parts.reduce(self) do |memo, key|
memo[key.to_s] if memo
end
end
end
# usage
hash.fetch_path('name', 'Mike', 'age')
您可以轻松修改它以使用
#fetch
代替 #[]
(如果您愿意)。
如果您不想修补标准 Ruby 类
Hash
,请使用 .fetch(x, {})
变体。所以上面的例子看起来像这样:
hash.fetch('name', {}).fetch('Mike', {}).fetch('age')
自 Ruby 2.3.0 起:
您还可以使用称为“安全导航运算符”的
&.
为:hash&.[]('name')&.[]('Mike')&.[]('age')
。这个是绝对安全的。
使用
dig
不安全,因为如果 hash.dig(:name, :Mike, :age)
为零,hash
将失败。
但是,您可以将两者组合为:
hash&.dig(:name, :Mike, :age)
。
因此以下任一选项都可以安全使用:
hash&.[]('name')&.[]('Mike')&.[]('age')
hash&.dig(:name, :Mike, :age)
如果您的目标是在缺少任何中间键时引发
KeyError
,那么您需要编写自己的方法。相反,如果您使用 fetch 为缺失的键提供默认值,那么您可以通过使用默认值构造哈希来避免使用 fetch。
hash = Hash.new { |h1, k1| h1[k1] = Hash.new { |h2, k2| h2[k2] = Hash.new { |h3, k3| } } }
hash['name']['Mike']
# {}
hash['name']['Steve']['age'] = 20
hash
# {"name"=>{"Mike"=>{}, "Steve"=>{"age"=>20}}}
这不适用于任意嵌套的哈希,您需要在构造它们时选择最大深度。
对于使用 Ruby
Hash
或更低版本的其他人来说,使用方法而不是添加到 2.2
类的版本。
def dig(dict, *args)
key = args.shift
if args.empty?
return dict[key]
else
dig(dict[key], *args)
end
end
所以你可以这样做:
data = { a: 1, b: {c: 2}}
dig(data, :a) == 1
dig(data, :b, :c) == 2
fetch
的要点是,在违反合同时会引发显式错误,而不是必须追踪代码中无声的nil
,这可能会导致不可预测的状态。
尽管当您希望
dig
成为默认值时,nil
非常优雅且有用,但它不提供与 fetch
相同的错误报告保证。 OP似乎想要fetch
的明确错误,但没有丑陋的冗长和链接。
一个示例用例是从
YAML.load_file()
接收普通嵌套哈希,并要求针对丢失的键给出明确的错误。
一种选择是将
[]
别名为 fetch
,如此处所示,但这不是对嵌套结构的深度操作。
我最终使用了递归函数和
hash.instance_eval {alias [] fetch}
将别名深入应用到这样一个简单的哈希中。一个类也可以工作,它的好处是有一个独立于 Hash
的不同子类。
irb(main):001:1* def deeply_alias_fetch!(x)
irb(main):002:2* if x.instance_of? Hash
irb(main):003:2* x.instance_eval {alias [] fetch}
irb(main):004:2* x.each_value {|v| deeply_alias_fetch!(v)}
irb(main):005:2* elsif x.instance_of? Array
irb(main):006:2* x.each {|e| deeply_alias_fetch!(e)}
irb(main):007:1* end
irb(main):008:0> end
=> :deeply_alias_fetch!
irb(main):009:0> h = {:a => {:b => 42}, :c => [{:d => 1, :e => 2}, {}]}
irb(main):010:0> deeply_alias_fetch!(h)
=> {:a=>{:b=>42}, :c=>[{:d=>1, :e=>2}, {}]}
irb(main):011:0> h[:a][:bb]
Traceback (most recent call last):
5: from /usr/bin/irb:23:in `<main>'
4: from /usr/bin/irb:23:in `load'
3: from /usr/lib/ruby/gems/2.7.0/gems/irb-1.2.1/exe/irb:11:in `<top (required)>'
2: from (irb):11
1: from (irb):11:in `fetch'
KeyError (key not found: :bb)
Did you mean? :b
irb(main):012:0> h[:c][0][:e]
=> 2
irb(main):013:0> h[:c][0][:f]
Traceback (most recent call last):
5: from /usr/bin/irb:23:in `<main>'
4: from /usr/bin/irb:23:in `load'
3: from /usr/lib/ruby/gems/2.7.0/gems/irb-1.2.1/exe/irb:11:in `<top (required)>'
2: from (irb):14
1: from (irb):14:in `fetch'
KeyError (key not found: :f)
如果可以的话
用途:
hash[["ayy","bee"]]
而不是:
hash["ayy"]["bee"]
这样可以省去很多烦恼