Python filter / max combo - checking for empty iterator
Asked Answered
F

5

9

(Using Python 3.1)

I know this question has been asked many times for the general question of testing if iterator is empty; obviously, there's no neat solution to that (I guess for a reason - an iterator doesn't really know if it's empty until it's asked to return its next value).

I have a specific example, however, and was hoping I can make clean and Pythonic code out of it:

#lst is an arbitrary iterable
#f must return the smallest non-zero element, or return None if empty
def f(lst):
  flt = filter(lambda x : x is not None and x != 0, lst)
  if # somehow check that flt is empty
    return None
  return min(flt)

Is there any better way to do that?

EDIT: sorry for the stupid notation. The parameter to the function is indeed an arbitrary iterable, rather than a list.

Fredericksburg answered 15/10, 2010 at 6:28 Comment(2)
Yes. ActiveState Python 3.1.2.Fredericksburg
itertools.c_c_c_c_c_COMBO_BREAKER()Macon
O
9
def f(lst):
  flt = filter(lambda x : x is not None and x != 0, lst)
  try:
    return min(flt)
  except ValueError:
    return None

min throws ValueError when the sequence is empty. This follows the common "Easier to Ask for Forgiveness" paradigm.

EDIT: A reduce-based solution without exceptions

from functools import reduce
def f(lst):
  flt = filter(lambda x : x is not None and x != 0, lst)
  m = next(flt, None)
  if m is None:
    return None
  return reduce(min, flt, m)
Orgasm answered 15/10, 2010 at 6:36 Comment(10)
I'm afraid of ValueError being raised by something else than an empty list. Is it known with 100% certainty that min won't raise ValueError in any other circumstance? Also, I may need to replace lst with a list comprehension; and in that case, I am even more afraid of ValueError caused by my own code inside list comprehension.Fredericksburg
@max, I'm not positive, since Python isn't great about documenting that kind of thing. However, flt should always be iterable. And even it it weren't, it looks like min throws TypeError if you pass an argument of the wrong type. I can't think of anything else min itself would throw. Now, currently it looks like lst is a list. Certainly it's a misleading name if not. In that case, you don't have to worry about the list comprehension, because it's completely done before f started. If it's a generator comprehension, that's a little more complicated.Orgasm
Feels like it's a bit dangerous if Python doesn't document this type of thing. I am, after all, writing the software for the life support module on the spaceship bringing human settlers to... ah never mind :) but I do prefer not to rely on a specific exception type from a builtin unless it's heavily documented.Fredericksburg
If I get it to list, might as well check its length explicitly I suppose.Fredericksburg
@max, I've posted a solution that avoids both the list and exceptions.Orgasm
@Fredericksburg - "I'm afraid of ValueError being raised by something else than an empty list." ... does it matter (for higher level purposes) that you make that distinction? I mean, either way, the input was unexpected, right, whether it was [1,2,3, chicken()] or just [].Henchman
@detly, yes, but a ValueError thrown by chicken might be best handled at a higher level (rather than swallowed by f)Orgasm
@Matthew Flaschen - that's fair enough. Always worth asking, though.Henchman
Note: min(it) returns None if it contains/returns None. Thus the non-exception version behaves consistently if your lst happens to contain None. EDIT: but in this code flt never contains None anyway...Waneta
"Is it known with 100% certainty that min won't raise ValueError in any other circumstance?" min could propagate any exception raised by attempting to compare two elements. This would most commonly be TypeError, but a user-defined __lt__ etc. can raise whatever it likes.Carberry
A
10
t = [1,2,3]
if any(filter(lambda x: x == 10, t)):
   print("found 10")
Aeri answered 10/8, 2021 at 19:21 Comment(1)
If you're giving answer other than given answer please explain how its different from others.Floatstone
O
9
def f(lst):
  flt = filter(lambda x : x is not None and x != 0, lst)
  try:
    return min(flt)
  except ValueError:
    return None

min throws ValueError when the sequence is empty. This follows the common "Easier to Ask for Forgiveness" paradigm.

