Hash.new([])的行为不符合预期

时间:2012-11-11 23:02:33

标签: ruby arrays hash

  

可能重复:
  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

2 个答案:

答案 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

您正在直接修改hh[:foo] += 1h[:foo] = h[:foo]+1相同。 h[:foo]被分配0+1

当你这样做时

h = Hash.new([])
h[:foo] << :bar

您正在修改h[:foo][],这是h的默认值,但不是h的任何键的值。在[]变为[:bar]之后,默认值h变为[:bar],但这不是h[:foo]的值。