Search code examples
pythonrecursionlist-comprehensionnested-lists

Stored Location Issue With List Comprehensions


I'm trying to write a function in Python that creates a game board with a specified number of dimensions. The idea is for the board to be represented as nested lists, with the initial value of each space being the same (and being specified in the input of the function). To do this, I wrote the following code:

def make_board(dimensions, val, lst = []):
    if not dimensions:
        return lst
    elif len(dimensions) == 1 and not lst:
        return [val for i in range(dimensions[0])]
    elif len(dimensions) == 1 and lst:
        return [lst.copy() for i in range(dimensions[0])]
    else:
        if not lst:
            new_lst = [val for i in range(dimensions[-1])]
        else:
            new_lst = [lst.copy() for i in range(dimensions[-1])]
        return make_board(dimensions[:-1], val, new_lst)

This runs fine when I initially make the board. However, if I subsequently try to update one of the spaces, I run into an issue where it updates more than one space. So for example, this is what I get when I run the following:

example = make_board((2, 4, 2), 0)
print(example)
>>> [[[0, 0], [0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0], [0, 0]]]
example[1][3][0] = 4
>>> [[[0, 0], [0, 0], [0, 0], [4, 0]], [[0, 0], [0, 0], [0, 0], [4, 0]]]

For some reason that I'm not understanding, it seems like my list comprehensions in the function are storing the values/lists at the same address. Specifically, when I iterate over the 8 inner lists of length 2 and print the hex id's, the hex id's are the same for the corresponding indices (so example[0][0] has the same hex id as example[1][0] and so on).

One thing I noticed is that initially when I tried to change the value to 4, I got [[[4, 0], [4, 0], [4, 0], [4, 0]], [[4, 0], [4, 0], [4, 0], [4, 0]]]. When I added .copy() to the two instances of lst in my function above, it changed it so that there was only the one duplicate. However, I'm confused about where to go from here. I haven't run into this problem with list comprehensions before so any advice on what else I might do to try trouble shooting would be greatly appreciated.

This runs fine when I initially make the board. However, if I subsequently try to update one of the spaces, I run into an issue where it updates more than one space. So for example, this is what I get when I run the following:

example = make_board((2, 4, 2), 0)
print(example)
>>> [[[0, 0], [0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0], [0, 0]]]
example[1][3][0] = 4
>>> [[[0, 0], [0, 0], [0, 0], [4, 0]], [[0, 0], [0, 0], [0, 0], [4, 0]]]

For some reason that I'm not understanding, it seems like my list comprehensions in the function are storing the values/lists at the same address. Specifically, when I iterate over the 8 inner lists of length 2 and print the hex id's, the hex id's are the same for the corresponding indices (so example[0][0] has the same hex id as example[1][0] and so on).

One thing I noticed is that initially when I tried to change the value to 4, I got [[[4, 0], [4, 0], [4, 0], [4, 0]], [[4, 0], [4, 0], [4, 0], [4, 0]]]. When I added .copy() to the two instances of lst in my function above, it changed it so that there was only the one duplicate. However, I'm confused about where to go from here. I haven't run into this problem with list comprehensions before so any advice on what else I might do to try trouble shooting would be greatly appreciated.


Solution

  • x.copy() returns a shallow copy of x (i.e. it constructs a new list containing references to the elements within the original list)--instead, using copy.deepcopy(), which recursively copies the objects of the elements contained in the original list into the new list, should work for you:

    import copy
    
    def make_board(dimensions, val, lst=[]):
        if not dimensions:
            return lst
        elif len(dimensions) == 1 and not lst:
            return [val for _ in range(dimensions[0])]
        elif len(dimensions) == 1 and lst:
            return [copy.deepcopy(lst) for _ in range(dimensions[0])]
        else:
            if not lst:
                new_lst = [val for _ in range(dimensions[-1])]
            else:
                new_lst = [copy.deepcopy(lst) for _ in range(dimensions[-1])]
        return make_board(dimensions[:-1], val, new_lst)
    
    example = make_board((2, 4, 2), 0)
    example[1][3][0] = 4
    print(example)
    

    this returns

    [[[0, 0], [0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0], [4, 0]]]