14

我有以下哈希:

hash = {'name' => { 'Mike' => { 'age' => 10, 'gender' => 'm' } } }

我可以通过以下方式访问年龄:

hash['name']['Mike']['age']

如果我使用Hash#fetch方法呢?如何从嵌套哈希中检索密钥?

正如塞尔吉奥所提到的,做到这一点的方法(不为自己创造东西)将是通过一系列fetch方法:

hash.fetch('name').fetch('Mike').fetch('age')
4

8 回答 8

47

从 Ruby 2.3.0 开始,您可以使用Hash#dig

hash.dig('name', 'Mike', 'age')

它还带来了额外的好处,如果一路上的一些值变成了nil,你会得到nil而不是异常。

在迁移之前,您可以使用ruby​​_dig gem。

于 2016-01-15T21:23:14.617 回答
7

编辑:现在有一种内置方式,请参阅此答案


没有我知道的内置方法。我目前的项目中有这个

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而不是#[](如果您愿意)。

于 2013-10-01T12:26:36.113 回答
1

从 Ruby 2.3.0 开始:

您还可以使用&.称为“安全导航运算符”的方式:hash&.[]('name')&.[]('Mike')&.[]('age')。这个绝对安全。

使用dig不安全的,如果为 nilhash.dig(:name, :Mike, :age)则会失败。hash

但是,您可以将两者结合为:hash&.dig(:name, :Mike, :age).

因此,以下任何一种都可以安全使用:

hash&.[]('name')&.[]('Mike')&.[]('age')
hash&.dig(:name, :Mike, :age)
于 2017-09-11T15:36:00.873 回答
0

如果您的目标是在缺少任何中间键时引发 a 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}}}

这不适用于任意嵌套的哈希,您需要在构造它们时选择最大深度。

于 2013-10-01T13:35:03.843 回答
0

使用方法而不是Hash为使用 Ruby2.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
于 2019-01-09T17:00:21.110 回答
0

如果您不想修补标准 Ruby 类,请Hash使用.fetch(x, {})变体。因此,对于上面的示例,将如下所示:

hash.fetch('name', {}).fetch('Mike', {}).fetch('age')
于 2020-02-04T10:21:04.560 回答
0

关键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)
于 2021-06-24T01:56:40.890 回答
-3

如果你可以的话

利用:

hash[["ayy","bee"]]

代替:

hash["ayy"]["bee"]

它会省去很多烦恼

于 2014-10-13T11:54:25.323 回答