Quel est le chemin le plus court pour compter le nombre d'éléments dans un générateur / iterator?

StackOverflow https://stackoverflow.com/questions/5384570

Question

Si je veux que le nombre d'éléments dans un itérables sans se soucier des éléments eux-mêmes, ce qui serait le moyen pythonique pour obtenir cela? En ce moment, je définirais

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

mais je comprends lambda est proche d'être considéré comme nuisible et lambda _: 1 est certainement pas assez.

(Le cas d'utilisation de ce compte est le nombre de lignes dans un fichier de texte correspondant à une expression régulière, c.-à-grep -c.)

Était-ce utile?

La solution

La manière habituelle est

sum(1 for i in it)

Autres conseils

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 d'échange et les frais généraux de réaffectation pour les entrées plus grandes:

# 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 toutes mises en œuvre en C); en évitant l'exécution de code d'octet par boucle est généralement la clé de la performance en CPython.

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

def no_opt_iter(it):
    yield from it

Ensuite, en utilisant la magie de ipython %timeit (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 longtemps que 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 moyens 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, ce qui est une augmentation de 33% sur l'approche simple sum), mais pour les entrées plus, il fonctionne à peu près la moitié du temps par élément supplémentaire; pour une longueur de 5 entrées, le coût est équivalent, et quelque part dans la gamme de longueur 50-100, la surcharge initiale est imperceptibles par rapport au travail réel; l'approche sum prend à peu près deux fois plus longtemps.

En fait, si les questions d'utilisation de la mémoire ou les entrées ne sont pas de taille limitée et la vitesse vous intéresse plus de brièveté, utiliser cette solution. Si les entrées sont bornés et un peu petite, len(list(it)) est probablement le meilleur, et si elles sont sans bornes, mais la simplicité compte / brièveté, vous utiliseriez sum(1 for x in it).

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 simple expression de l'idée où la performance ne va pas à la matière pour la plupart des cas.

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

import more_itertools as mit


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

Je aime le paquet cardinalité pour cela, il est très léger et tente d'utiliser la la plus rapide possible mise en œuvre disponible selon le itérables.

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

Voici mes choix un ou l'autre:

print(len([*gen]))
print(len(list(gen)))
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top