Iteratore di liste circolari in Python


99

Ho bisogno di scorrere un elenco circolare, possibilmente molte volte, ogni volta iniziando dall'ultimo elemento visitato.

Il caso d'uso è un pool di connessioni. Un client richiede una connessione, un iteratore controlla se la connessione puntata è disponibile e la restituisce, altrimenti esegue un ciclo finché non ne trova una disponibile.

C'è un modo pulito per farlo in Python?

Risposte:


159

Usa itertools.cycle, questo è il suo scopo esatto:

from itertools import cycle

lst = ['a', 'b', 'c']

pool = cycle(lst)

for item in pool:
    print item,

Produzione:

a b c a b c ...

(Loop per sempre, ovviamente)


Per far avanzare manualmente l'iteratore e estrarne i valori uno per uno, è sufficiente chiamare next(pool):

>>> next(pool)
'a'
>>> next(pool)
'b'

1
Stai stampando elementi in un ciclo. Cosa voglio lasciare il ciclo e tornare più tardi? (Voglio iniziare da dove avevo lasciato).
user443854

7
@ user443854 utilizzare pool.next()per ottenere il singolo elemento successivo dal ciclo
Jacob Krall

4
@ user443854 FWIW questa è una risposta molto migliore della mia. Non c'è motivo di reimplementare le funzioni di libreria!
Jacob Krall

5
pool.next () non ha funzionato per me, solo next (pool). Probabilmente a causa di Python 3?
fjsj

6
@fjsj che è corretto, su Python 3 devi usare next(iterator)(che BTW funziona anche bene su Python 2.x, e quindi è la forma canonica che dovrebbe essere usata). Vedi Generatore.next () visibile in Python 3.0? per una spiegazione più approfondita. Ho aggiornato di conseguenza la mia risposta.
Lukas Graf

54

La risposta corretta è usare itertools.cycle . Ma supponiamo che la funzione di libreria non esista. Come lo implementeresti?

Usa un generatore :

def circular():
    while True:
        for connection in ['a', 'b', 'c']:
            yield connection

Quindi, puoi utilizzare forun'istruzione per iterare all'infinito oppure puoi chiamare next()per ottenere il singolo valore successivo dall'iteratore del generatore:

connections = circular()
next(connections) # 'a'
next(connections) # 'b'
next(connections) # 'c'
next(connections) # 'a'
next(connections) # 'b'
next(connections) # 'c'
next(connections) # 'a'
#....

Bello! Come fa a sapere di ricominciare da capo quando la lista è esaurita?
user443854

1
@ user443854 il while Truemezzo per ripetere per sempre
Jacob Krall

2
@juanchopanza: Sì; itertools.cycleè una risposta migliore. Questo mostra come potresti scrivere la stessa funzionalità se itertoolsnon è disponibile :)
Jacob Krall

Il generatore semplice salva anche una copia di ogni elemento come itertools.cyclefa? O il semplice generatore sarebbe un design più efficiente in termini di memoria? Secondo i cycledocumenti :Note, this member of the toolkit may require significant auxiliary storage (depending on the length of the iterable).
dthor

2
@dthor questo generatore crea un elenco con tre elementi e lo alfabetizza, quindi distrugge l'elenco e ne crea uno nuovo, per sempre. Quella documentazione per cycleimplica che l'input iterabile venga convertito listprima dell'avvio del suo generatore, poiché iterableè solo "buono per un passaggio sull'insieme di valori".
Jacob Krall

9

Oppure puoi fare così:

conn = ['a', 'b', 'c', 'd', 'e', 'f']
conn_len = len(conn)
index = 0
while True:
    print(conn[index])
    index = (index + 1) % conn_len

stampa abcdefab c ... per sempre


3

puoi farlo con append(pop())loop:

l = ['a','b','c','d']
while 1:
    print l[0]
    l.append(l.pop(0))

o for i in range()loop:

l = ['a','b','c','d']
ll = len(l)
while 1:
    for i in range(ll):
       print l[i]

o semplicemente:

l = ['a','b','c','d']

while 1:
    for i in l:
       print i

tutti stampati:

>>>
a
b
c
d
a
b
c
d
...etc.

dei tre sarei incline all'approccio append (pop ()) come funzione

servers = ['a','b','c','d']

def rotate_servers(servers):
    servers.append(servers.pop(0))
    return servers

while 1:
    servers = rotate_servers(servers)
    print servers[0]

Upvoting questo perché mi ha aiutato con un caso d'uso completamente diverso in cui voglio semplicemente iterare su un elenco un numero di volte, ogni volta con l'elemento iniziale che avanza di un passaggio. Il mio caso d'uso è quello di iterare sui giocatori in una partita di poker, facendo avanzare il disco del dealer di un giocatore in avanti per ogni round.
Johan

2

Hai bisogno di un iteratore personalizzato: adatterò l'iteratore da questa risposta .

from itertools import cycle

class ConnectionPool():
    def __init__(self, ...):
        # whatever is appropriate here to initilize
        # your data
        self.pool = cycle([blah, blah, etc])
    def __iter__(self):
        return self
    def __next__(self):
        for connection in self.pool:
            if connection.is_available:  # or however you spell it
                return connection

2

Se desideri programmare i ntempi, implementa la ncycles ricetta itertools :

from itertools import chain, repeat


def ncycles(iterable, n):
    "Returns the sequence elements n times"
    return chain.from_iterable(repeat(tuple(iterable), n))


list(ncycles(["a", "b", "c"], 3))
# ['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c']
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.