Domanda

Python fornisce un buon metodo per ottenere la lunghezza di un iterabile desideroso, len(x) questo è.Ma non sono riuscito a trovare nulla di simile per gli iterabili pigri rappresentati dalle comprensioni e dalle funzioni del generatore.Ovviamente non è difficile scrivere qualcosa del tipo:

def iterlen(x):
  n = 0
  try:
    while True:
      next(x)
      n += 1
  except StopIteration: pass
  return n

Ma non riesco a liberarmi della sensazione che sto reimplementando una bicicletta.

(Mentre digitavo la funzione, un pensiero mi colpì la mente:forse non esiste davvero una funzione del genere, perché "distrugge" il suo argomento.Non è un problema nel mio caso, però).

PS:riguardo alle prime risposte - sì, qualcosa del genere len(list(x)) funzionerebbe anche, ma ciò aumenta drasticamente l'utilizzo della memoria.

PPS:ricontrollato...Ignora il P.S., sembra che abbia commesso un errore mentre lo provavo, funziona bene.Scusa per il disturbo.

È stato utile?

Soluzione

Non c'è una sola perché non è possibile farlo nel caso generale - cosa succede se si dispone di un generatore infinita pigro? Ad esempio:

def fib():
    a, b = 0, 1
    while True:
        a, b = b, a + b
        yield a

Questa non termina ma genera i numeri di Fibonacci. È possibile ottenere il maggior numero di numeri di Fibonacci come si desidera chiamando next().

Se si ha realmente bisogno di conoscere il numero di elementi ci sono, allora non si può scorrere attraverso di loro linearmente una volta comunque, quindi basta usare una struttura dati diversa ad esempio un elenco regolare.

Altri suggerimenti

Il modo più semplice è probabilmente solo sum(1 for _ in gen) dove gen è il generatore.

def count(iter):
    return sum(1 for _ in iter)

O meglio ancora:

def count(iter):
    try:
        return len(iter)
    except TypeError:
        return sum(1 for _ in iter)

Se non è iterabile, si getterà un TypeError.

In alternativa, se si vuole contare qualcosa di specifico nel generatore:

def count(iter, key=None):
    if key:
        if callable(key):
            return sum(bool(key(x)) for x in iter)
        return sum(x == key for x in iter)
    try:
        return len(iter)
    except TypeError:
        return sum(1 for _ in iter)

Quindi, per coloro che desiderano conoscere la sintesi di tale discussione. I punteggi migliori finali per il conteggio un'espressione di 50 milioni di lengthed generatore utilizzando:

  • 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 per prestazioni di esecuzione (compreso il consumo di memoria), vi farà sorpreso:

`` `

1: test_list.py:8: 0,492 KiB

gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))

( 'elenco, 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()

( 'sum, 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)

( 'ridurre, sec', 13,436614598002052) `` `

Quindi, len(list(gen)) è la più frequente e meno memoria di consumo

Puoi utilizzare enumerate() per scorrere il flusso di dati generato, quindi restituire l'ultimo numero: il numero di elementi.

Ho provato a utilizzare itertools.count() con itertools.izip() ma senza fortuna.Questa è la risposta migliore/più breve che ho trovato:

#!/usr/bin/python

import itertools

def func():
    for i in 'yummy beer':
        yield i

def icount(ifunc):
    size = -1 # for the case of an empty iterator
    for size, _ in enumerate(ifunc()):
        pass
    return size + 1

print list(func())
print 'icount', icount(func)

# ['y', 'u', 'm', 'm', 'y', ' ', 'b', 'e', 'e', 'r']
# icount 10

La soluzione di Kamil Kisiel è decisamente migliore:

def count_iterable(i):
    return sum(1 for e in i)

ridurre (funzione, iterable [, initializer]) una soluzione puramente funzionale efficiente della memoria:

>>> iter = "This string has 30 characters."
>>> reduce(lambda acc, e: acc + 1, iter, 0)
30

Per definizione, solo un sottoinsieme di generatori restituirà dopo un certo numero di argomenti (hanno una lunghezza predefinita), e anche allora, solo un sottoinsieme di questi generatori finiti hanno un'estremità prevedibile (accesso al generatore può avere lato -Effetti che potrebbe fermare il generatore in precedenza).

Se si desidera implementare metodi di lunghezza per il generatore, è necessario prima definire che cosa si considera la "lunghezza" (è il numero totale di elementi? Il numero di elementi rimanenti?), Poi avvolgere il generatore in una classe . Ecco un esempio:

class MyFib(object):
    """
    A class iterator that iterates through values of the
    Fibonacci sequence, until, optionally, a maximum length is reached.
    """

    def __init__(self, length):
        self._length = length
        self._i = 0

     def __iter__(self):
        a, b = 0, 1
        while not self._length or self._i < self._length:
            a, b = b, a + b
            self._i += 1
            yield a

    def __len__(self):
        "This method returns the total number of elements"
        if self._length:
            return self._length
        else:
            raise NotImplementedError("Infinite sequence has no length")
            # or simply return None / 0 depending
            # on implementation

Ecco come usarlo:

In [151]: mf = MyFib(20)

In [152]: len(mf)
Out[152]: 20

In [153]: l = [n for n in mf]

In [154]: len(l)
Out[154]: 20

In [155]: l
Out[155]: 
[1,
 1,
 2,
...
6765]


In [156]: mf0 = MyFib(0)

In [157]: len(mf0)
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-157-2e89b32ad3e4> in <module>()
----> 1 len(mf0)

/tmp/ipython_edit_TWcV1I.py in __len__(self)
     22             return self._length
     23         else:
---> 24             raise NotImplementedError
     25             # or simply return None / 0 depending
     26             # on implementation

NotImplementedError: 

In [158]: g = iter(mf0)

In [159]: l0 = [g.next(), g.next(), g.next()]

In [160]: l0
Out[160]: [1, 1, 2]

Prova il href="http://more-itertools.readthedocs.io/en/latest/api.html" rel="nofollow noreferrer"> more_itertools pacchetto per una soluzione semplice. Esempio:

>>> import more_itertools

>>> it = iter("abcde")                                         # sample generator
>>> it
<str_iterator at 0x4ab3630>

>>> more_itertools.ilen(it)
5

questo post per un altro applicato esempio.

Questo è un hack, ma se si vuole veramente avere il lavoro len su un iterabile generale (consumarlo nel modo), è possibile creare la propria versione di len.

La funzione len è sostanzialmente equivalente alla seguente (anche se le implementazioni di solito forniscono alcune ottimizzazioni per evitare la ricerca extra):

def len(iterable):
    return iterable.__len__()

Quindi possiamo definire il nostro new_len per provare, e se __len__ non esiste, contare il numero di elementi di noi stessi consumando l'iterabile:

def new_len(iterable):
    try:
      return iterable.__len__()
    except AttributeError:
      return sum(1 for _ in iterable)

I lavori di cui sopra in Python 2/3, e (per quanto ne so) dovrebbe coprire ogni tipo immaginabile di iterabile.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top