Capture method in if statement in list comprehension

I have the following use case:

[(x, f(x)) for x in list_x if f(x) == cond(x)]

      

Above the list I guess is making the call to f (x) twice? How to avoid this and fix the value of f (x) so that f (x) is called only once.

I guess the simple fix is ​​to convert the above list view to a for loop, but curious if this can be done efficiently using a list comprehension.

+3


source to share


3 answers


You can use a nested generator expression to call a function just one time:

[(x, fx) for (x, fx) in ((x, f(x)) for x in list_x) if fx == cond(x)]

      

The generator expression is repeated in lockstep to create (x, fx)

list comprehension tuples .

If you find it easier for the reader, you can first split the generator expression into a separate name:

mapped_x = ((x, f(x)) for x in list_x)
filtered_x = [(x, fx) for (x, fx) in mapped_x if fx == cond(x)]

      

Iterate over this point: the generator expression is executed lazily; the loop for

in the expression advances step by step for each step in the loop for ... in mapped_x

.



Demo:

>>> list_x = range(5)
>>> f = lambda x: print('f({!r})'.format(x)) or (x ** 2 - 1)
>>> cond = lambda x: print('cond({!r})'.format(x)) or x % 2 == 0
>>> mapped_x = ((x, f(x)) for x in list_x)
>>> [(x, fx) for (x, fx) in mapped_x if fx == cond(x)]
f(0)
cond(0)
f(1)
cond(1)
f(2)
cond(2)
f(3)
cond(3)
f(4)
cond(4)
[(1, 0)]

      

Note that it f(x)

is called only once and the condition is checked immediately.

How effective this is depends on how expensive the call is f(x)

; the generator expression is executed as a separate function block and the interpreter will switch between the two blocks (the loop for comprehension of the list is also a block object).

If f(x)

is a Python function, you already won because you now halve the number of function frame objects you f(x)

create (each call creates a frame object too, and creating that data is relatively expensive). For C functions, you should create some trial runs with timeit

module
to see which is faster for your expected list size.

+6


source


If you don't want to call twice f

, you can apply f

to the first list



[a for a in map(lambda x: (x, f(x)), list_x) if a[1] == cond(x)]

+1


source


A filter would be the cleanest way to do this. It will evaluate the passed function and if it returns true it will keep the element, otherwise it won't.

print (list(filter(lambda x: x[1] == cond(x[0]), [(x, f(x)) for x in list_x])))

      

+1


source







All Articles