2010-08-03 15 views
12

Busco una solución mejor/más Pythonic para el siguiente fragmento de¿Cómo contar elementos no nulos en un iterable?

count = sum(1 for e in iterable if e) 
+7

¿Qué hay de malo o 'antiponético' en su camino? –

+1

Las expresiones de generador son Pythonic. ¿Quieres decir más corto? –

+0

OP, tenga en cuenta exactamente lo que es y no es nulo, en Python. Por ejemplo 'sum (1 para e en [False, 0, '', [],(), [''], (''), [False], (False,), None] si e)' en realidad evalúa a 3 en lugar de 0, porque extrañamente las secuencias anidadas '[['']]', '[[False]]', '[(False,)]' cuentan como no nulas, pero '[('')] 'no, [! De todos modos, en general, el código que das está bien y es adecuado para la tarea, si tenemos la garantía de que la lista es plana. – smci

Respuesta

18
len(filter(None, iterable)) 

Usando None como el predicado de filter sólo dice utilizar el truthiness de los artículos. (Tal vez más claro sería len(filter(bool, iterable)))

+0

+1 Esto es agradable y rápido (aproximadamente 8 veces más rápido que la expresión del generador en mi computadora). 'bool' parece ser un poco más lento que' None' –

+10

Usa O (N) memoria extra. –

+1

Solución rápida, pero la memoria adicional O (N) es un valor negativo. Mi lista generalmente tiene 1000000 elementos, así que usar memoria de 1MB solo para contar elementos distintos de cero no es tan eficiente. Además, no creo que hayas cronometrado el GC para reclamar la lista generada por el filtro. – Alexandru

3

Honestamente, no puedo pensar en una mejor manera de hacerlo que lo que tienes.

Bueno, supongo que la gente podría discutir sobre "mejor", pero creo que es poco probable que encuentres algo más corto, más simple y más claro.

+0

Sí. Gracias. – ars

0

Este no es el más rápido, pero tal vez útil para el código de golf

sum(map(bool, iterable)) 
+1

Utiliza O (N) memoria extra. –

+0

Solo en Python 2.x - en Python 3, el mapa devuelve un generador, no una lista. – PaulMcG

+0

También puede usar imap desde itertools. –

2
sum(not not e for e in iterable) 
+1

Wow. Esta respuesta es lo suficientemente antigua como para que 'bool (e)' no existiera. –

3

más Pythonic es escribir una pequeña función auxiliar y ponerlo en su fiel "utilidades" módulo (o submódulo del paquete correspondiente, cuando tienen suficiente ;-):

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)) 

Exactamente cómo decide implementar º Estas utilidades son menos importantes (puede elegir en cualquier momento recodificar algunas en Cython, por ejemplo, si algún perfil en una aplicación que utiliza las utilidades muestra que es útil): la clave es tenerlas como su propia biblioteca útil de utilidad funciones, con nombres y patrones de llamadas que gusto, para que su suma importancia-aplicación de código de nivel más claro, más fácil de leer, y más concisa que si se rellena por completo de la contorsiones inline -)

+0

"patrones de llamadas [que] te gustan": se espera que los lectores de código no-autor sepan qué (por ejemplo) 'suma (mapa (bool, iterable))' sin preguntar en SO o buscar en TFM ... tener encontrar una definición en otro lugar rompe el flujo de lectura. –

0

Si' simplemente tratando de ver si el iterable no está vacío, entonces esto probablemente ayudaría:

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

el otras respuestas tomarán O (N) tiempo para completarse (y algunas tomarán O (N) memoria; buen ojo, John!). Esta función toma O (1) vez. Si realmente necesita la longitud, las otras respuestas le ayudarán más.

+0

Ummm bien (1) esto sería más corto: 'is_iterable_empty = lambda it: not any (it)' (2) ¿Esto no consume el primer elemento si el iterable no está vacío y es un iterador? –

+0

En realidad, su solución con la función 'any' no hará lo mismo en todos los casos. Si el iterador solo devuelve objetos booleanos 'False' (o listas vacías, etc.), entonces su lambda daría un falso positivo. Estoy seguro de que este código podría acortarse, pero me gusta dar código ampliado para demostraciones. En cuanto al consumo del primer objeto, sí, lo hará, pero todas las soluciones aquí consumirán al menos un elemento (la mayoría consume todo). No hay una buena forma de evitar esto, pero mientras esté documentado, todo debería estar bien. –

0

Propablemente, la forma más pitonica es escribir código que no necesita la función de conteo.

Por lo general, lo más rápido es escribir el estilo de las funciones con las que es mejor y seguir refinando su estilo.

Escribir una vez Leer A menudo código.

¡Por cierto, su código no hace lo que dice su título! Para contar no 0 elementos no es sencilla teniendo en cuenta los errores de redondeo en los números flotantes, que Falso es 0 ..

Si no has valores de punto flotante en la lista, esto podría hacerlo:

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 
""" 
+0

No, no es así, es solo una forma complicada de decir 'len (list (item para item in seq if item! = 0))'. Eso es en realidad ** menos Ptónico **. No es necesario 'no cero (seq)' para construir la lista intermedia desechable cuando todo lo que queremos hacer es contar 1 para determinar si un elemento es distinto de cero o no. Hacer eso dentro de una expresión de generador significa que no necesitamos construir una lista intermedia, AFAIK. – smci

0

Aquí es una solución con O (n) tiempo de ejecución y O (1) memoria adicional:

count = reduce(lambda x,y:x+y, imap(lambda v: v>0, iterable)) 

Espero que ayude!

Cuestiones relacionadas