当用于修改数组元素时,Array.each有时会产生不可预测的结果

时间:2011-09-14 00:30:09

标签: ruby-on-rails ruby arrays reference

我在RoR应用程序中调试了一个问题,并遇到了下面的代码(其中'map'是一个二维的整数数组)。代码尝试复制并附加每个子数组的最后一个元素:

map.each { |x| x << x[-1] }

在这行代码之前,

    (rdb:29) p map
    [[1, 2, 1, 3, 4], [1, 2, 2, 3, 4], [1, 2, 2, 3, 4], [1, 2, 2, 3, 4],
    [1, 2, 2, 3, 4], [1, 2, 2, 3, 4]]
    (rdb:29) p map.class
    Array
    (rdb:29) p map.first.class
    Array
    (rdb:29) p map.last.class
    Array

后:

    (rdb:29) p map
    [[1, 2, 1, 3, 4, 4], [1, 2, 2, 3, 4, 4], [1, 2, 2, 3, 4, 4],
    [1, 2, 2, 3, 4, 4], [1, 2, 2, 3, 4, 4, 4], [1, 2, 2, 3, 4, 4, 4]]

这里的问题是最后2个子数组附加了两个整数而不是一个整数,而前4个子数组是正确的。我更改了代码以使用Array.map,然后它正常工作:

map = map.map { |x| x + [x[-1]] }

总结一下: 我知道Array.each块中的迭代元素不应该被更改。但是为什么这样做会产生不可预测的结果呢?代码实际上大部分时间都在工作,有时会看到这个问题。它是Ruby或RoR中的错误吗?

1 个答案:

答案 0 :(得分:2)

修改each块内的子阵列不是你的问题,根本就没有错。您的问题是您的外部数组map有时包含对同一子数组对象的多个引用。

考虑一下:

>> x = [[1, 2, 1, 3, 4], [1, 2, 2, 3, 4], [1, 2, 2, 3, 4]]
>> x.each { |a| a << a[-1] }
=> [[1, 2, 1, 3, 4, 4], [1, 2, 2, 3, 4, 4], [1, 2, 2, 3, 4, 4]]

每次结果都是一样的。但如果你有这个:

>> gotcha = [1, 2, 1, 3, 4]
>> x = [[1, 2, 1, 3, 4], gotcha, gotcha]
>> x.each { |a| a << a[-1] }
=> [[1, 2, 1, 3, 4, 4], [1, 2, 1, 3, 4, 4, 4], [1, 2, 1, 3, 4, 4, 4]]

然后,您将获得您所看到的额外尾随元素(每次),因为gotcha被修改两次。第二种情况下的x puts与第一种情况下的x相同,但它们不相同。

您的Array#map方法始终有效,因为:

x + [x[-1]]

基本上复制x,然后将x[-1]附加到该副本,它永远不会修改x,因此上面的gotcha行为不会发生。

你无法摆脱指针,即使它们被称为引用也是如此。