EDIT: A reduce-based solution without exceptions

from functools import reduce
def f(lst):
  flt = filter(lambda x : x is not None and x != 0, lst)
  m = next(flt, None)
  if m is None:
    return None
  return reduce(min, flt, m)
Orgasm answered 15/10, 2010 at 6:36 Comment(10)
I'm afraid of ValueError being raised by something else than an empty list. Is it known with 100% certainty that min won't raise ValueError in any other circumstance? Also, I may need to replace lst with a list comprehension; and in that case, I am even more afraid of ValueError caused by my own code inside list comprehension.Fredericksburg
@max, I'm not positive, since Python isn't great about documenting that kind of thing. However, flt should always be iterable. And even it it weren't, it looks like min throws TypeError if you pass an argument of the wrong type. I can't think of anything else min itself would throw. Now, currently it looks like lst is a list. Certainly it's a misleading name if not. In that case, you don't have to worry about the list comprehension, because it's completely done before f started. If it's a generator comprehension, that's a little more complicated.Orgasm
Feels like it's a bit dangerous if Python doesn't document this type of thing. I am, after all, writing the software for the life support module on the spaceship bringing human settlers to... ah never mind :) but I do prefer not to rely on a specific exception type from a builtin unless it's heavily documented.Fredericksburg
If I get it to list, might as well check its length explicitly I suppose.Fredericksburg
@max, I've posted a solution that avoids both the list and exceptions.Orgasm
@Fredericksburg - "I'm afraid of ValueError being raised by something else than an empty list." ... does it matter (for higher level purposes) that you make that distinction? I mean, either way, the input was unexpected, right, whether it was [1,2,3, chicken()] or just [].Henchman
@detly, yes, but a ValueError thrown by chicken might be best handled at a higher level (rather than swallowed by f)Orgasm
@Matthew Flaschen - that's fair enough. Always worth asking, though.Henchman
Note: min(it) returns None if it contains/returns None. Thus the non-exception version behaves consistently if your lst happens to contain None. EDIT: but in this code flt never contains None anyway...Waneta
"Is it known with 100% certainty that min won't raise ValueError in any other circumstance?" min could propagate any exception raised by attempting to compare two elements. This would most commonly be TypeError, but a user-defined __lt__ etc. can raise whatever it likes.Carberry
A
2
def f(lst):
    # if you want the exact same filtering as the original, you could use
    # lst = [item for item in lst if (item is not None and item != 0)]

    lst = [item for item in lst if item]
    if lst: return min(lst)
    else: return None

the list comprehension only allows items that don't evaluate to boolean false (which filters out 0 and None)

an empty list i.e. [] will evaluate to False, so "if lst:" will only trigger if the list has items

Anticline answered 15/10, 2010 at 6:41 Comment(1)
it's simple enough to replace "if item" with a more strict set if you want, however I don't think "", [], set(), tuple() count as nonzero anyway ;) looks like he's going for NUMBERS with his min() call.Anticline
V
1

If you just want to check if the return of filter is empty, you might do (Python3)

len(list(filter(lambda e : e == 2, [1,2,3])))

But notice, hence filter is a generator if you this test twice, second time, you will receive a diferent result:

len(list(filter(lambda e : e == 2, [1,2,3]))) len(list(filter(lambda e : e == 2, [1,2,3])))

>>> 1

>>> 1

But:

f = filter(lambda e : e == 2, [1,2,3]) len(list(f)) len(list(f))

>>> 1

>>> 0

Versify answered 18/3, 2020 at 15:1 Comment(1)
Converting to a list just to see if the iterator is empty is both inefficient and verbose.Ison
L
0

you can go for reduce expression too return reduce(lambda a,b: a<b and a or b,x) or None

Lello answered 15/10, 2010 at 9:41 Comment(1)
This doesn't answer OP's question, this still has issues with empty iterators or empty lists.Lidia

© 2022 - 2024 — McMap. All rights reserved.