我有以下哈希:
hash = {'name' => { 'Mike' => { 'age' => 10, 'gender' => 'm' } } }
我可以通过以下方式访问年龄:
hash['name']['Mike']['age']
如果我使用Hash#fetch
方法怎么办?如何从嵌套哈希中检索密钥?
正如塞尔吉奥所说,做到这一点的方式(不为自己创造一些东西)将采用一系列fetch
方法:
hash.fetch('name').fetch('Mike').fetch('age')
答案 0 :(得分:33)
从Ruby 2.3.0开始,您可以使用Hash#dig
:
hash.dig('name', 'Mike', 'age')
此外还有额外的好处,如果一路上的某些值变为nil
,您将获得nil
而不是例外。
在迁移之前,您可以使用ruby_dig gem。
答案 1 :(得分:3)
我所知道的没有内置方法。我在我目前的项目中有这个
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
代替#[]
(如果您愿意)。
答案 2 :(得分:0)
如果您的目标是在缺少任何中间密钥时提出KeyError
,那么您需要编写自己的方法。如果您使用fetch为缺失键提供默认值,那么您可以通过使用默认值构造Hashes来绕过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}}}
这对于任意嵌套的Hashes不起作用,你需要在构造它们时选择最大深度。
答案 3 :(得分:0)
从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)
答案 4 :(得分:0)
使用某种方法而不是使用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
答案 5 :(得分:0)
如果您不想猴子修补标准的Ruby类Hash
,请使用.fetch(x, {})
变体。因此,对于上面的示例,看起来像这样:
hash.fetch('name', {}).fetch('Mike', {}).fetch('age')
答案 6 :(得分:0)
fetch
的要点在于,在违反合同时会引发显式错误,而不必追踪代码中可能导致不可预测状态的无声 nil
。< /p>
尽管 dig
在您期望 nil
为默认值时优雅且有用,但它不提供与 fetch
相同的错误报告保证。 OP 似乎想要 fetch
的显式错误,但没有丑陋的冗长和链接。
一个示例用例是从 YAML.load_file()
接收一个简单的嵌套散列,并要求对丢失的键进行显式错误。
一种选择是将 []
别名为 fetch
,如图所示 here,但这不是对嵌套结构的深度操作。
我最终使用了递归函数和 hash.instance_eval {alias [] fetch}
将别名深入应用到这样一个简单的散列中。一个类也能正常工作,受益于与 Hash
分开的不同子类。
irb(main):110:1* def deeply_alias_fetch!(hash)
irb(main):111:2* if hash.instance_of? Hash
irb(main):112:2* hash.instance_eval {alias [] fetch}
irb(main):113:2* hash.each_value {|v| deeply_alias_fetch!(v)}
irb(main):114:1* end
irb(main):115:0> end
=> :deeply_alias_fetch!
irb(main):116:0> h = {:a => {:b => 42}, :c => {:d => 1, :e => 2}}
irb(main):122:0> deeply_alias_fetch!(h)
=> {:a=>{:b=>42}, :c=>{:d=>1, :e=>2}}
irb(main):123: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):123
1: from (irb):123:in `fetch'
KeyError (key not found: :bb)
Did you mean? :b
irb(main):124:0> h.dig(:a, :bb)
=> nil
答案 7 :(得分:-3)
如果可以的话
使用:
hash[["ayy","bee"]]
而不是:
hash["ayy"]["bee"]
它会挽救很多烦恼