views:

48

answers:

3

(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.

+1  A: 
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)
Matthew Flaschen
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.
max
@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.
Matthew Flaschen
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.
max
If I get it to list, might as well check its length explicitly I suppose.
max
@max, I've posted a solution that avoids both the list and exceptions.
Matthew Flaschen
@max - "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 `[]`.
detly
@detly, yes, but a ValueError thrown by `chicken` might be best handled at a higher level (rather than swallowed by `f`)
Matthew Flaschen
@Matthew Flaschen - that's fair enough. Always worth asking, though.
detly
A: 
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

lunixbochs
@lunix: yes, but it also filters out e.g. `""`, `[]`, `set()`, `tuple()`...
katrielalex
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.
lunixbochs
A: 

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

Tumbleweed