可能重复:
Ruby method Array#<< not updating the array in hash
Strange ruby behavior when using Hash.new([])
我一直在做Koans这很棒,而且我一直都没有发现任何重大问题,但我偶然发现了这一点,并且无法理解它:
def test_default_value_is_the_same_object
hash = Hash.new([])
hash[:one] << "uno"
hash[:two] << "dos"
assert_equal ["uno", "dos"], hash[:one] # But I only put "uno" for this key!
assert_equal ["uno", "dos"], hash[:two] # But I only put "dos" for this key!
assert_equal ["uno", "dos"], hash[:three] # I didn't shove anything for :three!
assert_equal true, hash[:one].object_id == hash[:two].object_id
end
所有的测试都在通过(我只看了一下错误,它帮助我猜出了正确的断言)。
最后一个断言,好吧,它们都没有初始化,因此它们的值必须具有相同的对象ID,因为它们都采用默认值。
我不明白为什么默认值被改变,我甚至都不确定发生了什么。
我在IRB中尝试过,想到可能会对Hash / Array进行一些篡改让我发疯,但我得到了相同的结果。
我首先认为hash[:one] << "uno"
会暗示hash
成为{ one: ["uno] }
,但仍然是{ }
。
虽然我猜<<
只调用push
,但只有在使用=
符号时才会添加新密钥
请告诉我错过了什么。
编辑:我正在使用Ruby 1.9.3
答案 0 :(得分:3)
当您使用哈希的默认参数时,相同的对象将用于尚未显式设置的所有键。这意味着此处仅使用一个数组,即传递给Hash.new
的数组。请参阅下面的证据。
>> h = Hash.new([])
=> {}
>> h[:foo] << :bar
=> [:bar]
>> h[:bar] << :baz
=> [:bar, :baz]
>> h[:foo].object_id
=> 2177177000
>> h[:bar].object_id
=> 2177177000
奇怪的是,正如您所发现的,如果您检查哈希,您会发现它是空的!这是因为只修改了默认对象,尚未分配任何键。
幸运的是,还有另一种方法可以为哈希值执行默认值。您可以改为提供默认块:
>> h = Hash.new { |h,k| h[k] = [] }
=> {}
>> h[:foo] << :bar
=> [:bar]
>> h[:bar] << :baz
=> [:baz]
>> h[:foo].object_id
=> 2176949560
>> h[:bar].object_id
=> 2176921940
当您使用此方法时,每次使用未分配的密钥时,块都会被执行,并且它将提供哈希本身和密钥作为参数。通过在块中指定默认值,您可以确保为每个不同的键创建新对象,并且分配将自动进行。这是在Ruby中创建“Hash of Arrays”的惯用方法,并且通常比默认参数方法更安全。
也就是说,如果你正在使用不可变值(比如数字),那么像Hash.new(0)
那样做是安全的,因为你只需通过重新赋值来改变这些值。但是因为我更喜欢在脑子里保留较少的概念,所以我几乎只使用块形式。
答案 1 :(得分:1)
当你这样做时
h = Hash.new(0)
h[:foo] += 1
您正在直接修改h
。 h[:foo] += 1
与h[:foo] = h[:foo]+1
相同。 h[:foo]
被分配0+1
。
当你这样做时
h = Hash.new([])
h[:foo] << :bar
您正在修改h[:foo]
,[]
,这是h
的默认值,但不是h
的任何键的值。在[]
变为[:bar]
之后,默认值h
变为[:bar]
,但这不是h[:foo]
的值。