6

What's the most idiomatic way of writing a filter with a negation?

Example:

is_even= lambda x : x % 2 == 0
odd_numbers= filter( lambda x: not is_even(x), range(10) )

Of course, you can just use list comprehensions - but then you needn't use filter anyway

In case anyone wonders, I've stumbled upon this while trying to split a list based on a condition

loopbackbee
  • 21,962
  • 10
  • 62
  • 97
  • In case anyone wonders, I'm trying to [split a list based on a condition](https://stackoverflow.com/questions/949098/python-split-a-list-based-on-a-condition) – loopbackbee Jan 09 '15 at 18:35
  • Is that missin `)` a typo? – Bhargav Rao Jan 09 '15 at 18:35
  • Damn, you edited it 1s after I posted. That's too fast :P .. – Bhargav Rao Jan 09 '15 at 18:36
  • Can you tell us what you're actually trying to do? A broader description please. What's wrong with what you posted? – Reut Sharabani Jan 09 '15 at 18:37
  • @ReutSharabani I mentioned that in the comments, but I'll move it to the question itself. There's nothing *wrong* with what I wrote, I was just wondering if there's a more idiomatic or less verbose way of doing it – loopbackbee Jan 09 '15 at 18:45
  • There is no need to write two lambda functions. How about `odd = filter(lambda x: x % 2, range(10))`? – Hans Then Jan 09 '15 at 18:52
  • The `itertools` module has [ifilterfalse()](https://docs.python.org/2/library/itertools.html#itertools.ifilterfalse), which filters for elements where the function returns False. ([itertools.filterfalse()](https://docs.python.org/3.1/library/itertools.html#itertools.filterfalse) in Python 3). No built-in equivalent, but `not` seems simple enough. – Lack Jan 09 '15 at 18:58
  • @HansThen For the sake of the example, assume `is_even` is already defined – loopbackbee Jan 09 '15 at 19:03
  • @Lack `ifilterfalse` seems exactly what I was looking for, and exists for python 2. If you care to submit an answer, I'll accept it – loopbackbee Jan 09 '15 at 19:05
  • Great. I tried to make the difference between Python 2 and 3 clear in the answer. Your example (in the question) actually gives different results in 2 and 3. – Lack Jan 09 '15 at 19:36

2 Answers2

10

The itertools module includes both ifilter() and ifilterfalse(), which filter elements where the function returns True and False respectively.

odd_numbers = ifilterfalse(is_even, range(10))

Note that, in Python 2, there is a difference between filter and ifilter: odd_numbers here would be an iterator, while filter() would give a list (see itertools.ifilter Vs. filter Vs. list comprehensions). If you actually want to build a list, your example with not seems fine, assuming you are set on using filter - list comprehensions may be more 'idiomatic' (List filtering: list comprehension vs. lambda + filter).

In Python 3, filter() constructs an iterator, not a list, and itertools.filterfalse() is the complement.

Community
  • 1
  • 1
Lack
  • 1,625
  • 1
  • 17
  • 29
3

Splitting based on a predicate is called a partition. I would find it more idiomatic to implement partition as a separate function rather than repeat its internals specifically for odd and even numbers. Python 3's Itertools Recipes has the following implementation:

def partition(pred, iterable):
    'Use a predicate to partition entries into false entries and true entries'
    # partition(is_odd, range(10)) --> 0 2 4 6 8   and  1 3 5 7 9
    t1, t2 = tee(iterable)
    return filterfalse(pred, t1), filter(pred, t2)

It uses filterfalse (as described by @Lack) and tee defined in that module. So your highest-level code would look like:

odds, evens = partition(is_even, range(10))
Karl Bielefeldt
  • 47,314
  • 10
  • 60
  • 94
  • Yes, this is a better solution to the actual problem if you are using both parts of the list. – Lack Jan 09 '15 at 19:43
  • This is probably worth posting in the question I linked to, as nobody mentioned it yet. My question in particular was more motivated by curiosity than the practical need to solve the actual partitioning (for which there's a lot of answers already) – loopbackbee Jan 09 '15 at 19:49