2013-08-02 18 views
6

Ho queste due implementazioni per calcolare la lunghezza di un generatore finita, mantenendo i dati per ulteriori elaborazioni:Lunghezza di un generatore finita

def count_generator1(generator): 
    '''- build a list with the generator data 
     - get the length of the data 
     - return both the length and the original data (in a list) 
     WARNING: the memory use is unbounded, and infinite generators will block this''' 
    l = list(generator) 
    return len(l), l 

def count_generator2(generator): 
    '''- get two generators from the original generator 
     - get the length of the data from one of them 
     - return both the length and the original data, as returned by tee 
     WARNING: tee can use up an unbounded amount of memory, and infinite generators will block this''' 
    for_length, saved = itertools.tee(generator, 2) 
    return sum(1 for _ in for_length), saved 

Entrambi hanno inconvenienti, sia fare il lavoro. Qualcuno potrebbe commentarli o addirittura offrire un'alternativa migliore?

+3

Non c'è modo di conoscere la lunghezza di un generatore iterabile senza consumare l'intera cosa. –

+0

Lo so. Questa non è la domanda – dangonfast

+2

nota: se se non hai bisogno della lunghezza precisa, puoi usare ['operator.length_hint()' (Python 3.4+)] (http://docs.python.org/dev/library /operator#operator.length_hint) che restituisce una lunghezza stimata senza consumare l'iteratore. Vedi [PEP 424 - Un metodo per esporre un suggerimento sulla lunghezza] (http://www.python.org/dev/peps/pep-0424/) – jfs

risposta

11

Se si deve fare questo, il primo metodo è molto meglio - poiché si consumano tutti i valori, itertools.tee() dovrà memorizzare comunque tutti i valori, il che significa che un elenco sarà più efficiente.

di citare the docs:

Questo itertools può richiedere notevole spazio di memoria (a seconda di quantità di dati temporanei devono essere memorizzati). In generale, se un iteratore utilizza la maggior parte o tutti i dati prima che inizi un altro iteratore, è più veloce usare list() invece di tee().

+0

Bene, in entrambi i casi sto consumando il generatore e sto memorizzando i dati completi. Nel primo, creando un 'list', nel secondo solo perché' tee' deve fare lo stesso (o qualcosa di simile). Penso che ottenere la lunghezza della lista sia più veloce (già parte dell'oggetto lista?), Ecco perché preferisco il primo metodo. Dal punto di vista del consumo di memoria, entrambi sembrano equivalenti, giusto? – dangonfast

+0

@gonvaled L'utilizzo della memoria sarà probabilmente simile, ma come cito dai documenti, fare una lista sarà più veloce. –

+0

ok, anche questa è stata la mia impressione. Grazie. – dangonfast

2

ho corse Windows a 64 bit Python 3.4.3 timeit su alcuni approcci mi veniva in mente:

>>> from timeit import timeit 
>>> from textwrap import dedent as d 
>>> timeit(
...  d(""" 
...  count = -1 
...  for _ in s: 
...   count += 1 
...  count += 1 
...  """), 
...  "s = range(1000)", 
...) 
50.70772041983173 
>>> timeit(
...  d(""" 
...  count = -1 
...  for count, _ in enumerate(s): 
...   pass 
...  count += 1 
...  """), 
...  "s = range(1000)", 
...) 
42.636973504498656 
>>> timeit(
...  d(""" 
...  count, _ = reduce(f, enumerate(range(1000)), (-1, -1)) 
...  count += 1 
...  """), 
...  d(""" 
...  from functools import reduce 
...  def f(_, count): 
...   return count 
...  s = range(1000) 
...  """), 
...) 
121.15513102540672 
>>> timeit("count = sum(1 for _ in s)", "s = range(1000)") 
58.179126025925825 
>>> timeit("count = len(tuple(s))", "s = range(1000)") 
19.777029680237774 
>>> timeit("count = len(list(s))", "s = range(1000)") 
18.145157531932 
>>> timeit("count = len(list(1 for _ in s))", "s = range(1000)") 
57.41422175998332 

Incredibilmente, l'approccio più veloce è stato quello di utilizzare un list (nemmeno un tuple) a esaurire l'iteratore e ottenere la lunghezza da lì:

>>> timeit("count = len(list(s))", "s = range(1000)") 
18.145157531932 

Naturalmente, questo rischia di problemi di memoria. La migliore alternativa a basso memoria è stato quello di utilizzare enumerare su un NOOP for -loop:

>>> timeit(
...  d(""" 
...  count = -1 
...  for count, _ in enumerate(s): 
...   pass 
...  count += 1 
...  """), 
...  "s = range(1000)", 
...) 
42.636973504498656 

Cheers!

Problemi correlati