Esiste un modo efficace per sapere quanti elementi ci sono in un iteratore in Python, in generale, senza iterare in ciascuno di essi e contare?
Esiste un modo efficace per sapere quanti elementi ci sono in un iteratore in Python, in generale, senza iterare in ciascuno di essi e contare?
Risposte:
No. Non è possibile.
Esempio:
import random
def gen(n):
for i in xrange(n):
if random.randint(0, 1) == 0:
yield i
iterator = gen(10)
La lunghezza di iterator
è sconosciuta fino a quando non viene ripetuta.
def gen(): yield random.randint(0, 1)
è infinito, quindi non sarai mai in grado di trovare una lunghezza iterando attraverso di essa.
numIters = 0 ; while iterator: numIters +=1
?
Questo codice dovrebbe funzionare:
>>> iter = (i for i in range(50))
>>> sum(1 for _ in iter)
50
Anche se scorre ogni elemento e li conta, è il modo più veloce per farlo.
Funziona anche quando l'iteratore non ha elementi:
>>> sum(1 for _ in range(0))
0
Ovviamente, funziona all'infinito per un input infinito, quindi ricorda che gli iteratori possono essere infiniti:
>>> sum(1 for _ in itertools.count())
[nothing happens, forever]
Inoltre, tieni presente che l'iteratore sarà esaurito in questo modo e ulteriori tentativi di utilizzarlo non vedranno alcun elemento . Questa è una conseguenza inevitabile del design dell'iteratore Python. Se vuoi conservare gli elementi, dovrai memorizzarli in un elenco o qualcosa del genere.
_
riferimento a Perl $_
? :)
_
per una variabile fittizia di cui non ti interessa il valore.
No, qualsiasi metodo richiederà di risolvere ogni risultato. Tu puoi fare
iter_length = len(list(iterable))
ma eseguirlo su un iteratore infinito ovviamente non tornerà mai più. Consumerà anche l'iteratore e dovrà essere ripristinato se si desidera utilizzare i contenuti.
Raccontaci quale problema reale stai cercando di risolvere potrebbe aiutarci a trovare un modo migliore per raggiungere il tuo obiettivo reale.
Modifica: l'utilizzo list()
leggerà l'intero iterabile in memoria in una sola volta, il che potrebbe essere indesiderabile. Un altro modo è fare
sum(1 for _ in iterable)
come postato da un'altra persona. Ciò eviterà di tenerlo in memoria.
len(list(iterable))
caricherà tutti i dati in memoria. È possibile utilizzare: reduce(lambda x, _: x+1, iterable, 0)
. Modifica: anche il codice Zonda333 con la somma è buono.
functools.reduce
Non puoi (tranne il tipo di un particolare iteratore implementa alcuni metodi specifici che lo rendono possibile).
In genere, puoi contare gli elementi dell'iteratore solo consumando l'iteratore. Uno dei modi probabilmente più efficienti:
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)
(Per Python 3.x sostituire itertools.izip
con zip
).
sum(1 for _ in iterator)
, questo era quasi il doppio più veloce.
zip
importante : se passi zip(counter, iterable)
, otterrai effettivamente 1 in più rispetto al conteggio iterabile!
Tipo. Si potrebbe verificare il __length_hint__
metodo, ma sappiate che (almeno fino a Python 3.4, come gsnedders punti utilmente out) si tratta di un dettaglio di implementazione non documentata ( seguente messaggio in filo ), che potrebbe benissimo scomparire o evocazione nasale demoni, invece.
Altrimenti no. Gli iteratori sono solo un oggetto che espone solo il next()
metodo. Puoi chiamarlo tutte le volte che è necessario e potrebbero eventualmente rilanciare StopIteration
. Fortunatamente, questo comportamento è per lo più trasparente per il programmatore. :)
__length_hint__
è ora documentato, ma è un suggerimento e non garantisce l'accuratezza.
Mi piace la cardinalità pacchetto per questo, è molto leggero e cerca di utilizzare l'implementazione più veloce possibile a seconda dell'iterabile.
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
L' count()
attuazione effettiva è la seguente:
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
Quindi, per coloro che vorrebbero conoscere il riassunto di quella discussione. I punteggi migliori finali per il conteggio di un'espressione di un generatore di 50 milioni usando:
len(list(gen))
, len([_ for _ in gen])
, sum(1 for _ in gen),
ilen(gen)
(da more_itertool ),reduce(lambda c, i: c + 1, gen, 0)
, ordinati in base all'esecuzione (incluso il consumo di memoria), ti sorprenderà:
`` `
gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))
('list, sec', 1.9684218849870376)
gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen])
('list_compr, sec', 2.5885991149989422)
gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic()
('sum, sec', 3.441088170016883)
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)
gen = (i for i in data*1000); t0 = monotonic(); reduce(lambda counter, i: counter + 1, gen, 0)
('ridurre, sec', 13.436614598002052) `` '
Quindi, len(list(gen))
è il consumo di memoria più frequente e meno
len(list(gen))
consumare meno memoria dell'approccio basato sulla riduzione? Il primo crea un nuovo list
che comporta l'allocazione di memoria mentre il secondo no. Quindi mi aspetto che quest'ultimo sia più efficiente in termini di memoria. Inoltre, il consumo di memoria dipenderà dal tipo di elemento.
len(tuple(iterable))
può essere ancora più efficiente: articolo di Nelson Minar
Un iteratore è solo un oggetto che ha un puntatore all'oggetto successivo che deve essere letto da un qualche tipo di buffer o stream, è come un LinkedList in cui non sai quante cose hai fino a quando non le esegui. Gli iteratori sono pensati per essere efficienti perché tutto ciò che fanno è dirti cosa sarà il prossimo tramite i riferimenti invece di usare l'indicizzazione (ma come hai visto perdi la possibilità di vedere quante voci sono le prossime).
Per quanto riguarda la tua domanda originale, la risposta è ancora che non c'è modo in generale di conoscere la lunghezza di un iteratore in Python.
Dato che la tua domanda è motivata da un'applicazione della libreria pysam, posso dare una risposta più specifica: sono un collaboratore di PySAM e la risposta definitiva è che i file SAM / BAM non forniscono un conteggio esatto di letture allineate. Né queste informazioni sono facilmente disponibili da un file indice BAM. La cosa migliore da fare è stimare il numero approssimativo di allineamenti utilizzando la posizione del puntatore del file dopo aver letto un numero di allineamenti ed estrapolato in base alla dimensione totale del file. Questo è sufficiente per implementare una barra di avanzamento, ma non un metodo per contare gli allineamenti in tempo costante.
Un rapido benchmark:
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))
I risultati:
10000 loops, best of 3: 37.2 µs per loop
10000 loops, best of 3: 47.6 µs per loop
10000 loops, best of 3: 61 µs per loop
Cioè il semplice count_iter_items è la strada da percorrere.
Adattandolo per python3:
61.9 µs ± 275 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
74.4 µs ± 190 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
82.6 µs ± 164 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Esistono due modi per ottenere la lunghezza di "qualcosa" su un computer.
Il primo modo è quello di memorizzare un conteggio - questo richiede tutto ciò che tocca il file / i dati per modificarlo (o una classe che espone solo le interfacce - ma si riduce alla stessa cosa).
L'altro modo è iterare su di esso e contare quanto è grande.
Questo è in contrasto con la definizione stessa di un iteratore, che è un puntatore a un oggetto, oltre a informazioni su come raggiungere l'oggetto successivo.
Un iteratore non sa quante altre volte sarà in grado di iterare fino al termine. Questo potrebbe essere infinito, quindi l'infinito potrebbe essere la tua risposta.
Sebbene in generale non sia possibile eseguire ciò che è stato richiesto, è spesso utile avere un conteggio di quanti elementi sono stati ripetuti dopo averli ripetuti. Per questo, puoi usare jaraco.itertools.Counter o simili. Ecco un esempio usando Python 3 e rwt per caricare il pacchetto.
$ 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
Presumibilmente, vuoi contare il numero di elementi senza iterare, in modo che l'iteratore non sia esaurito e lo usi di nuovo in seguito. Questo è possibile con copy
odeepcopy
import copy
def get_iter_len(iterator):
return sum(1 for _ in copy.copy(iterator))
###############################################
iterator = range(0, 10)
print(get_iter_len(iterator))
if len(tuple(iterator)) > 1:
print("Finding the length did not exhaust the iterator!")
else:
print("oh no! it's all gone")
L'output è "Finding the length did not exhaust the iterator!
"
Facoltativamente (e sconsigliato), è possibile ombreggiare la len
funzione integrata come segue:
import copy
def len(obj, *, len=len):
try:
if hasattr(obj, "__len__"):
r = len(obj)
elif hasattr(obj, "__next__"):
r = sum(1 for _ in copy.copy(obj))
else:
r = len(obj)
finally:
pass
return r
map
iteratore in attesa che le chiamate di funzione risultanti si verifichino una sola volta.