如何同时获取多个哈希值?

71
这是一个什么样的缩短版呢?
from = hash.fetch(:from)
to = hash.fetch(:to)
name = hash.fetch(:name)
# etc

注意fetch,如果键不存在,我想要引发一个错误。

一定有更短的版本,例如:

from, to, name = hash.fetch(:from, :to, :name) # <-- imaginary won't work

如果需要的话,可以使用ActiveSupport。


一个重要而未被问到的问题是,你想从哈希中重新分配值给变量,这样做有什么用途? - Mike Szyndel
@MichaelSzyndel 我无法解析你上面的评论。 - sawa
1
为什么你想要使用 from = hash.fetch(:from); to = hash.fetch(:to); 而不是直接使用 hash[:from] - Mike Szyndel
这是一个普遍性的问题,有两种不同的用例不能一一列举。就像有时需要使用fetch而不是[]来避免无声失败一样,有时也需要使用一个fetch版本的values_at - matthew.tuck
8个回答

126
使用 Hash 的 values_at 方法:
from, to, name = hash.values_at(:from, :to, :name)

对于哈希表中不存在的键,这将返回nil


10
这不符合楼主的要求。 - sawa
@MichaelSzyndel 请自己阅读问题。 - sawa
没有这样的方法可以获取多个键并引发缺失异常。其中一个原因可能是 - 当应该引发异常时 - 当键不存在时(它可能会为其他键留下未分配的变量)或在获取所有键之后?您可以轻松地自己添加此检查,我真的看不出为什么要坚持在此处引发异常。 - Mike Szyndel
3
“为什么坚持在这里抛出异常” - 因为必须确保键存在。“何时应该引发异常-当键不存在时(它可能会导致其他键的未分配变量)” - 是的,在普通的fetch中,这也会导致未分配变量。 - Dmytrii Nagirniak
是的,但为什么不直接使用raise MyException if hsh_values.include? nil呢? - Yo Ludke
@YoLudke,因为在键根本不存在的情况下,这样做是没有任何作用的。 - Altonymous

53

Ruby 2.3终于引入了哈希表的fetch_values方法,可以直接实现以下功能:

{a: 1, b: 2}.fetch_values(:a, :b)
# => [1, 2]
{a: 1, b: 2}.fetch_values(:a, :c)
# => KeyError: key not found: :c

所以现在它确实存在了,太棒了。如果可能的话,应该将其标记为答案,因为它并不是最容易找到的。 - Adrien Rey-Jarthon

5
hash = {from: :foo, to: :bar, name: :buz}

[:from, :to, :name].map{|sym| hash.fetch(sym)}
# => [:foo, :bar, :buz]
[:frog, :to, :name].map{|sym| hash.fetch(sym)}
# => KeyError

3
my_array = {from: 'Jamaica', to: 'St. Martin'}.values_at(:from, :to, :name)
my_array.keys.any? {|key| element.nil?} && raise || my_array

这将会像你要求的那样引发一个错误。
 my_array = {from: 'Jamaica', to: 'St. Martin', name: 'George'}.values_at(:from, :to, :name)
 my_array.keys.any? {|key| element.nil?} && raise || my_array

这将返回数组。
但是OP要求在缺少键时失败...
class MissingKeyError < StandardError
end
my_hash = {from: 'Jamaica', to: 'St. Martin', name: 'George'}
my_array = my_hash.values_at(:from, :to, :name)
my_hash.keys.to_a == [:from, :to, :name] or raise MissingKeyError
my_hash = {from: 'Jamaica', to: 'St. Martin'}
my_array = my_hash.values_at(:from, :to, :name)
my_hash.keys.to_a == [:from, :to, :name] or raise MissingKeyError

1
my_array.any? {|element| element.nil?} && raise 是不正确的,因为一个键可能存在一个 nil 值。 - Dmytrii Nagirniak
你是正确的。我检查了值而不是键。我会修改为键。 - vgoff
keys.to_a == [:from, :to, :name] 这是不正确的。在这里顺序很重要,但 Hash 的键没有特定的顺序,它应该至少 keys.to_a - [:from, :to, :name] == []。总的来说,这只是一份不划算的工作。 - Dmytrii Nagirniak
如果你进行减法,但是键值并不存在,你最终会得到一个空数组,这也不好。因此,匹配一个空数组并不意味着该键值一定存在。错过了它。 - vgoff

1
"

我会选择最简单的事情

"
from, to, name = [:from, :to, :name].map {|key| hash.fetch(key)}

如果你想使用values_at,你可以使用带有默认值块的Hash

hash=Hash.new {|h, k| raise KeyError.new("key not found: #{k.inspect}") }
# ... populate hash
from, to, name = hash.values_at(:from, :to, :name) # raises KeyError on missing key

或者,如果您有兴趣,可以猴子补丁Hash
class ::Hash
  def fetch_all(*args)
    args.map {|key| fetch(key)}
  end
end
from, to, name = hash.fetch_all :from, :to, :name

这与我的有什么不同? - sawa
@sawa 显然,它分配了结果 LOL :) - Dmytrii Nagirniak

1
你可以使用 KeyError 对象作为默认值来初始化哈希表。如果你尝试获取的键不存在,它将返回一个 KeyError 的实例。然后,你只需要检查它(值的)类别,如果是 KeyError 就抛出它。
hash = Hash.new(KeyError.new("key not found"))

让我们向这个哈希表中添加一些数据。
hash[:a], hash[:b], hash[:c] = "Foo", "Bar", nil

最后查看值并在找不到键时引发错误。
hash.values_at(:a,:b,:c,:d).each {|v| raise v if v.class == KeyError}

只有在键不存在时,它才会引发异常。如果您的键具有 nil 值,它不会抱怨。


我无法控制哈希的创建方式。因此,我必须重新创建/复制才能使用“特殊”值作为无键标记。 - Dmytrii Nagirniak

1

模式匹配是 Ruby 2.7 中的一个实验性特性。

hash = { from: 'me', to: 'you', name: 'experimental ruby' }

hash in { from:, to:, name: }

查看{{link1}}


0

我需要返回一个哈希值,所以我进行了一个补丁:

class Hash
  def multi_fetch(*keys)
    keys.map { |key| [key, fetch(key)] }.to_h
  end
end

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