I have a class that primarily implements some logic around a multi-dimensional array, which is essentially a grid of numbers. These numbers can swap positions, etc.. However, when they swap, other objects of the same class also appear to be modified. I'm not sure why.
I'm using instance variables to store the grid, so I don't understand why changes are apparently affecting other class members.
Here's a simplified example;
class TestGrid
attr_accessor :grid
@grid = []
def initialize(newgrid)
@grid = newgrid
end
def to_s
out = ""
@grid.each{|row|
out += row.join("|") + "\n"
}
out
end
def swap(x, y)
@grid[x[0]][x[1]], @grid[y[0]][y[1]] = @grid[y[0]][y[1]], @grid[x[0]][x[1]]
end
end
When we interact with a single instance in IRB, things look fine;
1.9.3-p385 :001 > require './TestGrid.rb'
=> true
1.9.3-p385 :002 > x = TestGrid.new([[1,2],[3,4]])
=> 1|2
3|4
1.9.3-p385 :003 > x.swap([0,1],[1,1])
=> [4, 2]
1.9.3-p385 :004 > puts x
1|4
3|2
=> nil
However, if I create a second instance by cloning or duping;
1.9.3-p385 :006 > x = TestGrid.new([[1,2],[3,4]])
=> 1|2
3|4
1.9.3-p385 :007 > y = x.clone
=> 1|2
3|4
1.9.3-p385 :008 > x.swap([0,1],[1,1])
=> [4, 2]
1.9.3-p385 :009 > puts x
1|4
3|2
=> nil
1.9.3-p385 :010 > puts y
1|4
3|2
=> nil
Why are my changes to x also being applied to y? From my understanding of Object#Clone, theses are supposed to be distinct instance, unrelated to each other. Their object ID's would seem to support that expectation;
1.9.3-p385 :012 > puts "#{x.object_id} #{y.object_id}"
70124426240320 70124426232820
For reference, I ended up creating an initialize_copy method which ensures the affected parameter is deep copied. I didn't really like the idea of Marshalling objects around just to copy an array deeply, so I decided on this instead.
def initialize_copy(original)
super
@grid = []
original.grid.each{|inner|
@grid << inner.dup
}
end