Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

filter map vs list comprehension

Tags:

python

Is filter/map equivalent to list comprehension? Suppose I have the following function

def fib_gen():
    a,b = 0,1
    yield 0
    yield 1
    while True:
        a,b = b,a+b
        yield b

Now I can use list comprehension to list fib numbers:

a = fib_gen()
print [a.next() for i in range(int(sys.argv[1]))]

Suppose I want to list only even fib numbers. I would do the following with filter/map:

a = fib_gen()
print filter(even, map(lambda x: a.next(), range(int(sys.argv[1]))))

How can I get the same result with list comprehension?

like image 399
Oleg Pavliv Avatar asked Jun 19 '11 14:06

Oleg Pavliv


5 Answers

Is filter/map equivalent to list comprehension?

Yes, map(f, L) is equivalent to [f(x) for x in L]. filter(f, L) is equivalent to [x for x in L if f(x)]. But, since list comprehensions with side effects are generally bad (and here you modify the state of the generator), you can use itertools for a bit cleaner solution:

 a = fib_gen()
 a = itertools.islice(a, int(sys.argv[1]))
 a = itertools.ifilter(even, a)
 print list(a)
like image 50
Cat Plus Plus Avatar answered Nov 15 '22 22:11

Cat Plus Plus


filter and map converts quite easily into a list comprehension.

Here's a basic example:

[hex(n) for n in range(0, 100) if n > 20]

This is equivalent to:

list(map(hex, filter(lambda x: x > 20, range(0, 100))))

The comprehension is in my opinion more readable. However if the conditions become very advanced, I prefer filter.

So in your case:

[n for n in itertools.islice(fib_gen(), 100) if even(n)]

I have used islice here because the sequence is infinite. But if you use a generator expression it becomes an infinite stream as well:

gen = (n for n in fib_gen() if even(n))

Now you can slice the sequence as well with islice:

print itertools.islice(gen, int(sys.argv[1]))

This avoids the need to use next in the comprehensions themselves. As long as you don't try to evaluate the infinite sequence (as we would if we omitted islice in the list comprehension), we can work with your sequence.

like image 37
Skurmedel Avatar answered Nov 15 '22 23:11

Skurmedel


You could use a generator to store the intermediate result, and "filter" on it.

fibs = (a.next() for i in whatever)
even_fibs = [num for num in fibs if num % 2 == 0]

or in one line:

even_fibs = [num for num in (a.next() for i in whatever) if num % 2 == 0]

Note that, if you want to take a definite number of elements from an iterator, you could use itertools.islice instead:

from itertools import islice
fibs_max_count = int(sys.argv[1])
even_fibs = [num for num in islice(fib_gen(), fibs_max_count) if num%2 == 0]
like image 42
kennytm Avatar answered Nov 15 '22 23:11

kennytm


I don't thing this is going to work with a generator. In order to have this work with list comprehension, you would need to have:

print [a.next() for i in range(int(sys.argv[1])) if even(a.next())]

This returns:

[1, 3, 13, 55, 233]

The problem is you need to access twice the next number in the list but the second a.next() call makes what it supposed to do, ie get the next number. To my knowledge, it is not possible to store the value of a.next() with list comprehension to use twice.

like image 45
badzil Avatar answered Nov 15 '22 23:11

badzil


You could use the following code:

a = fib_gen()
print [a.next() for i in range(int(sys.argv[1])) if i%3==0]

This is a particular case that works because every third fibbonacci number is even.

like image 1
murgatroid99 Avatar answered Nov 16 '22 00:11

murgatroid99