2011-03-21 2 views
48

Si je veux le nombre d'éléments dans un itérable sans se soucier des éléments eux-mêmes, quel serait le moyen pythonique pour obtenir cela? En ce moment, je définiraisQuel est le moyen le plus court de compter le nombre d'éléments dans un générateur/itérateur?

def ilen(it): 
    return sum(itertools.imap(lambda _: 1, it)) # or just map in Python 3 

mais je comprends lambda est près d'être considérés comme dangereux et lambda _: 1 est certainement pas assez.

(Le cas d'utilisation de c'est à compter le nombre de lignes dans un fichier texte correspondant à une expression rationnelle, à savoir grep -c.)

+4

S'il vous plaît ne pas utiliser '_' comme nom de variable, parce que (1) il a tendance à confondre les gens, en leur faisant croire que c'est une sorte de syntaxe particulière, (2) entre en collision avec' _' en l'interpréteur interactif et (3) se heurte à l'alias gettext commun. –

+4

@Sven: J'utilise '_' tout le temps pour les variables inutilisées (une habitude de programmation Prolog et Haskell). (1) est une raison pour demander cela en premier lieu. Je n'ai pas considéré (2) et (3), merci de les signaler! –

+2

dupliqué: http://stackoverflow.com/questions/390852/is-there-any-built-in-way-to-get-the-length-of-an-iterable-in-python – tokland

Répondre

92

La manière habituelle est

sum(1 for i in it) 
+1

vous pouvez utiliser 'len (list (it)) '- ou si les éléments sont uniques, alors' len (set (it)) 'pour sauvegarder un caractère. – F1Rumors

+6

@ F1Rumors Utiliser 'len (list (it))' est correct dans la plupart des cas. Cependant, quand vous avez un itérateur paresseux qui donne beaucoup et beaucoup d'éléments, vous ne voulez pas les stocker tous en mémoire en même temps, juste pour les compter, ce qui est évité en utilisant le code dans cette réponse. –

+0

convenu: en réponse, il était basé sur le "plus court code" étant plus important que "plus bas de la mémoire". – F1Rumors

5

Une façon courte est:

def ilen(it): 
    return len(list(it)) 

Notez que si vous générez un beaucoup d'éléments (par exemple, des dizaines de milliers ou plus), puis les mettre dans une liste peut devenir un problème de performance. Cependant, ceci est une expression simple de l'idée où la performance ne va pas avoir d'importance dans la plupart des cas.

+0

J'y avais pensé, mais les performances importent car je traite souvent de gros fichiers texte. –

+6

Tant que vous ne manquerez pas de mémoire, cette solution est en fait plutôt bonne en termes de performances, car elle fera la boucle en code C pur - tous les objets doivent être générés de toute façon. Même pour les gros itérateurs, c'est plus rapide que «sum (1 for i in it)» tant que tout est en mémoire. –

14

méthode qui est significativement plus rapide que sum(1 for i in it) lorsque le itérables peut être long (et non de manière significative plus lente lorsque la itérables est courte), tout en maintenant la mémoire fixe le comportement au-dessus (contrairement len(list(it))) pour éviter raclée de swap et les frais généraux de réaffectation pour les entrées plus importantes:

# On Python 2 only, get zip that lazily generates results instead of returning list 
from future_builtins import zip 

from collections import deque 
from itertools import count 

def ilen(it): 
    # Make a stateful counting iterator 
    cnt = count() 
    # zip it with the input iterator, then drain until input exhausted at C level 
    deque(zip(it, cnt), 0) # cnt must be second zip arg to avoid advancing too far 
    # Since count 0 based, the next value is the count 
    return next(cnt) 

Comme len(list(it)) il réalise la boucle dans le code C sur CPython (deque, count et zip sont tous mis en oeuvre dans C); éviter l'exécution de code octet par boucle est généralement la clé de la performance dans CPython.

Il est étonnamment difficile de trouver des cas de test juste pour comparer les performances (list triche en utilisant __length_hint__ qui n'est pas susceptible d'être disponible pour iterables d'entrée arbitraires, itertools fonctions qui ne fournissent pas __length_hint__ ont souvent des modes de fonctionnement spéciaux qui fonctionnent plus rapide lorsque la valeur retournée sur chaque boucle est libérée avant que la valeur suivante ne soit demandée, ce que fera deque avec maxlen=0). Le cas de test j'était de créer une fonction de générateur qui prendrait une entrée et retourner un générateur de niveau C qui ne disposaient pas des optimisations de conteneurs spéciaux itertools de retour ou __length_hint__, en utilisant Python 3.3 de yield from:

def no_opt_iter(it): 
    yield from it 

Ensuite, en utilisant ipython%timeit magie (en remplaçant différentes constantes pour 100):

>>> %%timeit -r5 fakeinput = (0,) * 100 
... ilen(no_opt_iter(fakeinput)) 

Lorsque l'entrée est pas assez grand que len(list(it)) causerait des problèmes de mémoire, sur une machine Linux x64 Python 3.5, ma solution prend environ 50% plus tha n def ilen(it): return len(list(it)), quelle que soit la longueur d'entrée.

Pour les plus petits des intrants, les coûts d'installation pour appeler deque/zip/count/next signifie qu'il faut infinitésimale plus de cette façon que def ilen(it): sum(1 for x in it) (environ 200 ns plus sur ma machine pour une longueur 0 entrée, qui est de 33% augmentation par rapport à l'approche simple sum), mais pour les entrées plus longues, il est utilisé environ la moitié du temps par élément supplémentaire; pour la longueur 5 entrées, le coût est équivalent, et quelque part dans la gamme de longueur 50-100, le surcoût initial est imperceptible par rapport au travail réel; l'approche sum prend environ deux fois plus de temps. Fondamentalement, si l'utilisation de la mémoire est importante ou si les entrées n'ont pas de taille limitée et que vous vous souciez de la vitesse plus que de la brièveté, utilisez cette solution. Si les entrées sont bornées et petites, len(list(it)) est probablement la meilleure, et si elles sont illimitées, mais que la simplicité/concision compte, vous utiliserez sum(1 for x in it).

1

J'aime le paquet cardinality pour cela, il est très léger et essaie d'utiliser l'implémentation la plus rapide possible en fonction de l'itérable.

Utilisation:

>>> import cardinality 
>>> cardinality.count([1, 2, 3]) 
3 
>>> cardinality.count(i for i in range(500)) 
500 
>>> def gen(): 
...  yield 'hello' 
...  yield 'world' 
>>> cardinality.count(gen()) 
2 
1

more_itertools est une bibliothèque tierce qui implémente un outil ilen. pip install more_itertools

import more_itertools as mit 


mit.ilen(x for x in range(10)) 
# 10 
Questions connexes