Existe-t-il un moyen efficace de savoir le nombre d’éléments contenus dans un itérateur dans Python, en général, sans parcourir chacun d’entre eux sans compter?
Non, ce n'est pas possible.
Exemple:
import random
def gen(n):
for i in xrange(n):
if random.randint(0, 1) == 0:
yield i
iterator = gen(10)
La longueur de iterator
est inconnue jusqu'à ce que vous la parcouriez.
Ce code devrait fonctionner:
>>> iter = (i for i in range(50))
>>> sum(1 for _ in iter)
50
Bien qu’il parcoure chaque élément et les compte, c’est le moyen le plus rapide de le faire.
Cela fonctionne aussi pour quand l'itérateur n'a pas d'objet:
>>> sum(1 for _ in range(0))
0
Non, toute méthode nécessitera la résolution de tous les résultats. Tu peux faire
iter_length = len(list(iterable))
mais exécuter cela sur un itérateur infini ne reviendra bien sûr jamais. Cela consommera également l'itérateur et il devra être réinitialisé si vous souhaitez utiliser le contenu.
Nous dire quel problème réel que vous essayez de résoudre peut nous aider à vous trouver un meilleur moyen d'atteindre votre objectif actuel.
Edit: Utiliser list()
lira l’ensemble de la mémoire en une fois, ce qui peut être indésirable. Une autre façon est de faire
sum(1 for _ in iterable)
comme une autre personne a posté. Cela évitera de le garder en mémoire.
Vous ne pouvez pas (sauf que le type d'un itérateur particulier implémente des méthodes spécifiques qui le rendent possible).
En règle générale, vous pouvez compter les éléments d'itérateur uniquement en utilisant l'itérateur. L'un des moyens les plus efficaces:
import itertools
from collections import deque
def count_iter_items(iterable):
"""
Consume an iterable not reading it into memory; return the number of items.
"""
counter = itertools.count()
deque(itertools.izip(iterable, counter), maxlen=0) # (consume at C speed)
return next(counter)
(Pour Python 3.x, remplacez itertools.izip
par Zip
).
Kinda. Vous pourriez vérifier la méthode __length_hint__
, mais sachez que (du moins jusqu’à Python 3.4, comme l’a signalé utilement le navigateur web), c’est un détail de l’application non documentée ( message suivant dans le fil ), pourrait très bien disparaître ou invoquer des démons nasaux à la place.
Sinon, non. Les itérateurs ne sont qu'un objet qui expose uniquement la méthode next()
. Vous pouvez l'appeler autant de fois que nécessaire et ils peuvent éventuellement ou non élever StopIteration
. Heureusement, ce comportement est la plupart du temps transparent pour le codeur. :)
J'aime le paquetage cardinality pour cela, il est très léger et essaie d'utiliser l'implémentation la plus rapide possible disponible en fonction de l'itérable.
Usage:
>>> 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
L'implémentation réelle de count()
est la suivante:
def count(iterable):
if hasattr(iterable, '__len__'):
return len(iterable)
d = collections.deque(enumerate(iterable, 1), maxlen=1)
return d[0][0] if d else 0
Un itérateur est simplement un objet qui a un pointeur sur le prochain objet à lire par une sorte de tampon ou de flux. C'est comme un LinkedList où vous ne savez pas combien de choses vous avez jusqu'à ce que vous les parcouriez. Les itérateurs sont censés être efficaces, car ils ne font que vous indiquer la suite des références au lieu d'utiliser l'indexation (mais vous perdez la possibilité de voir le nombre d'entrées suivantes).
En ce qui concerne votre question initiale, la réponse est toujours qu’il n’ya aucun moyen en général de connaître la longueur d’un itérateur en Python.
Étant donné que votre question est motivée par une application de la bibliothèque pysam, je peux vous donner une réponse plus précise: je contribue à PySAM et la réponse définitive est que les fichiers SAM/BAM ne fournissent pas un nombre exact de lectures alignées. Cette information n’est pas non plus facilement disponible à partir d’un fichier d’index BAM. La meilleure solution consiste à estimer le nombre approximatif d'alignements en utilisant l'emplacement du pointeur de fichier après avoir lu un certain nombre d'alignements et en extrapolant en fonction de la taille totale du fichier. Cela suffit pour implémenter une barre de progression, mais pas une méthode de comptage des alignements en temps constant.
Il y a deux façons d'obtenir la longueur de "quelque chose" sur un ordinateur.
La première méthode consiste à stocker un nombre - cela nécessite que tout ce qui touche le fichier/les données soit modifié (ou une classe qui n'expose que les interfaces - mais cela revient au même).
L’autre méthode consiste à parcourir le site et à compter son importance.
Un repère rapide:
import collections
import itertools
def count_iter_items(iterable):
counter = itertools.count()
collections.deque(itertools.izip(iterable, counter), maxlen=0)
return next(counter)
def count_lencheck(iterable):
if hasattr(iterable, '__len__'):
return len(iterable)
d = collections.deque(enumerate(iterable, 1), maxlen=1)
return d[0][0] if d else 0
def count_sum(iterable):
return sum(1 for _ in iterable)
iter = lambda y: (x for x in xrange(y))
%timeit count_iter_items(iter(1000))
%timeit count_lencheck(iter(1000))
%timeit count_sum(iter(1000))
Les resultats:
10000 loops, best of 3: 35.4 µs per loop
10000 loops, best of 3: 40.2 µs per loop
10000 loops, best of 3: 50.7 µs per loop
C'est à dire. le simple count_iter_items est la voie à suivre.
Il est courant de mettre ce type d’informations dans l’en-tête du fichier et pysam vous en donne l’accès. Je ne connais pas le format, mais avez-vous vérifié l'API?
Comme d'autres l'ont dit, vous ne pouvez pas connaître la longueur de l'itérateur.
def count_iter(iter):
sum = 0
for _ in iter: sum += 1
return sum
Bien qu’il soit en général impossible de faire ce qui a été demandé, il est toujours utile de pouvoir compter combien d’items ont été itérés sur après après les avoir itérés. Pour cela, vous pouvez utiliser jaraco.itertools.Counter ou similaire. Voici un exemple utilisant Python 3 et rwt pour charger le paquet.
$ rwt -q jaraco.itertools -- -q
>>> import jaraco.itertools
>>> items = jaraco.itertools.Counter(range(100))
>>> _ = list(counted)
>>> items.count
100
>>> import random
>>> def gen(n):
... for i in range(n):
... if random.randint(0, 1) == 0:
... yield i
...
>>> items = jaraco.itertools.Counter(gen(100))
>>> _ = list(counted)
>>> items.count
48
Cela va à l'encontre de la définition même d'un itérateur, qui est un pointeur sur un objet, ainsi que des informations sur la façon d'atteindre l'objet suivant.
Un itérateur ne sait pas combien de fois il pourra itérer jusqu'à ce qu'il se termine. Cela pourrait être infini, donc l'infini pourrait être votre réponse.