views:

130

answers:

7

I'm looking for a better/more Pythonic solution for the following snippet

count = sum(1 for e in iterable if e)
+3  A: 

Honestly, I can't think of a better way to do it than what you've got.

Well, I guess people could argue about "better," but I think you're unlikely to find anything shorter, simpler, and clearer.

David Zaslavsky
Yes. Thank you.
ars
+8  A: 
len(filter(None, iterable))

Using None as the predicate for filter just says to use the truthiness of the items. (maybe clearer would be len(filter(bool, iterable)))

teepark
+1 This is nice and fast (about 8x faster than the generator expression on my computer). `bool` seems to be just slightly slower than `None`
gnibbler
Uses O(N) extra memory.
John Machin
Fast solution, but the O(N) extra memory is a minus. My list usually has 1000000 elements so using 1MB memory only to count non-zero elements it's not that efficient. Plus I don't think you timed the gc to reclaim the list generated by filter.
Alexandru
A: 

This isn't the fastest, but maybe handy for code-golf

sum(map(bool, iterable))
gnibbler
Uses O(N) extra memory.
John Machin
Only in Python 2.x - in Python 3, map returns a generator, not a list.
Paul McGuire
Also you can use imap from itertools.
Tomasz Wysocki
This won't work if the iterator contains any values that evaluate to false (like an empty string, zero, `False`, an empty list...)
Quartz
@Quartz, well like most answers here, it is equivalent to the snippet provided in the question body, rather than the heading :)
gnibbler
+1  A: 
sum(not not e for e in iterable)
John Machin
+2  A: 

Most Pythonic is to write a small auxiliary function and put it in your trusty "utilities" module (or submodule of appropriate package, when you have enough;-):

import itertools as it

def count(iterable):
  """Return number of items in iterable."""
  return sum(1 for _ in iterable)

def count_conditional(iterable, predicate=None):
  """Return number of items in iterable that satisfy the predicate."""
  return count(it.ifilter(predicate, iterable))

Exactly how you choose to implement these utilities is less important (you could choose at any time to recode some of them in Cython, for example, if some profiling on an application using the utilities shows it's useful): the key thing is having them as your own useful library of utility functions, with names and calling patterns you like, to make your all-important application level code clearer, more readable, and more concise that if you stuffed it full of inlined contortions!-)

Alex Martelli
"calling patterns [that] you like": non-author code readers may be expected to know what (for example) `sum(map(bool, iterable))` does without asking on SO or looking up TFM ... having to find a definition somewhere else breaks the reading flow.
John Machin
A: 

If you're just trying to see whether the iterable is not empty, then this would probably help:

def is_iterable_empty(it):
    try:
        iter(it).next()
    except StopIteration:
        return True
    else:
        return False

The other answers will take O(N) time to complete (and some take O(N) memory; good eye, John!). This function takes O(1) time. If you really need the length, then the other answers will help you more.

Quartz
Ummm well (1) this would be shorter: `is_iterable_empty = lambda it: not any(it)` (2) Doesn't this consume the first element if the iterable is not empty and is an iterator?
John Machin
Actually, your solution with the `any` function won't actually do the same thing in all cases. If the iterator just returns `False` boolean objects (or empty lists, etc.), then your lambda would give a false positive. I'm sure this code could be shortened, but I like giving expanded code for demonstrations.As for consuming the first object, yes, it will, but all of the solutions here will consume at least one element (most consume all). There isn't a good way to get around this, but as long as it's documented, then everything should be okay.
Quartz
A: 

Propably the most Pythonic way is to write code that does not need count function.

Usually fastest is to write the style of functions that you are best with and continue to refine your style.

Write Once Read Often code.

By the way your code does not do what your title says! To count not 0 elements is not simple considering rounding errors in floating numbers, that False is 0..

If you have not floating point values in list, this could do it:

def nonzero(seq):
  return (item for item in seq if item!=0) 

seq = [None,'', 0, 'a', 3,[0], False] 
print seq,'has',len(list(nonzero(seq))),'non-zeroes' 

print 'Filter result',len(filter(None, seq))

"""Output:
[None, '', 0, 'a', 3, [0], False] has 5 non-zeroes
Filter result 3
"""
Tony Veijalainen