0

How do I write my class to access array for different objects?

class ListArray
  attr_accessor :arr

  def initialize arr
    @arr = arr
  end
end

a = ListArray.new([0, 1, 2, 3])
b = ListArray.new(a.arr)
a.arr[2] = 999
b.arr[2] = 4
a.arr[2] #=> 4  ?
b.arr[2] #=> 4

When I changed value of b.arr[2] to 4, a.arr[2] value (which should be 999) takes the value 4.

I don't know what I did wrong.

[edit]

My full code looks more like this:

class OtherClass
   def list
end

class ListArray
   attr_accessor :arr
   def initialize arr
      @arr = arr
   end
   def putItem ...
   def getItem ...
   def cutList &bloc ...
end

a = ListArray.new obj1_other_class.list
  # obj2_other_class.list =>  [[1, 2], [3, 4], ... ]
  # [3, 4] is an item
b = ListArray.new obj2_other_class.list
a.putItem [5, 6]
c = ListArray.new a.arr
c.arr += b.arr
c.arr[1][0] = 7
      ...

How can I avoid object's same id problems?

µgeek
  • 43
  • 5

2 Answers2

4
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.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • Thank you very much for your very complete answer! I really like to understand! I didn't think I had to worry about id's object before a long time... – µgeek Jan 18 '19 at 05:19
  • You’ve got a small typo just after your 4th code block — just after “The interesting thing here”, you’re missing `_id` on `b.arr_object`. I tried to submit an edit, but it had to be at least 6 characters different, and this was only 3... – Nate Jan 18 '19 at 06:11
  • No problem, @CarySwoveland! Solid explanation, btw. – Nate Jan 18 '19 at 06:27
  • Is it possible to implement 'a.arr.dup.map { |e| e.dup }' in initialize's method to avoid problems every time you call ListArray? – µgeek Jan 18 '19 at 06:40
  • µ, one could do that but it would be problematic if a nested array were ever passed to the method. If the array should always contain only literals--say integers--you could check that (in `initialize`) before `@arr = arr.dup` (e.g., `raise ArgumentError, "not all elements of #{arr} are integers" unless arr.all? { |n| n.is_a? Integer }`) . – Cary Swoveland Jan 18 '19 at 06:58
3

By

b = ListArray.new a.arr

You actually transfer the reference of a.arr to b, not it's value.

You can do it like this:

b = ListArray.new a.arr.dup

Check this question, which is about the argument passing of reference or value.

Til
  • 5,150
  • 13
  • 26
  • 34