Here is a more general situation that illustrates the problem you are having and ways of correcting it. Suppose we had the following nested arrays:
a0 = [1, 2]
a1 = [3, 4]
a = [a0, a1]
#=> [[1, 2], [3, 4]]
edges = [a]
#=> [[[1, 2], [3, 4]]]
a0
, a1
, a
and edges
have unique object ids:
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
For readability I have removed the first seven digits of each of the object ids, which in all cases is 4833847
. Note edges[0][1][0] #=> 3
and 3.object_id #=> 7
. For reasons of efficiency, integers (and certain other Ruby objects have fixed, smallish, object ids.
Now create a new array from edges
using the method Array::new:
new_edges = Array.new(edges)
#=> [[[1, 2], [3, 4]]]
Examine the (last six digits of the) object ids:
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)
It is seen that new_edges
is a new object, but all of its nested arrays and elements are the same objects as the corresponding nested arrays and elements in edges
.
Now let's do the following:
edges[0][1][0] = 5
edges[0][1][0].object_id #=> 11
Then
edges
#=> [[[1, 2], [5, 4]]]
new_edges
#=> [[[1, 2], [5, 4]]]
new_edges
was changed as well as edges
because edges[0][1]
and new_edges[0][1]
are the same object (array) and we just changed the first element of that object.
How do we avoid changing new_edges
when edges
is changed?
Firstly, note that new_edges = Array.new(edges)
can be replaced with new_edges = edges.dup
. As before, edges
and new_edges
will be different objects but all of their corresponding nested arrays will the same objects.
We wish to define new_edges
by making a deep copy of edges
, so that changes to the latter will not affect the former, and vice-versa:
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)
Now change a nested element in edges
and observe the values of edges
and new_edges
:
edges[0][1][0] = 5
edges
#=> [[[1, 2], [5, 4]]]
new_edges
#=> [[[1, 2], [3, 4]]]
It is seen that new_edges
is not modified.
If there are greater levels of nesting it can become tedious and error-prone to make a deep copy using map
and dup
. An easier way is to use Marshal#dump and Marshal#load, which create deep copies of a wide range of Ruby objects that may contain multiple levels of nested objects:
edges
#=> [[[1, 2], [5, 4]]]
new_edges = Marshal.load(Marshal.dump(edges))
#=> [[[1, 2], [5, 4]]]
Changes to edges
will now leave new_edges
unaffected.
edges[0][1][0] = 3
edges
#=> [[[1, 2], [3, 4]]]
new_edges
#=> [[[1, 2], [5, 4]]]