2

I have a 8x8 array that contains objects in some positions, ' ' in others. (It's a chessboard)

My code was running slowly partially due to the use of copy.deepcopy(x) so I did some testing and found that iterating through the array was something like 32x faster. When I ran the code it threw errors so I compared the result from the iteration and the copy.deepcopy(x) and they aren't the same. I can't see where I'm going wrong or find anything similar (I have looked)

the function is below:

def makeCopy(array):
    new = [[],[],[],[],[],[],[],[]]
    for x in range(0,8):
        for y in range(0,8):
            new[x].append(array[x][y])
    a = copy.deepcopy(array)
    if new != a:
        print('failed')
    else:
        print('good')
    return new

When it gets called it prints failed so they aren't the same, which breaks the rest of my code.

I'm passing it something like this (some piece positions may differ):

currentGameState = [[Rook('black', [0,0]),Knight('black',[1,0]),Bishop('black', [2,0]),Queen('black',[3,0]),King('black',[4,0]),Bishop('black', [5,0]),Knight('black',[6,0]),Rook('black', [7,0])],
                    [Pawn('black', [0,1]),Pawn('black', [1,1]),Pawn('black', [2,1]),Pawn('black', [3,1]),Pawn('black', [4,1]),Pawn('black', [5,1]),Pawn('black', [6,1]),Pawn('black', [7,1])],
                    [' ',' ',' ',' ',' ',' ',' ',' '],
                    [' ',' ',' ',' ',' ',' ',' ',' '],
                    [' ',' ',' ',' ',' ',' ',' ',' '],
                    [' ',' ',' ',' ',' ',' ',' ',' '],
                    [Pawn('white', [0,6]),Pawn('white', [1,6]),Pawn('white', [2,6]),Pawn('white', [3,6]),Pawn('white', [4,6]),Pawn('white', [5,6]),Pawn('white', [6,6]),Pawn('white', [7,6])],
                    [Rook('white', [0,7]),Knight('white', [1,7]),Bishop('white', [2,7]),Queen('white',[3,7]),King('white',[4,7]),Bishop('white', [5,7]),Knight('white',[6,7]),Rook('white', [7,7])]]
  • Have you tried `new = list(map(list, array))`? – tobias_k Jul 03 '20 at 13:21
  • @tobias_k just tried it, same issue – user8430559 Jul 03 '20 at 13:26
  • Ok - do these custom classes have `__copy__`/`__deepcopy__`/`__eq__`/`__neq__` methods? – hemmelig Jul 03 '20 at 13:27
  • @hemmelig no - could you point me to some documentation for those? I'm a student who's been given this as a task to keep me occupied - this is my first properly object-oriented program so I'm a little out of my depth – user8430559 Jul 03 '20 at 13:29
  • Is it actually the case that `a == array`? Maybe your classes `__eq__` method is just broken, so the structure is fine, but the lists are still not "equal". – tobias_k Jul 03 '20 at 13:29
  • @tobias_k I'm fairly sure that the structure is what is broken, when changing from using my ```makeCopy``` function to ```deepcopy``` the rest of the code runs fine, albeit slowly, when using my function it starts crashing all over the place – user8430559 Jul 03 '20 at 13:31
  • Please check `copy.deepcopy(array) == array` anyway. For me your code works fine (substituting the `Rook` classes etc. with simple lists or tuples) – tobias_k Jul 03 '20 at 13:33
  • Look up dunder methods in Python. They are magic methods that can provide a host of additional utility for your classes. `__eq__`, for example, will allow you to use the rich operator `==` to compare between instances of your classes. Also see: [How to override the copy deepcopy operations for a python object](https://stackoverflow.com/questions/1500718/how-to-override-the-copy-deepcopy-operations-for-a-python-object) – hemmelig Jul 03 '20 at 13:36
  • 1
    @tobias_k Ok, ```copy.deepcopy(array) == array``` returns ```False```, but I'm not sure why that effects it, for some reason the result of ```copy.deepcopy``` works in my program and the iterative copy doesn't – user8430559 Jul 03 '20 at 13:38
  • Do you actually want to copy the game pieces, too? They seem to include their current position. When you move a piece, do you modify it or create a new one? Still, the basic structure-copying should work fine. – tobias_k Jul 03 '20 at 13:39
  • My guess is: `deepcopy` also copies the game pieces, which is required for your program to work if you modify the pieces (see above comment), and since you probably do not implement `__eq__`, the new instances are not considered equal to their copies (without `__eq__`, `==` just compares memory addresses). – tobias_k Jul 03 '20 at 13:40
  • @tobias_k ok, looks like I do want to be copying the piece objects, how would I do that? use copy.copy or does it have to be deepcopy? – user8430559 Jul 03 '20 at 13:48
  • I think hemmelig and tobias_k have hit on the real problem. There's nothing wrong with the copying. The problem is that two objects of the same (user-defined) class with exactly the same data contents don't compare equal unless you provide custom comparison functions. See [Compare object instances for equality by their attributes](https://stackoverflow.com/questions/1227121/compare-object-instances-for-equality-by-their-attributes). – Dennis Sparrow Jul 03 '20 at 14:01
  • @DennisSparrow I think there has to be a problem with my iterative copy as it's results don't work but deepcopy's do. I think it is because the iterative one doesn't create a new object of the class as tobias_k said. – user8430559 Jul 03 '20 at 14:07
  • Yes, on further investigation, I think that's right, as in the answer from C. Celora. – Dennis Sparrow Jul 03 '20 at 14:17

4 Answers4

2

You're not actually copying the objects inside the matrix.

for x in range(0,8):
    for y in range(0,8):
        new[x].append(array[x][y])

With this code you're actually binding the same object inside a single cell between the two list of lists. Doing this if you change one object inside the first list's cell it will change the object inside the other list's cell. That's why it's faster

With copy.deepcopy(array) you copy the cells and also the objects inside each cell.

Python3 copy module: https://docs.python.org/3/library/copy.html

C. Celora
  • 429
  • 1
  • 3
  • 12
  • so deepcopy is my only option here? – user8430559 Jul 03 '20 at 13:27
  • I'm afraid yes. Can i ask what is the purpose of copying the chess board? – C. Celora Jul 03 '20 at 13:39
  • 1
    You do not have to use `copy.deepcopy`, though. Just also create a copy of the pieces in your nested loop, in particular creating a new list for the mutable position. – tobias_k Jul 03 '20 at 13:42
  • Making moves to be checked by minimax. the makeMove function gets hit like a million times or something stupid so small gains their speed up minimax hugely. And mskeMove relies on copying, hence me attempting to speed it up – user8430559 Jul 03 '20 at 13:43
  • 1
    In this case, do not create fat classes for the game pieces that you probably do not need at all. Instead of `Rook("black", [3,2])`, just use the string `Rb`, or tuple `("Rook", "black")`, and drop the position (do you really need it?) – tobias_k Jul 03 '20 at 13:48
  • @tobias_k sorry, you're going to have to tell me what a fat class is, I've looked it up and can't find it. I think I do need to use the classes as each object has a getMoves() method that relies on its position and colour to search the rest of the board for legal moves – user8430559 Jul 03 '20 at 13:52
  • But it does not have to be a method of the class. You could just have a "static" function that takes a color string, type string, and position (from the grid) and outputs the moves. – tobias_k Jul 03 '20 at 13:55
  • @tobias_k that sounds like a great idea, but it'll require a fairly significant rewrite... If its the only way to speed it up I'll give it a go – user8430559 Jul 03 '20 at 14:02
  • @user8430559 Alternatively, instead of copying the pieces when copying the board, create a _modified_ copy when _moving_ the pieces. This should be a much smaller change, result in much less copying (one copied piece instead of 64 per move), and you can use your simple list-copy-loop. (Sorry for spamming your answer, I'll stop now.) – tobias_k Jul 03 '20 at 14:05
  • @tobias_k no worries about the spamming, it's really useful. I'm not quite sure what you mean though. Could you post an answer with some form of example code to help me get my head around it? that would probably solve my problem, Thanks – user8430559 Jul 03 '20 at 14:10
1

Your copy function actually works fine for copying the structure of the list, although it could be simplified by using a list comprehension: new = [[x for x in row] for row in array]

The problem, and the difference to copy.deepcopy is, though, that it does not copy the actual game pieces like Rook('black', [0,0]). From the various comments, I gather that:

  • the pieces do not implement __eq__, thus == just compares memory addresses and thus your copy is considered "different" from the deepcopy (but probably equal to the original, and, in fact, the deepcopy different from the original)
  • the pieces include their current position, e.g. [0,0], and you update/modify this position when moving a piece
  • thus, when adding the references to the same piece to the copy of the board, your Minimax-algorithm will "move" the pieces in all states, creating lots of invalid game states

Here are a few possible solutions, with increasing complexity of the required refactoring, but also increasing (assumed) benefit:

  • add a copy function and call that method when creating your "manual" copy, e.g. new = [[copy(x) for x in row] for row in array] in my above list comprehension; here, copy is not a method, since there are str and game pieces in those lists, and copy has to work for both (for str, it can just return the original, though)
  • instead of using that copy function each time you copy the state, only create a copy of a game piece just before moving that piece, all the other pieces can remain the same; this way, you only have to create one copy instead of 64 per move (you still have to create a copy of the actual board, though)
  • drop your Rook etc. classes entirely and just use a string representation like "Rb" for "black Rook" or a tuple ("Rook", "black") and do not redundantly include their current position but just take it from the grid itself when calculating possible moves (this would then be a function, not a method of the no-longer-existing class)

The copy function used for the first two approaches could e.g. look as simple as this:

def copy(piece):
    if isinstance(piece, str):
        return piece
    else:
        return type(piece)(piece.color, list(piece.position))

You could also use copy.deepcopy on the individual pieces in your loop / list comprehension, which would probably be a bit faster then using it to copy the entire board, but till much slower than a tailored copy function.

tobias_k
  • 81,265
  • 12
  • 120
  • 179
0

When I tried running your code as-is (using np.ones((8, 8)) as the example input), I got:

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

because the result of new != a is:

[[False False False False False False False False]
 [False False False False False False False False]
 [False False False False False False False False]
 [False False False False False False False False]
 [False False False False False False False False]
 [False False False False False False False False]
 [False False False False False False False False]
 [False False False False False False False False]]

Instead, use:

def makeCopy(array):
    new = [[],[],[],[],[],[],[],[]]
    for x in range(0,8):
        for y in range(0,8):
            new[x].append(array[x][y])
    a = copy.deepcopy(array)
    if (new != a).any():
        print('failed')
    else:
        print('good')
    return new

Here we use array.any() to check if any elements in the list (of lists) do not match.

hemmelig
  • 240
  • 2
  • 8
  • that just gives me ```AttributeError: 'bool' object has no attribute 'any'``` I may not have explained myself properly. I need ```makeCopy(array)``` to return exactly the same value as deepcopy would have, Once it is doing that I'll remove the checks and use the function instead – user8430559 Jul 03 '20 at 13:15
  • Can you provide an example input - I think using a numpy array with shape (8, 8) rather than a list of lists with shape (8, 8) is causing a difference – hemmelig Jul 03 '20 at 13:18
  • Edited the original question – user8430559 Jul 03 '20 at 13:24
0

your code prints out 'good' for me, I tried it with integer and with string lists of a list, and returns the correct initial list you give as an argument to the makeCopy function. Are you sure you're using a list of lists to test your function?

maxwell01
  • 1
  • 1