2010-08-03 5 views
12

Je cherche une meilleure/solution plus Pythonic pour l'extrait suivantComment compter les éléments non-null dans un itérable?

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

Qu'est-ce qui ne va pas ou qui n'est pas «pythononique»? –

+1

Les expressions de générateur sont Pythonic. Voulez-vous simplement dire plus court? –

+0

OP, sachez exactement ce qui est et n'est pas non nul, en Python. Par exemple 'sum (1 pour e dans [Faux, 0, '', [],(), [''], (''), [Faux], (Faux,), Aucun] si e)' évalue réellement à 3 au lieu de 0, car bizarrement les séquences imbriquées '[['']]', '[[False]]', '[(False,)]' comptent comme non nul, et pourtant '[('')] 'ne le fait pas, [! Quoi qu'il en soit, en général, le code que vous donnez est correct et adapté à la tâche, si nous avons la garantie que la liste est plate. – smci

Répondre

18
len(filter(None, iterable)) 

En utilisant None comme le prédicat pour filter dit simplement d'utiliser le truthiness des articles. (Peut-être plus clair serait len(filter(bool, iterable)))

+0

+1 C'est agréable et rapide (environ 8 fois plus rapide que l'expression du générateur sur mon ordinateur). 'bool' semble être légèrement plus lent que' None' –

+10

Utilise O (N) de mémoire supplémentaire. –

+1

Solution rapide, mais la mémoire supplémentaire O (N) est un moins. Ma liste contient généralement des éléments 1000000, donc l'utilisation de 1 Mo de mémoire uniquement pour compter les éléments non-zéro n'est pas très efficace. De plus, je ne pense pas que vous avez chronométré le gc pour récupérer la liste générée par le filtre. – Alexandru

3

Honnêtement, je ne peux pas penser à une meilleure façon de le faire que ce que vous avez. Eh bien, je pense que les gens pourraient discuter de «mieux», mais je pense que vous ne trouverez probablement rien de plus court, de plus simple et de plus clair.

+0

Oui. Je vous remercie. – ars

0

Ce n'est pas le plus rapide, mais peut-être à portée de main pour le code-golf

sum(map(bool, iterable)) 
+1

Utilise O (N) de mémoire supplémentaire. –

+0

Seulement en Python 2.x - en Python 3, la carte retourne un générateur, pas une liste. – PaulMcG

+0

Vous pouvez également utiliser imap de itertools. –

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

Wow. Cette réponse est assez ancienne pour que 'bool (e)' n'existait pas. –

3

La plupart Pythonic est d'écrire une petite fonction auxiliaire et le mettre dans votre fidèle module « utilitaires » (ou sous-module de package approprié, quand vous avez assez ;-):

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

Exactement comment vous choisissez de mettre en œuvre e Ces utilitaires sont moins importants (vous pouvez choisir à tout moment de recoder certains d'entre eux dans Cython, par exemple, si un profilage sur une application utilisant les utilitaires montre que c'est utile): la clé est de les avoir comme bibliothèque utile fonctions, avec les noms et les habitudes d'appel vous aimez, pour rendre votre tout important de niveau d'application code plus clair, plus lisible et plus concis que si vous fourra pleine de contorsions inlined -)

+0

"modèles d'appel que vous aimez": les lecteurs de code non-auteur peuvent savoir ce que (sum (map (bool, iterable)) 'fait sans demander sur SO ou rechercher TFM ... ayant trouver une définition ailleurs brise le flux de lecture. –

0

Si vous Si vous essayez juste de voir si l'itérable n'est pas vide, alors cela vous aidera probablement:

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

les autres réponses prendront O (N) pour se terminer (et certaines prendront la mémoire O (N); bon oeil, John!). Cette fonction prend O (1) fois. Si vous avez vraiment besoin de la longueur, alors les autres réponses vous aideront plus.

+0

Ummm bien (1) ce serait plus court: 'is_iterable_empty = lambda it: not any (it)' (2) Ne consomme-t-il pas le premier élément si l'itérable n'est pas vide et est un itérateur? –

+0

En fait, votre solution avec la fonction 'any' ne fera pas réellement la même chose dans tous les cas. Si l'itérateur renvoie juste des objets booléens 'False' (ou des listes vides, etc.), alors votre lambda donnerait un faux positif. Je suis sûr que ce code pourrait être raccourci, mais j'aime donner du code élargi pour les démonstrations. En ce qui concerne la consommation du premier objet, oui, mais toutes les solutions consommeront au moins un élément (la plupart consomment tout). Il n'y a pas de bonne façon de contourner cela, mais tant que cela est documenté, tout devrait bien se passer. –

0

Probablement le moyen le plus Python est d'écrire du code qui n'a pas besoin de la fonction de comptage.

Habituellement, le plus rapide est d'écrire le style de fonctions que vous êtes le meilleur et continuer d'affiner votre style.

Écrire une fois Lire souvent code. D'ailleurs, votre code ne fait pas ce que dit votre titre! Compter pas 0 éléments n'est pas simple compte tenu des erreurs d'arrondi en nombre flottant, ce faux est 0 ..

Si vous ne l'avez pas des valeurs à virgule flottante dans la liste, cela pourrait le faire:

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

Non, ce n'est pas une façon alambiquée de dire 'len (list (item pour item dans seq if item! = 0))'. C'est en fait ** moins Pythonic **. Il n'y a pas besoin de 'nonzero (seq)' pour construire la liste intermédiaire throwaway quand tout ce que nous voulons faire est de compter 1 pour savoir si un élément est différent de zéro ou non. Faire cela à l'intérieur d'une expression de générateur signifie que nous n'avons pas besoin de construire une liste intermédiaire, AFAIK. – smci

0

Voici une solution avec O (n) runtime et O (1) mémoire supplémentaire:

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

Hope that helps!

Questions connexes