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

2

95

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?

Irascible answered 6/10, 2013 at 17:40 Comment(1)
That's really not how any() and all() work: "Return True if (any/all) element of the iterable is true. If the iterable is empty, return False."Biosphere
L
177

Generally speaking:

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

  • in the case of all, no values in the iterable are falsy;
  • in the case of any, at least one value is truthy.

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:

  • 0, 0.0, None, [], (), [], set(), and other empty collections are mapped to False
  • all other values are mapped to True.

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


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

Lungki answered 6/10, 2013 at 17:45 Comment(1)
I think instead of any(x == big_foobar for x in foobars) it'd be more idiomatic to write big_foobar in foobars.Overbite
E
0

For the question in the title:

if a list contains one set of values or another

it might be more natural to use set operations. In other words, instead of

if any(x==playerOne for x in board) or any(x==playerTwo for x in board):

# or 
if playerOne in board or playerTwo in board:

use set.issubset (or set.intersection1):

if {playerOne, playerTwo}.issubset(board):

# or
if {playerOne, playerTwo} & set(board):

If playerOne and playerTwo are set/list/tuple of values, then compute their union and test if it's a subset of board:

if {*playerOne,*playerTwo}.issubset(board):

Also if the question is

if every item on the board is either playerOne marker or playerTwo marker

then instead of

if all(x == playerOne or x == playerTwo for x in board):

test set equality:1

if {playerOne, playerTwo} == set(board):

1 You can obviously assign set(board) to some variable beforehand so that you don't have to cast board to a set every time you need to test this condition.

Engler answered 11/2, 2023 at 6:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.