这是一个更一般的情况,说明了您遇到的问题以及解决问题的方法。假设我们有以下嵌套数组:
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] 是同一个对象(数组),我们只是更改了该对象的第一个元素。
当edges 改变时,我们如何避免改变new_edges?
首先,请注意new_edges = Array.new(edges) 可以替换为new_edges = edges.dup。和以前一样,edges 和 new_edges 将是不同的对象,但它们对应的所有嵌套数组都是相同的对象。
我们希望通过制作edges 的深拷贝来定义new_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]]]