class ListArray
attr_accessor :arr
def initialize(arr)
@arr = arr
end
def arr_object_id
@arr.object_id
end
end
Create an instance of ListArray
, creating an instance variable @arr
equal to [0, 1, 2, 3]
:
a = ListArray.new [0, 1, 2, 3]
#=> #<ListArray:0x0000574d960e19e8 @arr=[0, 1, 2, 3]>
Let's check the value of @arr
and retrieve its object id:
a.arr
#=> [0, 1, 2, 3]
a.arr_object_id
#=> 47995370802440
Now create another instance of ListArray
, creating its instance variable @arr
and setting it equal to the value of a.arr
:
b = ListArray.new(a.arr)
#=> #<ListArray:0x0000574d9611bdf0 @arr=[0, 1, 2, 3]>
b.arr
#=> [0, 1, 2, 3]
b.arr_object_id
#=> 47995370802440
The interesting thing here is that a.arr_object_id == b.arr_object_id
. That's not surprising, however, because we initialized b
's instance variable to a
's instance variable, so they are the same object!
Next, change the value of a
's instance variable to [0, 1, 999, 3]
:
a.arr[2] = 999
a.arr
#=> [0, 1, 999, 3]
a.arr_object_id
#=> 47995370802420
Check if the value of b
's instance variable has changed:
b.arr
#=> [0, 1, 999, 3]
b.arr_object_id
#=> 47995370802440
It has, because a
's and b
's instance variables @arr
hold the same object.
To make b
's instance variable hold an array whose instance variables are the same as a
's, but make the two arrays different objects, create b
with its instance variable @arr
equal to a copy of the value of a
's instance variable:
a = ListArray.new [0, 1, 2, 3]
#=> #<ListArray:0x0000574d9610d818 @arr=[0, 1, 2, 3]>
a.arr_object_id
#=> ...320
b = ListArray.new(a.arr.dup)
#=> #<ListArray:0x0000574d961143c0 @arr=[0, 1, 2, 3]>
b.arr
#=> [0, 1, 2, 3]
b.arr_object_id
#=> ...100 (different than a.arr_object_id)
a.arr[2] = 19
a.arr
#=> [0, 1, 19, 3]
b.arr
#=> [0, 1, 2, 3]
But, wait, we're not finished. Here's a second example that illustrates why you cannot always merely apply dup
.
a = ListArray.new [0, [1, 2], 3]
#=> #<ListArray:0x0000574d9614b370 @arr=[0, [1, 2], 3]>
a.arr_object_id
#=> ...700
a.arr[1].object_id
#=> ...720
a.arr[1][1].object_id
#=> 5
2.object_id
#=> 5
b = ListArray.new(a.arr.dup)
#=> #<ListArray:0x0000574d96119258 @arr=[0, [1, 2], 3]>
b.arr
#=> [0, [1, 2], 3]
b.arr_object_id
#=> ...160 (different than a.arr_object_id)
b.arr[1].object_id
#=> ...720 (same as a.arr[1].object_id)
b.arr[1][1].object_id
#=> 5
Now change the value of a.arr[1][1]
:
a.arr[1][1] = 9
a.arr
#=> [0, [1, 9], 3] (as expected)
a.arr[1].object_id
#=> ...720 (no change)
b.arr
#=> [0, [1, 9], 3]
b.arr[1].object_id
#=> ...720 (no change)
You see this changes b[1][1]
as well. That's because the contents of the object that is the value of both a.arr[1]
and b.arr[1]
has been altered. Now try this.
a.arr[1] = [8, 0]
a.arr
#=> [0, [8, 0], 3] (as expected)
a.arr[1].object_id
#=> ...880 (a new object!)
b.arr
#=> [0, [1, 9], 3] (unchanged!)
b.arr[1].object_id
#=> ...720 (unchanged)
For this example we would need to write:
a = ListArray.new [0, [1, 2], 3]
b = ListArray.new(a.arr.dup.map { |e| e.dup })
a.arr[1][1] = 9
a.arr
#=> [0, [1, 9], 3]
b.arr
#=> [0, [1, 2], 3] (no change!)
a.arr.dup.map { |e| e.dup }
is referred to as a deeper copy of a.arr
than is a.arr.dup
. If there were yet deeper nested arrays ([1, [2, [3, 4]], 5]
) we would have to dup
to lower levels of a.arr
. For a Ruby newbie it's not important to fully understand how deep copies are constructed, merely that they are needed to achieve independence for duped copies of objects.