Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Element-wise 'and' for lists in python?

Tags:

python

I have two lists:

X = [True,False]
Y = [True,True]

I am trying to compare X[0] with Y[0] and X[1] with Y[1].

I tried

in [7]: X and Y
Out[7]: [True, True]

but the result I was expecting was [True,False].

What should I be doing?

like image 549
Ginger Avatar asked Sep 16 '13 19:09

Ginger


1 Answers

This is a perfect opportunity to use map, because and can be represented with a built-in function:

import operator
X = [True,False]
Y = [True,True]
map(operator.and_, X,Y)
#=> [True, False]

The reason why you get the behaviour you did is that and performs operations on the operands as if they had bool applied to them. All non-empty lists evaluate to True in a boolean context.

As to the "list comprehension is always better" point: no it's not. The equivalent list comprehension is:

[x and y for x, y in zip(X, Y)]

Which has to build an intermediate object (either list or generator, depending on the python version), and still requires the reader to know what zip does, just as much as map does. It's also likely to be slightly slower (because map + builtin function is fast - it all happens in the C layer, essentially). In fact, timeit shows that izip is faster (see below), but I really think the readability point is more important; you may also see different results if performance really matters.

>>> timeit.timeit('map(operator.and_, X,Y)', 'import operator; import itertools; import random; X = [random.choice([True,False]) for _ in range(1000)]; Y = [random.choice([True,False]) for _ in range(1000)]', number=10000)
1.0160579681396484
>>> timeit.timeit('[x and y for x, y in zip(X, Y)]', 'import operator; import itertools; import random; X = [random.choice([True,False]) for _ in range(1000)]; Y = [random.choice([True,False]) for _ in range(1000)]', number=10000)
1.3570780754089355
>>> timeit.timeit('[x and y for x, y in itertools.izip(X, Y)]', 'import operator; import itertools; import random; X = [random.choice([True,False]) for _ in range(1000)]; Y = [random.choice([True,False]) for _ in range(1000)]', number=10000)
0.965054988861084

That said, if you need arbitrary numbers of lists, you need to use all in a list comprehension (or combined with izip directly); and and_ is technically bitwise and, so be aware that might have funky results if working with numeric types other than bool.

Here is an all version:

import itertools
map(all,itertools.izip(X,Y,Z))
like image 161
Marcin Avatar answered Sep 28 '22 21:09

Marcin