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

时间:2013-10-01 12:12:19

标签: ruby

我有以下哈希:

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

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

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

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

正如塞尔吉奥所说,做到这一点的方式(不为自己创造一些东西)将采用一系列fetch方法:

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

8 个答案:

答案 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"]
它会挽救很多烦恼