访问 ruby 中嵌套哈希的元素
我正在编写一个用 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]
如果 :a 不是 struct
等中的键,则得到 nil
。
I'm working a little utility written in ruby that makes extensive use of nested hashes. Currently, I'm checking access to nested hash elements as follows:
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
Is there a better way to do this? I'd like to be able to say:
value = structure[:a][:b]
And get nil
if :a is not a key in structure
, etc.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(15)
传统上,你确实必须做这样的事情:
然而,Ruby 2.3 添加了一个方法
Hash#dig
使这种方式更加优雅:有一个名为
ruby_dig
将为您进行反向修补。Traditionally, you really had to do something like this:
However, Ruby 2.3 added a method
Hash#dig
that makes this way more graceful:There is a gem called
ruby_dig
that will back-patch this for you.Hash
和Array
有一个 名为dig
的方法。如果任何级别的密钥丢失,它都会返回
nil
。如果您使用的 Ruby 版本早于 2.3,您可以安装 ruby_dig 或 hash_dig_and_collect 等 gem,或者自己实现此功能:
Hash
andArray
have a method calleddig
.It returns
nil
if the key is missing at any level.If you are using a version of Ruby older than 2.3, you can install a gem such as
ruby_dig
orhash_dig_and_collect
, or implement this functionality yourself:这些天我通常这样做的方式是:
这将为您提供一个散列,该散列创建一个新散列作为缺失键的条目,但对于第二级键返回 nil:
您可以嵌套它以添加多个层,这些层可以这样解决:
您还可以使用
default_proc
方法无限期地链接:上面的代码创建一个散列,其默认过程创建一个具有相同默认过程的新散列。因此,当查找未见过的键时创建为默认值的哈希将具有相同的默认行为。
编辑:更多详细信息
Ruby 哈希允许您控制在查找新密钥时如何创建默认值。指定后,此行为将封装为
Proc
对象,并可通过default_proc
和default_proc=
方法。还可以通过将块传递给Hash 来指定默认过程.new
。让我们稍微分解一下这段代码。这不是惯用的 ruby,但将其分成多行会更容易:
第 1 行声明变量
recursive_hash
为新的Hash
并开始一个块为>recursive_hash
的default_proc
。该块传递两个对象:h
(正在执行键查找的Hash
实例)和k
(正在查找的键)向上。第 2 行将哈希中的默认值设置为新的
Hash
实例。该哈希的默认行为是通过传递一个从发生查找的哈希的default_proc
创建的Proc
来提供的;即块本身定义的默认过程。以下是 IRB 会话中的示例:
当创建
recursive_hash[:foo]
处的哈希时,其default_proc
由recursive_hash
的 <代码>default_proc。这有两个效果:recursive_hash[:foo]
的默认行为与recursive_hash
相同。recursive_hash[:foo]
的default_proc
创建的哈希的默认行为将与recursive_hash
相同。因此,继续在 IRB 中,我们得到以下结果:
The way I usually do this these days is:
This will give you a hash that creates a new hash as the entry for a missing key, but returns nil for the second level of key:
You can nest this to add multiple layers that can be addressed this way:
You can also chain indefinitely by using the
default_proc
method:The above code creates a hash whose default proc creates a new Hash with the same default proc. So, a hash created as a default value when a lookup for an unseen key occurs will have the same default behavior.
EDIT: More details
Ruby hashes allow you to control how default values are created when a lookup occurs for a new key. When specified, this behavior is encapsulated as a
Proc
object and is reachable via thedefault_proc
anddefault_proc=
methods. The default proc can also be specified by passing a block toHash.new
.Let's break this code down a little. This is not idiomatic ruby, but it's easier to break it out into multiple lines:
Line 1 declares a variable
recursive_hash
to be a newHash
and begins a block to berecursive_hash
'sdefault_proc
. The block is passed two objects:h
, which is theHash
instance the key lookup is being performed on, andk
, the key being looked up.Line 2 sets the default value in the hash to a new
Hash
instance. The default behavior for this hash is supplied by passing aProc
created from thedefault_proc
of the hash the lookup is occurring in; ie, the default proc the block itself is defining.Here's an example from an IRB session:
When the hash at
recursive_hash[:foo]
was created, itsdefault_proc
was supplied byrecursive_hash
'sdefault_proc
. This has two effects:recursive_hash[:foo]
is the same asrecursive_hash
.recursive_hash[:foo]
'sdefault_proc
will be the same asrecursive_hash
.So, continuing in IRB, we get the following:
我为此制作了 rubygem。尝试 vine。
安装:
使用:
I made rubygem for this. Try vine.
Install:
Usage:
我认为最可读的解决方案之一是使用 Hashie:
I think one of the most readable solutions is using Hashie:
解决方案 1
我之前在问题中建议过这一点:
Hash#to_hash
已定义,并返回 self.然后你可以这样做:to_hash
确保当之前的键搜索失败时你得到一个空的哈希值。解决方案2
这个解决方案在本质上与 mu is Too Short 的答案类似,因为它使用了子类,但仍然有些不同。如果某个键没有值,它不会使用默认值,而是创建一个空哈希值,这样就不会出现DigitalRoss的答案所指出的分配混乱的问题mu 太短了。
不过,它偏离了问题中给出的规范。当给出未定义的键时,它将返回一个空哈希而不是
nil
。如果您从一开始就构建此 NilFreeHash 的实例并分配键值,它会起作用,但如果您想将哈希转换为此类的实例,这可能是一个问题。
Solution 1
I suggested this in my question before:
Hash#to_hash
is already defined, and returns self. Then you can do:The
to_hash
ensures that you get an empty hash when the previous key search fails.Solution2
This solution is similar in spirit to mu is too short's answer in that it uses a subclass, but still somewhat different. In case there is no value for a certain key, it does not use a default value, but rather creates a value of empty hash, so that it does not have the problem of confusion in assigment that DigitalRoss's answer has, as was pointed out by mu is too short.
It departs from the specification given in the question, though. When an undefined key is given, it will return an empty hash instread of
nil
.If you build an instance of this NilFreeHash from the beginning and assign the key-values, it will work, but if you want to convert a hash into an instance of this class, that may be a problem.
您可以使用额外的可变参数构建一个 Hash 子类,以便通过适当的检查一路向下挖掘。像这样的东西(当然有一个更好的名字):
然后只需使用
Thing
代替哈希:You could just build a Hash subclass with an extra variadic method for digging all the way down with appropriate checks along the way. Something like this (with a better name of course):
Then just use
Thing
s instead of hashes:这个哈希的猴子补丁函数应该是最简单的(至少对我来说)。它也不会改变结构,即将
nil
更改为{}
。即使您从原始源(例如 JSON)读取树,它仍然适用。它也不需要在运行或解析字符串时生成空的哈希对象。rescue nil
对我来说实际上是一个很好的简单解决方案,因为我有足够的勇气应对如此低的风险,但我发现它本质上在性能方面存在缺陷。示例:
还有一个好处是您可以使用它来处理保存的数组:
This monkey patch function for Hash should be easiest (at least for me). It also doesn't alter structure i.e. changing
nil
's to{}
. It would still also apply even if you're reading a tree from a raw source e.g. JSON. It also doesn't need to produce empty hash objects as it goes or parse a string.rescue nil
was actually a good easy solution for me as I'm brave enough for such a low risk but I find it to essentially have a drawback with performance.Example:
What's also good is that you can play around saved arrays with it:
XKeys gem 将通过增强功能,以简单、清晰、可读和紧凑的语法读取并自动激活写入时嵌套哈希 (::Hash) 或哈希和数组(::Auto,基于键/索引类型) #[] 和#[]=。哨兵符号 :[] 将推到数组的末尾。
The XKeys gem will read and auto-vivify-on-write nested hashes (::Hash) or hashes and arrays (::Auto, based on the key/index type) with a simple, clear, readable, and compact syntax by enhancing #[] and #[]=. The sentinel symbol :[] will push onto the end of an array.
您可以使用 andand gem,但我对此变得越来越警惕:
You can use the andand gem, but I'm becoming more and more wary of it:
有一种可爱但错误的方法可以做到这一点。这是对
NilClass
进行猴子补丁以添加返回nil
的[]
方法。我说这是错误的方法,因为您不知道其他软件可能会制作不同的版本,或者 Ruby 未来版本中的哪些行为更改可能会因此被破坏。更好的方法是创建一个新对象,其工作方式与 nil 非常相似,但支持此行为。使这个新对象成为哈希值的默认返回值。然后它就会起作用。
或者,您可以创建一个简单的“嵌套查找”函数,将散列和键传递给该函数,该函数按顺序遍历散列,并在可能时进行突破。
我个人更喜欢后两种方法之一。不过我认为如果第一个能够集成到 Ruby 语言中那就太好了。 (但是猴子补丁是一个坏主意。不要这样做。特别是不要展示你是一个多么酷的黑客。)
There is the cute but wrong way to do this. Which is to monkey-patch
NilClass
to add a[]
method that returnsnil
. I say it is the wrong approach because you have no idea what other software may have made a different version, or what behavior change in a future version of Ruby can be broken by this.A better approach is to create a new object that works a lot like
nil
but supports this behavior. Make this new object the default return of your hashes. And then it will just work.Alternately you can create a simple "nested lookup" function that you pass the hash and the keys to, which traverses the hashes in order, breaking out when it can.
I would personally prefer one of the latter two approaches. Though I think it would be cute if the first was integrated into the Ruby language. (But monkey-patching is a bad idea. Don't do that. Particularly not to demonstrate what a cool hacker you are.)
并不是说我会这样做,但你可以在
NilClass#[]
中使用 Monkeypatch:遵循 @DigitalRoss 的答案。是的,它需要更多的打字,但那是因为它更安全。
Not that I would do it, but you can Monkeypatch in
NilClass#[]
:Go with @DigitalRoss's answer. Yes, it's more typing, but that's because it's safer.
就我而言,我需要一个二维矩阵,其中每个单元格都是一个项目列表。
我发现这种技术似乎有效。它可能适用于OP:
输出是:
In my case, I needed a two-dimensional matrix where each cell is a list of items.
I found this technique which seems to work. It might work for the OP:
The output is:
我目前正在尝试这个:
我知道反对
try
的论点,但是在研究诸如params
之类的事情时它很有用。I am currently trying out this:
I know the arguments against
try
, but it is useful when looking into things, like say,params
.