如何使用嵌套哈希的 fetch 方法?

问题描述 投票:0回答:9

我有以下哈希:

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

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

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

如果我使用

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

正如 Sergio 提到的,做到这一点的方法(无需为自己创建一些东西)将是通过一系列

fetch
方法:

hash.fetch('name').fetch('Mike').fetch('age')
ruby
9个回答
49
投票

从 Ruby 2.3.0 开始,您可以使用

Hash#dig
:

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

它还有一个额外的好处,如果一路上的某些值变成

nil
,您将得到
nil
而不是异常。

您可以使用 ruby_dig gem,直到迁移为止。


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
代替
#[]
(如果您愿意)。


1
投票

如果您不想修补标准 Ruby 类

Hash
,请使用
.fetch(x, {})
变体。所以上面的例子看起来像这样:

hash.fetch('name', {}).fetch('Mike', {}).fetch('age')

1
投票

自 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)

0
投票

如果您的目标是在缺少任何中间键时引发

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}}}

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


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

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)

0
投票

我为 Ruby 创建了一个功能提案,如果您想在核心中看到

fetch_nested
方法,请支持。

https://bugs.ruby-lang.org/issues/20815


-4
投票

如果可以的话

用途:

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

而不是:

hash["ayy"]["bee"]

这样可以省去很多烦恼

© www.soinside.com 2019 - 2024. All rights reserved.