无法将数组用作Ruby哈希的默认值?

8

我正在向一个哈希键添加项目。 我期望得到的结构应该像这样:

{ 
  'a' : [1],
  'b' : [2, 3, 4]
}

我使用数组来初始化哈希表。
irb> hash = Hash.new([])
 => {} 

然后开始使用它:

irb> hash['a'] << 1
 => [1] 
irb> hash['b'] << 2
 => [1, 2] 

但事实证明:
irb> hash
 => {}

你对你所看到的输出结果不确定吗?或者哈希表实际包含了什么?在这里不清楚你正在寻找什么。 - jmccarthy
4个回答

11

可以尝试使用以下方法:

hash = Hash.new{|h, k| h[k] = []}
hash['a'] << 1 # => [1]
hash['b'] << 2 # => [2]

你得到意外结果的原因是你指定了一个空数组作为默认值,但是使用的是同一个数组;没有进行复制。正确的方法是使用一个新的空数组来初始化值,就像我的代码中那样。


+1 "你指定了一个空数组作为默认值,但是同一个数组被使用了"。太好了! - the Tin Man
什么?这对我来说真的很奇怪。当你使用一个字符串时,该字符串并没有被重复使用。该字符串可能是某种值类型,但我认为在Ruby中“一切”都是对象。为什么字符串和其他对象之间会有差异? - Automatico
@Cort3z:不确定你的意思。也许这可以帮助说明没有区别:a = b = 'foo'; a.replace 'bar'; b # => 'bar' - Marc-André Lafortune
也许我表达不够清晰:我的意思是,当你执行 Hash.new([]) 时,它使用相同的数组对象,但是当你执行 Hash.new("string") 时,它并没有使用相同的字符串,这让我感到很奇怪。 - Automatico
@Cort3z:它将使用相同的字符串。h = Hash.new("don't do this"); h[0].upcase!; h[1] # => "DON'T DO THIS" - Marc-André Lafortune
@Marc-AndréLafortune 现在我明白发生了什么。我正在执行 h = Hash.new("W"); h["test"] = "data"; p h["a"] #=> "W"。我猜它只重新分配了 h["test"] 对象,而没有实际更改对象。 - Automatico

4
你使用的构造函数[]作为默认值存储在访问未知键时返回。由于Array#<<直接修改其接收器,因此这个最初为空的数组会增长。
更详细地解释一下:
当你执行hash['a'] << 1时,发生以下情况:
  1. hash查看是否有名为'a'的键
  2. 它发现没有这样的键。
  3. 它查看是否存储了要返回的默认值。
  4. 由于你使用了Hash.new([])构造它,所以它确实有这样一个值[]并返回它。
  5. 现在评估[] << 1,这意味着哈希现在存储[1]作为请求先前未遇到的键时返回的值。
如果你想要存储键值对,请使用构造函数的第三种形式,并使用块:
hash = Hash.new{|h, key| h[key] = []}

2

hash['a'] << 1hash['b'] << 2 不是创建键/值对的正确语法。你必须使用=

hash['a'] = []
hash['a'] << 1

hash['b'] = []
hash['b'] << 2

这将为您提供哈希值 {'a' : [1], 'b' : [2]}


1

这正是你所期望看到的行为。

你从未向Hash中添加任何内容,因此Hash完全为空。当你查找一个键时,该键永远不存在,因此返回默认值,你已指定为一个Array

所以,你查找不存在的键'a',因此返回你指定的Array作为默认值。然后,你在该Array上调用<<,将一个值(1)附加到它上面。

接下来,你查找不存在的键'b',因此返回你指定的Array作为默认值,该数组现在包含了你之前添加的元素1。然后,你在该Array上调用<<,将值2附加到它上面。

你最终得到的是一个仍然为空的Hash,因为你从未向其中添加任何内容。现在Hash的默认值是包含值12的数组。

你看到的输出是因为IRb总是打印出最后评估的表达式的结果。你示例中的最后一个表达式是在Array上调用<<<<返回其接收器,这就是整个表达式的返回值,因此是IRb打印出的内容。


它恰好做你所要求的,却完全不做你所想的! - Andrew Grimm

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