2

可能重复:
Ruby 方法 Array#<< 未更新哈希中的数组
使用 Hash.new([]) 时奇怪的 ruby​​ 行为

我一直在做很棒的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

4

2 回答 2

3

当您使用 Hash 的默认参数时,相同的对象用于所有未显式设置的键。这意味着这里只使用了一个数组,即您传入的数组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.new(0)的操作是安全的,因为您只能通过重新分配来更改这些值。但是因为我更喜欢在脑海中保留更少的概念,所以我几乎只使用块形式。

于 2012-11-11T23:24:03.663 回答
1

当你这样做

h = Hash.new(0)
h[:foo] += 1

您正在直接修改h. h[:foo] += 1是一样的h[:foo] = h[:foo]+1h[:foo]正在分配0+1

当你这样做

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

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

于 2012-11-11T23:17:37.583 回答