我以前有过使用python的经验,并且对ruby还是陌生的。 阵列真的让我感到困惑。 这是我的代码示例:
edges = [[[1, 2], [100, 200]], [[700, 400], [1000, 2000]]]
new_edges = Array.new(edges)
new_edges.map{|edge| edge.map{|point| point.insert(0, 808912)}}
edges
#=> [[[808912, 1, 2], [808912, 100, 200]], [[808912, 700, 400], [808912, 1000, 2000]]]
new_edges
#=> [[[808912, 1, 2], [808912, 100, 200]], [[808912, 700, 400], [808912, 1000, 2000]]]
我相信会发生的是,edges
中的值不会被更改,而new_edges
中的值将被更改,但是两者都被更改。
如何创建初始数组的副本并进行操作而不更改任何缩写?
答案 0 :(得分:6)
您的代码具有与Python中相同的问题:
edges = [[[1, 2], [100, 200]], [[700, 400], [1000, 2000]]]
new_edges = list(edges)
for edge in edges:
for point in edge:
point.insert(0, 808912)
print(edges)
即您变换了外部数组,但内部数组仅由引用保存,因此更改一个(通过insert
)将更改edges
和new_edges
的深层内容。通过使用this tool逐步遍历代码,可以很容易地理解这类问题(尽管名称如此,它同时适用于Python和Ruby)。
在Ruby中,您可以使用insert
,而不是使用+
来修改数组,{>
edges = [[[1, 2], [100, 200]], [[700, 400], [1000, 2000]]]
new_edges = edges.map { |edge| edge.map { |point| [808912] + point } }
# => [[[808912, 1, 2], [808912, 100, 200]], [[808912, 700, 400], [808912, 1000, 2000]]]
edges
# => [[[1, 2], [100, 200]], [[700, 400], [1000, 2000]]]
答案 1 :(得分:2)
这是一个更一般的情况,它说明了您遇到的问题以及纠正方法。假设我们有以下嵌套数组:
a0 = [1, 2]
a1 = [3, 4]
a = [a0, a1]
#=> [[1, 2], [3, 4]]
edges = [a]
#=> [[[1, 2], [3, 4]]]
a0
,a1
,a
和edges
具有唯一的对象ID:
edges.object_id #=> 1208140
a.object_id #=> 1085620
edges[0].object_id #=> 1085620
a0.object_id #=> 0977080
a[0].object_id #=> 0977080
edges[0][0].object_id #=> 0977080
a1.object_id #=> 0995980
a[1].object_id #=> 0995980
edges[0][1].object_id #=> 0995980
edges[0][1][0].object_id #=> 7
出于可读性考虑,我删除了每个对象ID的前七个数字,在所有情况下均为4833847
。请注意edges[0][1][0] #=> 3
和3.object_id #=> 7
。出于效率考虑,整数(以及某些其他Ruby对象具有固定的,较小的对象ID。
现在使用方法Array::new从edges
创建一个新数组:
new_edges = Array.new(edges)
#=> [[[1, 2], [3, 4]]]
检查对象ID(的最后六位数字):
new_edges.object_id #=> 2400460 (different than edges.object_id)
new_edges[0].object_id #=> 1085620 (same as edges[0].object_id)
new_edges[0][0].object_id #=> 0977080 (same as edges[0][0].object_id)
new_edges[0][1].object_id #=> 0995980 (same as edges[0][1].object_id)
new_edges[0][1][0].object_id #=> 7 (same as edges[0][1][0].object_id)
可以看到new_edges
是一个新对象,但是其所有嵌套数组和元素都是与edges
中相应嵌套数组和元素相同的对象。
现在让我们执行以下操作:
edges[0][1][0] = 5
edges[0][1][0].object_id #=> 11
然后
edges
#=> [[[1, 2], [5, 4]]]
new_edges
#=> [[[1, 2], [5, 4]]]
new_edges
和edges
都被更改了,因为edges[0][1]
和new_edges[0][1]
是同一对象(数组),而我们只是更改了该对象的第一个元素。>
如何更改new_edges
时更改edges
?
首先,请注意,new_edges = Array.new(edges)
可以替换为new_edges = edges.dup
。和以前一样,edges
和new_edges
将是不同的对象,但是它们所有对应的嵌套数组将是相同的对象。
我们希望通过制作new_edges
的深拷贝来定义edges
,以便对后者的更改不会影响前者,反之亦然:>
new_edges = edges.map { |a| a.map { |aa| aa.dup } }
#=> [[[1, 2], [3, 4]]]
new_edges.object_id #=> 2134620 (different than edges.object_id)
new_edges[0].object_id #=> 2134600 (different than edges[0].object_id)
new_edges[0][0].object_id #=> 2134580 (different than edges[0][0].object_id)
new_edges[0][1].object_id #=> 2134560 (different than edges[0][1].object_id)
现在更改edges
中的嵌套元素,并观察edges
和new_edges
的值:
edges[0][1][0] = 5
edges
#=> [[[1, 2], [5, 4]]]
new_edges
#=> [[[1, 2], [3, 4]]]
可以看到new_edges
没有被修改。
如果嵌套的级别更高,则使用map
和dup
进行深层复制可能会变得很繁琐且容易出错。一种更简单的方法是使用Marshal#dump和Marshal#load,它们创建可能包含多个嵌套对象级别的各种Ruby对象的深层副本:
edges
#=> [[[1, 2], [5, 4]]]
new_edges = Marshal.load(Marshal.dump(edges))
#=> [[[1, 2], [5, 4]]]
对edges
的更改现在将使new_edges
不受影响。
edges[0][1][0] = 3
edges
#=> [[[1, 2], [3, 4]]]
new_edges
#=> [[[1, 2], [5, 4]]]
答案 2 :(得分:0)
您以
开始edges = [[[1, 2], [100, 200]], [[700, 400], [1000, 2000]]]
由于它是一个嵌套数组,因此Dup在这里不起作用,因此您需要先进行deep_dup,然后再进行操作。 (如果您不想变异原始数组)
这是一个用猴子修补的deep_dup(数组)的示例
def deep_dup
new_arr = []
self.each do |ele|
if ele.is_a? Array
new_arr << ele.deep_dup
else
new_arr << ele
end
end
new_arr
end
您可以观察。这样做更清洁,但无论如何
编辑:数组具有内置的deep_dup方法,请使用该方法。我一直以为Ruby将deep_dup留给实施,但是我错了。