pythonlistsetany

Using any() and all() to check if a list contains one set of values or another


My code is for a Tic Tac Toe game and checking for a draw state but I think this question could be more useful in a general sense.

I have a list that represents the board, it looks like this:

board = [1,2,3,4,5,6,7,8,9]

When a player makes a move, the integer they moved on is replaced with their marker ('x' or 'o'). I already have checks in place to look for a winning state. What I can't do is check for a draw state, where none of the list values are integers but a winning state has not been set.

The code I have so far:

if any(board) != playerOne or any(board) != playerTwo:
    print 'continue'
elif all(board) == playerOne or playerTwo:
    print 'Draw'

The if statement works, the elif does not. I think the problem is my 'or' operator. What I want to check for is: if the every item on the board is either playerOne marker or playerTwo marker. If I were to make the code:

elif all(board) == playerOne or all(board) == playerTwo:

I would be checking to see if every place on the board was playerOne or every place on the board is playerTwo, which it won't be.

So how do I check if the board is taken up by a combination of playerOne markers and playerTwo markers?


Solution

  • Generally speaking:

    all and any are functions that take some iterable and return True, if

    A value x is falsy iff bool(x) == False. A value x is truthy iff bool(x) == True.

    Any non-boolean elements in the iterable are perfectly acceptable — bool(x) maps, or coerces, any x according to these rules:

    The docstring for bool uses the terms 'true'/'false' for 'truthy'/'falsy', and True/False for the concrete boolean values.

    For example:

    if all(x > 0 for x in xs) or any(x > 100 for x in xs):
        # if nothing is zero or something is over a hundred …
    

    In your specific code samples:

    You’ve slightly misunderstood how these functions work. The following does something completely different from what you thought:

    if any(foobars) == big_foobar:
    

    ...because any(foobars) would first be evaluated to either True or False, and then that boolean value would be compared to big_foobar, which generally always gives you False (unless big_foobar coincidentally happened to be the same boolean value).

    Note: the iterable can be a list, but it can also be a generator or a generator expression (≈ lazily evaluated/generated list), or any other iterator.

    What you want instead is:

    if any(x == big_foobar for x in foobars):
    

    which basically first constructs an iterable that yields a sequence of booleans—for each item in foobars, it compares the item to the value held by big_foobar, and (lazily) emits the resulting boolean into the resulting sequence of booleans:

    tmp = (x == big_foobar for x in foobars)
    

    then any walks over all items in tmp and returns True as soon as it finds the first truthy element. It's as if you did the following:

    In [1]: foobars = ['big', 'small', 'medium', 'nice', 'ugly']                                        
    
    In [2]: big_foobar = 'big'                                                                          
    
    In [3]: any(['big' == big_foobar, 'small' == big_foobar, 'medium' == big_foobar, 'nice' == big_foobar, 'ugly' == big_foobar])        
    Out[3]: True
    

    Note: As DSM pointed out, any(x == y for x in xs) is equivalent to y in xs but the latter is more readable, quicker to write and runs faster.

    Some examples:

    In [1]: any(x > 5 for x in range(4))
    Out[1]: False
    
    In [2]: all(isinstance(x, int) for x in range(10))
    Out[2]: True
    
    In [3]: any(x == 'Erik' for x in ['Erik', 'John', 'Jane', 'Jim'])
    Out[3]: True
    
    In [4]: all([True, True, True, False, True])
    Out[4]: False
    

    See also: http://docs.python.org/2/library/functions.html#all