散列分配奇怪行为中的Ruby数组

时间:2017-01-04 17:17:57

标签: ruby

我试图理解为什么以下代码奇怪地表现出来(据我所知)。

[1] pry(main)> a = {}
=> {}
[2] pry(main)> a[1] = [[0,0]] * 7
=> [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
[3] pry(main)> a[2] = [[0,0]] * 7
=> [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
[4] pry(main)> a[1][2][0] = 3 # Should be one value changed, right? 
=> 3
[5] pry(main)> a
=> {1=>[[3, 0], [3, 0], [3, 0], [3, 0], [3, 0], [3, 0], [3, 0]],
 2=>[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]}

我认为应该发生的是,索引a的密钥1的哈希2中的数组的一个值应该更改为3,而是全部整个数组的第一个值更改为3。这里发生了什么,我错过了什么?这是我的Ruby版本。

$ ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux]

编辑:

我也试过以下

[1] pry(main)> a = {}
=> {}
[2] pry(main)> a[1] = ([[0,0].dup].dup * 7).dup
=> [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
[3] pry(main)> a[2] = ([[0,0].dup].dup * 7).dup
=> [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
[4] pry(main)> a[1][2][0] = 3
=> 3
[5] pry(main)> a
=> {1=>[[3, 0], [3, 0], [3, 0], [3, 0], [3, 0], [3, 0], [3, 0]],
 2=>[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]}
[6] pry(main)> a = {}
=> {}
[7] pry(main)> a[1] = ([[0,0].clone].clone * 7).clone
=> [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
[8] pry(main)> a[2] = ([[0,0].clone].clone * 7).clone
=> [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
[9] pry(main)> a
=> {1=>[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]],
 2=>[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]}
[10] pry(main)> a[1][2][0] = 3
=> 3
[11] pry(main)> a
=> {1=>[[3, 0], [3, 0], [3, 0], [3, 0], [3, 0], [3, 0], [3, 0]],
 2=>[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]}

当然值应该是副本吗?

2 个答案:

答案 0 :(得分:3)

a[1]中的所有元素都引用相同的数组。

执行[0,0]后,

[[0,0]] * 7未被深层复制。

解决方案:a[1] = Array.new(7) { [0,0] }(感谢@Stefan!)

答案 1 :(得分:0)

以下是如何诊断此问题:

object_id会告诉你Ruby保存对象的“插槽”。对象应具有唯一ID:

foo = []
bar = []
foo.object_id # => 70322472940660
bar.object_id # => 70322472940640

如果他们不这样做,虽然他们可能是不同的变量名,但他们仍然指向同一个对象。这在更高级的用途中可能是理想的,但通常它不是你想要的东西,因为改变一个会改变另一个会导致混乱,咬牙切齿并可能回归“不应该被命名的人”。例如,当我们看到时,我们会被愚弄:

[[]] * 2 # => [[], []]

因为看起来我们正在找回两个阵列,可以使用并行分配来分配,例如:

foo, bar = [[]] * 2

但是,当我们查看foobar以查看它们存储的位置时,我们可以看到问题,它们都指向相同的插槽,因此更改其中一个会更改另一个:

foo.object_id # => 70323794190700
bar.object_id # => 70323794190700
foo << 1
bar # => [1]

编写代码时,这是一个古老的问题;您必须了解“按值传递”与“按引用传递”以及当某种语言正在执行某项操作时,否则会出现此问题。然后你必须学习如何告诉语言以避免这个问题。