Ruby数组:+=与push的区别

6
我有一个数组的数组,想要向子数组添加元素。+=可以实现我的需求,但我想知道为什么push不行。
我期望(并且使用+=正常工作)的行为是:
b = Array.new(3,[])
b[0] += ["apple"]
b[1] += ["orange"]
b[2] += ["frog"]
翻译: b => [["apple"], ["orange"], ["frog"]] 使用 push 方法,我将被推送的元素添加到每个子数组的末尾(为什么?):
a = Array.new(3,[])
a[0].push("apple")
a[1].push("orange")
a[2].push("frog")

a => [["苹果", "橘子", "青蛙"], ["苹果", "橘子", "青蛙"], ["苹果", "橘子", "青蛙"]]
希望能对您有所帮助。

1
没有愚蠢的问题,只有错误的答案。这是我的第一个问题。我已经编写 Ruby 代码两天了(编程 40 年),创建了处理任何游戏牌组相关操作的类(自学)。这是我无法通过文档解决的第一个问题(是的,我错过了文档中的某些内容——我相信这种情况永远不会发生在您身上)。我搜索了“ruby += push”和其他几个尝试,但没有找到任何信息。Eiko 的慷慨精神是一个好的指导者,@Jorg。我相信你是一个优秀的“业余程序员”,你不必通过欺负新手来证明它。 - David Bunnell
2个回答

11

这里的问题在于b = Array.new(3, [])会将同一个对象作为所有数组单元格的基本值:

b = Array.new(3, [])
b[0].object_id #=> 28424380
b[1].object_id #=> 28424380
b[2].object_id #=> 28424380

当你使用b[0].push时,它将项目添加到"each"子数组中,因为它们实际上都是同一个数组。

那么为什么b[0] += ["value"]会起作用呢?嗯,在查看 Ruby 文档后我们可以得到以下信息:

ary + other_ary → new_ary

连接 - 返回一个由连接两个数组生成的第三个新数组。

[ 1, 2, 3 ] + [ 4, 5 ]    #=> [ 1, 2, 3, 4, 5 ]
a = [ "a", "b", "c" ]
c = a + [ "d", "e", "f" ]
c                         #=> [ "a", "b", "c", "d", "e", "f" ]
a                         #=> [ "a", "b", "c" ]

请注意

x += y

就是等于

x = x + y

这意味着它会产生一个新的数组。因此,在数组上重复使用 += 可能会非常低效。

因此,当您使用 += 时,它会完全替换数组,这意味着 b[0] 中的数组不再与 b[1]b[2] 相同。

正如您所看到的:

b = Array.new(3, [])
b[0].push("test") 
b #=> [["test"], ["test"], ["test"]]
b[0].object_id #=> 28424380
b[1].object_id #=> 28424380
b[2].object_id #=> 28424380
b[0] += ["foo"]
b #=> [["test", "foo"], ["test"], ["test"]]
b[0].object_id #=> 38275912
b[1].object_id #=> 28424380
b[2].object_id #=> 28424380

如果你想要确保初始化一个数组的数组中每个数组都是唯一的,你可以这样做:

b = Array.new(3) { [] }

这种不同的语法允许您传递一个代码块,用于计算每个单元格的原始值。由于该代码块针对每个单元格运行,因此每次都会创建一个单独的数组。


2
非常感谢。这是我在这里的第一个问题,我不知道如何将您的答案设置为“正确”的答案。也感谢您的效率评论。我会注意的。顺便说一下,问题已解决:'b=Array.new(3)' '3.times {|i| b[i] = []}'(如果有更好的方法来“断开对象链接”,请告诉我)。--只是一个自学Ruby的老C/C++/C#程序员 :) - David Bunnell
@DavidBunnell 很高兴能帮忙!如果您看一下这个答案的左上角,您应该会看到一个勾号(在得分和投票的上下箭头旁边)。如果您点击那个勾号,它就会变成绿色,让大家知道这个答案对您有用。欢迎来到stackoverflow,祝您编码愉快 ^_^ - eiko
@DavidBunnell,我已经更新了我的答案,并提供了一种更为优雅的初始化数组的方式。不过,你在评论中提到的代码也很相似! - eiko

0

这是因为在第二个代码段中,您选择了子数组并将其推入,如果您想要一个数组的数组,则需要将该数组推入主数组。

a = Array.new(3,[])
a.push(["apple"])
a.push(["orange"])
a.push(["frog"])

为了获得与第一个相同的结果。

编辑:我忘记提到,因为您使用空数组作为元素初始化数组,所以在推入元素之前,您将有三个空元素。


4
这不正确,也忽略了问题的根源:Array.new 中的 [] 参数会为所有条目创建一个共享数组。 - tadman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接