Ruby - 访问多维哈希并避免访问nil对象

94

可能重复:
Ruby:IF语句中的Nils
有没有一种干净的方法可以避免在嵌套的参数哈希中调用空值的方法?


假设我试图访问一个哈希表,如下所示:

my_hash['key1']['key2']['key3']

如果哈希表里存在key1、key2和key3,这种方式很好,但是如果例如key1不存在呢?

那我会得到NoMethodError: undefined method [] for nil:NilClass。没人喜欢这个错误。

到目前为止,我通过条件语句来处理这个问题:

if my_hash['key1'] && my_hash['key1']['key2'] ...

这种方式合适吗?还有没有其他更符合Ruby风格的做法?


被接受的答案提到了除了Ruby 2.3+的正确方法之外的所有可能方法:http://ruby-doc.org/core-2.3.1/Hash.html#method-i-dig - Eric Duminil
3个回答

189
这有许多方法。
如果你使用的是 Ruby 2.3 或以上版本,可以使用 dig
my_hash.dig('key1', 'key2', 'key3')

许多人坚持使用简单的ruby并链接&&保护测试。
你也可以使用stdlib Hash#fetch
my_hash.fetch('key1', {}).fetch('key2', {}).fetch('key3', nil)

有些人喜欢使用ActiveSupport的#try方法进行链接调用。
my_hash.try(:[], 'key1').try(:[], 'key2').try(:[], 'key3')

其他人使用 andand
myhash['key1'].andand['key2'].andand['key3']

有些人认为 自我中心的尼尔斯 是一个好主意(尽管如果有人发现你这样做,他们可能会追捕你并折磨你)。
class NilClass
  def method_missing(*args); nil; end
end

my_hash['key1']['key2']['key3']

你可以使用Enumerable#reduce(或别名inject)。
['key1','key2','key3'].reduce(my_hash) {|m,k| m && m[k] }

或者可以扩展哈希表,或是直接在目标哈希对象上添加一个嵌套查找方法。

module NestedHashLookup
  def nest *keys
    keys.reduce(self) {|m,k| m && m[k] }
  end
end

my_hash.extend(NestedHashLookup)
my_hash.nest 'key1', 'key2', 'key3'

哦,我们怎么能忘记maybe monad呢?
Maybe.new(my_hash)['key1']['key2']['key3']

你对在语句末尾使用 rescue nil 有什么想法? - jakeonrails
@jakeonrails rescue nil 几乎总是有害的。1)它可能会捕获并悄悄地丢弃您不知道可能被抛出的异常;2)异常是计算上昂贵的流程控制 - 应该仅在异常行为而非预期行为时使用它们。 - dbenhur
7
在Ruby 2.3及以上版本中,你可以使用dig方法来访问哈希表中的值。详见:http://ruby-doc.org/core-2.3.0_preview1/Hash.html#method-i-dig - LYu
自从 Ruby 2.3 版本以后,您可以使用 dig 方法来处理哈希和数组。 - azerty
2
在我之前的评论中,安全导航符运算符在哈希表上的语法是不正确的。正确的语法是:my_hash&.[]('key1')&.[]('key2')&.[]('key3') - thisismydesign
显示剩余3条评论

6

你也可以使用Object#andand

my_hash['key1'].andand['key2'].andand['key3']

5

条件 my_hash ['key1'] && my_hash ['key1'] ['key2'] 不符合 DRY原则。

备选方案:

1)自动创建 魔法。从那篇文章中:

def autovivifying_hash
   Hash.new {|ht,k| ht[k] = autovivifying_hash}
end

然后,以您提供的示例为例:
my_hash = autovivifying_hash     
my_hash['key1']['key2']['key3']

这类似于Hash.fetch方法,两者都使用新的哈希作为默认值,但是这种方式将细节移到了创建时。不可否认,这有点欺骗性:它永远不会返回“nil”,只会返回一个即时创建的空哈希表。根据您的用例,这可能是浪费的。

2)通过查找机制来抽象数据结构,并在幕后处理未找到的情况。以下是一个简单的示例:

def lookup(model, key, *rest) 
    v = model[key]
    if rest.empty?
       v
    else
       v && lookup(v, *rest)
    end
end
#####

lookup(my_hash, 'key1', 'key2', 'key3')
=> nil or value

3) 如果你对单子有兴趣,可以看看这个网站:Maybe

。该网站介绍了Ruby中的单子。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接