我正在使用ruby编写的一个小实用程序,它广泛使用嵌套哈希。目前,我正在检查对嵌套哈希元素的访问,如下所示:
structure = { :a => { :b => 'foo' }}
# I want structure[:a][:b]
value = nil
if structure.has_key?(:a) && structure[:a].has_key?(:b) then
value = structure[:a][:b]
end
有更好的方法吗?我想能够说:
value = structure[:a][:b]
如果出现以下情况,请获取nil
:a不是structure
中的关键字等。
答案 0 :(得分:36)
传统上,你真的必须做这样的事情:
structure[:a] && structure[:a][:b]
然而,Ruby 2.3添加了一种方法Hash#dig
,使这种方式更加优雅:
structure.dig :a, :b # nil if it misses anywhere along the way
有一个名为ruby_dig
的宝石会为你补丁。
答案 1 :(得分:32)
Ruby 2.3.0在[a-z\d]*
和Hash
上引入了a new method called dig
,完全解决了这个问题。
Array
如果在任何级别缺少密钥,则返回value = structure.dig(:a, :b)
。
如果您使用的是早于2.3版本的Ruby,您可以使用nil
gem或自行实现:
ruby_dig
答案 2 :(得分:27)
这些天我通常这样做的方式是:
h = Hash.new { |h,k| h[k] = {} }
这将为您提供一个散列,该散列创建一个新散列作为缺失键的条目,但为第二级键返回nil:
h['foo'] -> {}
h['foo']['bar'] -> nil
您可以嵌套此项以添加可以通过这种方式解决的多个图层:
h = Hash.new { |h, k| h[k] = Hash.new { |hh, kk| hh[kk] = {} } }
h['bar'] -> {}
h['tar']['zar'] -> {}
h['scar']['far']['mar'] -> nil
您还可以使用default_proc
方法无限期地链接:
h = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
h['bar'] -> {}
h['tar']['star']['par'] -> {}
上面的代码创建了一个哈希,其默认proc创建一个具有相同默认proc的新Hash。因此,当查找看不见的密钥时,作为默认值创建的哈希将具有相同的默认行为。
编辑:更多详情
Ruby哈希允许您控制在为新键进行查找时如何创建默认值。指定后,此行为将封装为Proc
对象,并且可通过default_proc
和default_proc=
方法访问。也可以通过将块传递给Hash.new
来指定默认proc。
让我们把这段代码打破一点。这不是惯用的红宝石,但更容易将其划分为多行:
1. recursive_hash = Hash.new do |h, k|
2. h[k] = Hash.new(&h.default_proc)
3. end
第1行将变量recursive_hash
声明为新Hash
,并将块开始为recursive_hash
的{{1}}。该块传递两个对象:default_proc
,这是正在执行键查找的h
实例,Hash
,正在查找该键。
第2行将哈希值中的默认值设置为新的k
实例。通过传递从发生查找的哈希的Hash
创建的Proc
来提供此哈希的默认行为;即,块本身正在定义的默认proc。
以下是IRB会议的一个例子:
default_proc
创建irb(main):011:0> recursive_hash = Hash.new do |h,k|
irb(main):012:1* h[k] = Hash.new(&h.default_proc)
irb(main):013:1> end
=> {}
irb(main):014:0> recursive_hash[:foo]
=> {}
irb(main):015:0> recursive_hash
=> {:foo=>{}}
时的哈希值时,recursive_hash[:foo]
default_proc
提供了recursive_hash
。这有两个影响:
default_proc
的默认行为与recursive_hash[:foo]
相同。recursive_hash
的{{1}}创建的哈希的默认行为与recursive_hash[:foo]
相同。因此,继续IRB,我们得到以下结论:
default_proc
答案 3 :(得分:14)
答案 4 :(得分:7)
我认为最易读的解决方案之一是使用Hashie:
require 'hashie'
myhash = Hashie::Mash.new({foo: {bar: "blah" }})
myhash.foo.bar
=> "blah"
myhash.foo?
=> true
# use "underscore dot" for multi-level testing
myhash.foo_.bar?
=> true
myhash.foo_.huh_.what?
=> false
答案 5 :(得分:3)
value = structure[:a][:b] rescue nil
答案 6 :(得分:2)
解决方案1
我之前在我的问题中提出过这个问题:
class NilClass; def to_hash; {} end end
Hash#to_hash
已经定义,并返回self。然后你可以这样做:
value = structure[:a].to_hash[:b]
to_hash
确保在上一次密钥搜索失败时获得空哈希值。
<强>溶液2 强>
这个解决方案在精神上类似于mu太短的答案,因为它使用了一个子类,但仍然有些不同。如果某个键没有值,它不会使用默认值,而是创建一个空哈希值,这样就不会出现DigitalRoss答案所具有的混淆问题,正如之所指出的那样。亩太短了。
class NilFreeHash < Hash
def [] key; key?(key) ? super(key) : self[key] = NilFreeHash.new end
end
structure = NilFreeHash.new
structure[:a][:b] = 3
p strucrture[:a][:b] # => 3
但它不同于问题中给出的规范。当给出未定义的键时,它将返回nil
的空哈希表达式。
p structure[:c] # => {}
如果您从头开始构建此NilFreeHash的实例并分配键值,它将起作用,但如果您想将哈希转换为此类的实例,则可能会出现问题。
答案 7 :(得分:1)
您可以使用额外的可变方法构建一个Hash子类,以便在整个过程中一直进行适当的检查。这样的事情(当然有更好的名字):
class Thing < Hash
def find(*path)
path.inject(self) { |h, x| return nil if(!h.is_a?(Thing) || h[x].nil?); h[x] }
end
end
然后只使用Thing
而不是哈希:
>> x = Thing.new
=> {}
>> x[:a] = Thing.new
=> {}
>> x[:a][:b] = 'k'
=> "k"
>> x.find(:a)
=> {:b=>"k"}
>> x.find(:a, :b)
=> "k"
>> x.find(:a, :b, :c)
=> nil
>> x.find(:a, :c, :d)
=> nil
答案 8 :(得分:1)
require 'xkeys'
structure = {}.extend XKeys::Hash
structure[:a, :b] # nil
structure[:a, :b, :else => 0] # 0 (contextual default)
structure[:a] # nil, even after above
structure[:a, :b] = 'foo'
structure[:a, :b] # foo
答案 9 :(得分:1)
哈希的这个猴子补丁函数应该是最简单的(至少对我而言)。它也没有改变结构,即将nil
改为{}
。即使您从原始资源中读取树,例如它仍然适用JSON。在进行或解析字符串时,它也不需要生成空哈希对象。 rescue nil
对我来说实际上是一个很好的解决方案,因为我有足够的勇气来承受如此低的风险,但我发现它基本上有一个性能上的缺点。
class ::Hash
def recurse(*keys)
v = self[keys.shift]
while keys.length > 0
return nil if not v.is_a? Hash
v = v[keys.shift]
end
v
end
end
示例:
> structure = { :a => { :b => 'foo' }}
=> {:a=>{:b=>"foo"}}
> structure.recurse(:a, :b)
=> "foo"
> structure.recurse(:a, :x)
=> nil
还有什么好处,你可以用它来玩游戏:
> keys = [:a, :b]
=> [:a, :b]
> structure.recurse(*keys)
=> "foo"
> structure.recurse(*keys, :x1, :x2)
=> nil
答案 10 :(得分:0)
您可以使用andand宝石,但我对此越来越警惕:
>> structure = { :a => { :b => 'foo' }} #=> {:a=>{:b=>"foo"}}
>> require 'andand' #=> true
>> structure[:a].andand[:b] #=> "foo"
>> structure[:c].andand[:b] #=> nil
答案 11 :(得分:0)
有可爱但错误的方法来做到这一点。哪个是修补NilClass
以添加返回[]
的{{1}}方法。我说这是错误的方法,因为你不知道其他软件可能会创建一个不同的版本,或者未来版本的Ruby中的行为更改可以被这个打破。
更好的方法是创建一个与nil
非常相似的新对象,但支持此行为。使此新对象成为哈希的默认返回值。然后它就会起作用。
或者,您可以创建一个简单的“嵌套查找”函数,您可以将散列和键传递给它,按顺序遍历散列,并在可能的情况下突破。
我个人更喜欢后两种方法中的一种。虽然我认为如果将第一个集成到Ruby语言中会很可爱。 (但猴子修补是一个坏主意。不要这样做。特别是不要证明你是一个很酷的黑客。)
答案 12 :(得分:0)
不是我会这样做,但你可以在NilClass#[]
中使用Monkeypatch:
> structure = { :a => { :b => 'foo' }}
#=> {:a=>{:b=>"foo"}}
> structure[:x][:y]
NoMethodError: undefined method `[]' for nil:NilClass
from (irb):2
from C:/Ruby/bin/irb:12:in `<main>'
> class NilClass; def [](*a); end; end
#=> nil
> structure[:x][:y]
#=> nil
> structure[:a][:y]
#=> nil
> structure[:a][:b]
#=> "foo"
使用@ DigitalRoss的答案。是的,它打字更多,但这是因为它更安全。
答案 13 :(得分:0)
就我而言,我需要一个二维矩阵,其中每个单元格都是一个项目列表。
我发现这种技术似乎有效。它可能适用于OP:
$all = Hash.new()
def $all.[](k)
v = fetch(k, nil)
return v if v
h = Hash.new()
def h.[](k2)
v = fetch(k2, nil)
return v if v
list = Array.new()
store(k2, list)
return list
end
store(k, h)
return h
end
$all['g1-a']['g2-a'] << '1'
$all['g1-a']['g2-a'] << '2'
$all['g1-a']['g2-a'] << '3'
$all['g1-a']['g2-b'] << '4'
$all['g1-b']['g2-a'] << '5'
$all['g1-b']['g2-c'] << '6'
$all.keys.each do |group1|
$all[group1].keys.each do |group2|
$all[group1][group2].each do |item|
puts "#{group1} #{group2} #{item}"
end
end
end
输出结果为:
$ ruby -v && ruby t.rb
ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-linux]
g1-a g2-a 1
g1-a g2-a 2
g1-a g2-a 3
g1-a g2-b 4
g1-b g2-a 5
g1-b g2-c 6
答案 14 :(得分:0)
我目前正在尝试这个:
# --------------------------------------------------------------------
# System so that we chain methods together without worrying about nil
# values (a la Objective-c).
# Example:
# params[:foo].try?[:bar]
#
class Object
# Returns self, unless NilClass (see below)
def try?
self
end
end
class NilClass
class MethodMissingSink
include Singleton
def method_missing(meth, *args, &block)
end
end
def try?
MethodMissingSink.instance
end
end
我知道反对try
的论据,但在查看事物时很有用,例如params
。