2

I am using a function in a list comprehension and an if function:

new_list = [f(x) for x in old_list if f(x) !=0]

It annoys me that the expression f(x) is computed twice in each loop.

Is there a way to do it in a cleaner way? Something along the lines of storing the value or including the if statement at the beginning of the list comprehension.

luator
  • 4,769
  • 3
  • 30
  • 51
vlemaistre
  • 3,301
  • 13
  • 30
  • 1
    maybe using just `for` loop? – Dawid Żurawski Jul 10 '19 at 09:16
  • 3
    `new_list = [computed for computed in [f(x) for x in old_list] if computed !=0]` - make a list of everything as an inner list, then filter in outer comprehension – h4z3 Jul 10 '19 at 09:18
  • To add to my comment above - put the inner in parentheses to make a generator (lazily evaluate, rather than store the whole inner list). – h4z3 Jul 10 '19 at 09:20
  • 1
    From python 3.8 onwards you could use `walrus` operator. `new_list = [y for x in old_list if (y := f(x))] ` – Abdul Niyas P M Jul 10 '19 at 09:21
  • See [Python list comprehension - want to avoid repeated evaluation](https://stackoverflow.com/q/15812779/5166387) – AAA Jul 10 '19 at 09:21

4 Answers4

4

Compute the results beforehand and iterate over them

new_list = [x for x in map(f, old_list) if x !=0]

Moreover, since map computes the result per element when the element is accessed this is just one loop.

Gaurav Waghmare
  • 394
  • 1
  • 3
  • 11
  • It's not the best idea to mix list comprehension and `map()`. – Olvin Roght Jul 10 '19 at 09:19
  • @OlvinRoght Can you please elaborate? – Gaurav Waghmare Jul 10 '19 at 09:23
  • There's two ways of processing list (*and other iterable data*): 1) list comprehension; 2) `map()`, `filter()` and `reduce()`. **Choose Your Side.** It's a bit confusing when you mix them into one expression. So it should be `new_list = list(filter(lambda x: x != 0, map(f, old_list)))` or `new_list = [x for x in [f(item) for item in old_list] if x != 0]`. – Olvin Roght Jul 10 '19 at 09:38
  • @OlvinRoght why would you need to 'choose a side' on this? these two concepts mix just fine! or do you have a counterexample? or is it a question of style to you (i could almost agree on that...)? – hiro protagonist Jul 10 '19 at 09:50
  • @hiroprotagonist, yes, it's more about style. `map()` is part of functional approach of programming. – Olvin Roght Jul 10 '19 at 09:53
4

you could use a generator expression (in order to avoid creating an unnecessary list) inside your list comprehension:

new_list = [fx for fx in (f(x) for x in old_list) if fx != 0]

starting from python 3.8 you will be able to do this:

new_list = [fx for x in old_list if (fx := f(x)) != 0]
hiro protagonist
  • 44,693
  • 14
  • 86
  • 111
3

in Python 3.8 we'll have the "walrus operator" and be able to do just that!

[y for x in old_list if (y := f(x)) != 0]
Adam.Er8
  • 12,675
  • 3
  • 26
  • 38
1

You could use a filter to remove results:

def f (x):
  return x * 2

old_list = [0, 1, 2, 3]

new_list = filter(lambda x: x != 0, [f(x) for x in old_list])

for x in new_list:
  print(x)

See it working here.

Alternatively you could memoize the function so as to prevent ever having to compute the same result twice:

def f (x):
  return x * 2

def memoize(f):
    memo = {}
    def helper(x):
        if x not in memo:
            print("Computing result for %s" % x)        
            memo[x] = f(x)
        return memo[x]
    return helper

memF = memoize(f)

old_list = [0, 1, 2, 3]

new_list = [memF(x) for x in old_list if memF(x) != 0]

for x in new_list:
  print(x)

Which is available here.

OliverRadini
  • 6,238
  • 1
  • 21
  • 46