0

I have a tictactoe program that first creates a gameplan where each row is a list containing an empty filler sign, which means the square is empty.

def createGamePlan(size, sign): 
    gPlan = []
    row = [sign]*size
    for i in range(size):
      gPlan.append(row)
    return gPlan

With a users choice of row and column I wish to update the sign of that specific element. So if i want to update the 1st column of the 2nd row to an "X" I would have

def updateGamePlan(row, col, gamePlan, sign):
    gamePlan[1][0] = "X"

This however changes the 1st column of every single row and I cant figure out why. I specify that its the 2nd row (1-th element of the gamePlan list, and the 0-th element of that inner list i.e the 1st column). Can someone point out what goes wrong and how I can, in the example above, only make it change the 1st column of the 2nd row, and not every row

da1g
  • 107
  • 2
  • 7

1 Answers1

3

The crucial thing to understand is that list objects do not have rows and columns. List objects are ordered, heterogeneous sequences of objects. When you do:

def createGamePlan(size, sign): 
    gPlan = []
    row = [sign]*size
    for i in range(size):
      gPlan.append(row) # appends the SAME object
    return gPlan

So, consider the following:

>>> a = ['foo']
>>> bar = []
>>> for _ in range(4):
...     bar.append(a)
...
>>> [id(x) for x in bar]
[4534044744, 4534044744, 4534044744, 4534044744]

The objects are all the same!

>>> bar[0].append('baz')
>>> bar
[['foo', 'baz'], ['foo', 'baz'], ['foo', 'baz'], ['foo', 'baz']]

You create a list that contains the same object many times. A solution? Append a copy.

def createGamePlan(size, sign): 
    gPlan = []
    row = [sign]*size
    for i in range(size):
      gPlan.append(row.copy()) # appends a NEW object
    return gPlan

Be careful, though, because .copy only makes a shallow copy. Consider:

>>> row = [['foo'], ['bar']]
>>> grid = []
>>> for _ in range(5):
...     grid.append(row.copy())
...
>>> grid
[[['foo'], ['bar']], [['foo'], ['bar']], [['foo'], ['bar']], [['foo'], ['bar']], [['foo'], ['bar']]]

OK, swell! these are all independant objects!:

>>> [id(x) for x in grid]
[4534044616, 4534135432, 4534135560, 4534135176, 4534135688]

So... this should work fine, no?

>>> grid[0][0].append('baz')
>>> grid
[[['foo', 'baz'], ['bar']], [['foo', 'baz'], ['bar']], [['foo', 'baz'], ['bar']], [['foo', 'baz'], ['bar']], [['foo', 'baz'], ['bar']]]

What's going on? Well, a shallow copy created new lists, but not new sublists, i.e., it didn't copy any elements contained in the elements:

>>> [id(x) for row in grid for x in row]
[4534135048, 4534135112, 4534135048, 4534135112, 4534135048, 4534135112, 4534135048, 4534135112, 4534135048, 4534135112]
>>>

For that, you want a deepcopy:

>>> import copy
>>> row = [['foo'], ['bar']]
>>> grid = []
>>> for _ in range(5):
...     grid.append(copy.deepcopy(row))
...
>>> grid
[[['foo'], ['bar']], [['foo'], ['bar']], [['foo'], ['bar']], [['foo'], ['bar']], [['foo'], ['bar']]]
>>> [id(x) for row in grid for x in row]
[4534135432, 4534135368, 4534135176, 4534135880, 4534136328, 4534161928, 4534135112, 4534162120, 4534162248, 4534162184]
>>> grid[0][0].append('baz')
>>> grid
[[['foo', 'baz'], ['bar']], [['foo'], ['bar']], [['foo'], ['bar']], [['foo'], ['bar']], [['foo'], ['bar']]]
>>>
juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172