method - Obtener el número de elementos en un iterador en Python
python itertools (15)
Aunque no es posible en general hacer lo que se ha pedido, a menudo es útil tener un recuento de la cantidad de elementos iterados después de haber iterado sobre ellos. Para eso, puedes usar jaraco.itertools.Counter o similar. Aquí hay un ejemplo que usa Python 3 y rwt para cargar el paquete.
$ 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
¿Hay una manera eficiente de saber cuántos elementos hay en un iterador en Python, en general, sin iterar a través de cada uno y contar?
Con respecto a su pregunta original, la respuesta sigue siendo que no hay forma en general de conocer la longitud de un iterador en Python.
Dado que tu pregunta está motivada por una aplicación de la biblioteca pysam, puedo darte una respuesta más específica: soy colaborador de PySAM y la respuesta definitiva es que los archivos SAM / BAM no proporcionan un conteo exacto de las lecturas alineadas. Tampoco esta información está disponible fácilmente desde un archivo de índice BAM. Lo mejor que se puede hacer es estimar el número aproximado de alineaciones utilizando la ubicación del puntero del archivo después de leer una serie de alineaciones y extrapolar en función del tamaño total del archivo. Esto es suficiente para implementar una barra de progreso, pero no un método para contar alineaciones en tiempo constante.
Entonces, para aquellos a quienes les gustaría saber el resumen de esa discusión. Los mejores puntajes finales para contar una expresión de generador de 50 millones de longitud utilizando:
-
len(list(gen))
, -
len([_ for _ in gen])
, -
sum(1 for _ in gen),
-
ilen(gen)
(de more_itertool ), -
reduce(lambda c, i: c + 1, gen, 0)
,
ordenados por el rendimiento de ejecución (incluido el consumo de memoria), le sorprenderá:
`` `
1: test_list.py:8: 0.492 KiB
gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))
(''list, sec'', 1.9684218849870376)
2: test_list_compr.py:8: 0.867 KiB
gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen])
(''list_compr, sec'', 2.5885991149989422)
3: test_sum.py:8: 0.859 KiB
gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic()
(''suma, sec'', 3.441088170016883)
4: more_itertools / more.py: 413: 1.266 KiB
d = deque(enumerate(iterable, 1), maxlen=1)
test_ilen.py:10: 0.875 KiB
gen = (i for i in data*1000); t0 = monotonic(); ilen(gen)
(''ilen, sec'', 9.812256851990242)
5: test_reduce.py:8: 0.859 KiB
gen = (i for i in data*1000); t0 = monotonic(); reduce(lambda counter, i: counter + 1, gen, 0)
(''reducir, seg'', 13.436614598002052) `` `
Entonces, len(list(gen))
es el consumible de memoria más frecuente y menos
Es una práctica común colocar este tipo de información en el encabezado del archivo y que pysam le dé acceso a esto. No sé el formato, pero ¿has revisado la API?
Como han dicho otros, no se puede conocer la longitud del iterador.
Este código debería funcionar:
>>> iter = (i for i in range(50))
>>> sum(1 for _ in iter)
50
Aunque itera a través de cada elemento y los cuenta, es la forma más rápida de hacerlo.
También funciona cuando el iterador no tiene ningún elemento:
>>> sum(1 for _ in range(0))
0
Esto va en contra de la definición misma de un iterador, que es un puntero a un objeto, más información sobre cómo llegar al siguiente objeto.
Un iterador no sabe cuántas veces más podrá iterar hasta que termine. Esto podría ser infinito, así que el infinito podría ser tu respuesta.
Hay dos formas de obtener la longitud de "algo" en una computadora.
La primera forma es almacenar un recuento: esto requiere que todo lo que toque el archivo / datos lo modifique (o una clase que solo exhiba interfaces), pero se reduce a lo mismo.
La otra forma es iterar sobre ella y contar qué tan grande es.
Me gusta el paquete de cardinality para esto, es muy liviano y trata de usar la implementación más rápida posible según el iterable.
Uso:
>>> 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
La implementación actual de count()
es la siguiente:
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
No se puede (excepto que el tipo de un iterador particular implementa algunos métodos específicos que lo hacen posible).
En general, puede contar elementos de iterador solo consumiendo el iterador. Una de las formas más eficientes:
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)
(Para Python 3.x reemplace itertools.izip
con zip
).
No, cualquier método requerirá que resuelva cada resultado. Tu puedes hacer
iter_length = len(list(iterable))
pero ejecutar eso en un iterador infinito, por supuesto, nunca volverá. También consumirá el iterador y deberá reiniciarse si desea usar los contenidos.
Decirnos qué problema real está tratando de resolver podría ayudarnos a encontrar una mejor manera de lograr su objetivo real.
Editar: Usar list()
leerá todo el iterable en la memoria a la vez, lo que puede ser indeseable. Otra forma es hacer
sum(1 for _ in iterable)
como otra persona publicó. Eso evitará mantenerlo en la memoria.
No, no es posible.
Ejemplo:
import random
def gen(n):
for i in xrange(n):
if random.randint(0, 1) == 0:
yield i
iterator = gen(10)
La longitud del iterator
es desconocida hasta que itere a través de él.
Un iterador es simplemente un objeto que tiene un puntero al siguiente objeto para ser leído por algún tipo de búfer o secuencia, es como una Lista Enlazada donde no sabe cuántas cosas tiene hasta que itera a través de ellas. Los iteradores deben ser eficientes porque lo único que hacen es decirte lo que sigue por referencias en lugar de utilizar la indexación (pero como viste, pierdes la capacidad de ver cuántas entradas hay a continuación).
Un poco Podrías comprobar el método __length_hint__
, pero ten en cuenta que (al menos hasta Python 3.4, como señala amablemente gsnedders) es un detalle de implementación no documentado ( siguiendo el mensaje en el hilo ), que bien podría desaparecer o convocar demonios nasales en su lugar.
De otra manera no. Los iteradores son solo un objeto que solo expone el método next()
. Puede llamarlo tantas veces como sea necesario y eventualmente pueden generar StopIteration
. Afortunadamente, este comportamiento es la mayor parte del tiempo transparente para el codificador. :)
Un punto de referencia rápido:
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(iter):
return sum(1 for _ in iter)
iter = (x for x in xrange(100))
%timeit count_iter_items(iter)
%timeit count_lencheck(iter)
%timeit sum(iter)
Los resultados:
1000000 loops, best of 3: 553 ns per loop
1000000 loops, best of 3: 730 ns per loop
1000000 loops, best of 3: 246 ns per loop
Es decir, el simple count_sum es el camino a seguir.
def count_iter(iter):
sum = 0
for _ in iter: sum += 1
return sum