blob blob - 5 months ago 17
Ruby Question

Ruby array of arrays and nil guard

I have the following code

class Room
attr_reader :content, :descr
def initialize
@content = get_content
@descr = get_description
end

def get_content
['errors', 'bugs', 'syntax problems'].sample
end

def get_description
"This room has #{self.content}"
end
end

@rooms = Array.new(3, Array.new(3))
@rooms[1][1] ||= Room.new
# => #<Room:0x68985d8 @content="errors", @descr="This room has errors">

p @rooms # =>
#[[nil, #<Room:0x68985d8 @content="errors", @descr="This room has errors">, nil],
# [nil, #<Room:0x68985d8 @content="errors", @descr="This room has errors">, nil],
# [nil, #<Room:0x68985d8 @content="errors", @descr="This room has errors">, nil]]


@rooms = Array.new(3, Array.new(3))
@rooms[1][2] ||= Room.new
# => #<Room:0x6aaab58 @content="bugs", @description="This room contains bugs">

p @rooms # =>
#[[nil, nil, #<Room:0x6aaab58 @content="bugs", @descr="This room has bugs">],
# [nil, nil, #<Room:0x6aaab58 @content="bugs", @descr="This room has bugs">],
# [nil, nil, #<Room:0x6aaab58 @content="bugs", @descr="This room has bugs">]]


It's supposed to create a new
Room
instance in one place (if it doesn't exist yet), not in the entire column at once. Instead of creating said instance in
[1][1]
or wherever, it's being set for
[0][2]
,
[1][2]
and
[2][2]
.

Why is that so? How can I change it to work correctly?

Answer

I struggled on this recently, you have to change this line @rooms = Array.new(3, Array.new(3)) into:

@rooms = Array.new(3) { Array.new(3) }

What happens in the former is that the argument Array.new(3) gets evaluated and creates a new array of 3 elements (I'll call it a). Then, doing Array.new(3, a) creates a new array of 3 elements, all elements containing a.

And a is a mutable object, so when you modify it for one column all the columns are modified.

In the solution, the block { Array.new(3) } is evaluated for each element, that's why each column is a different array.

Comments