Iterador de lista circular en Python

99

Necesito iterar sobre una lista circular, posiblemente muchas veces, cada vez comenzando con el último elemento visitado.

El caso de uso es un grupo de conexiones. Un cliente solicita una conexión, un iterador comprueba si la conexión apuntada está disponible y la devuelve; de ​​lo contrario, realiza un bucle hasta que encuentra una que esté disponible.

¿Hay alguna forma ordenada de hacerlo en Python?

usuario443854
fuente

Respuestas:

159

Uso itertools.cycle, ese es su propósito exacto:

from itertools import cycle

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

pool = cycle(lst)

for item in pool:
    print item,

Salida:

a b c a b c ...

(Bucles para siempre, obviamente)


Para avanzar manualmente el iterador y extraer valores de él uno por uno, simplemente llame a next(pool):

>>> next(pool)
'a'
>>> next(pool)
'b'
Lukas Graf
fuente
1
Está imprimiendo elementos en un bucle. ¿Qué quiero dejar el bucle y volver más tarde? (Quiero empezar donde lo dejé).
user443854
7
@ user443854 uso pool.next()para obtener el siguiente elemento único del ciclo
Jacob Krall
4
@ user443854 FWIW esta es una respuesta mucho mejor que la mía. ¡No hay razón para volver a implementar las funciones de la biblioteca!
Jacob Krall
5
pool.next () no funcionó para mí, solo next (pool). ¿Probablemente debido a Python 3?
fjsj
6
@fjsj eso es correcto, en Python 3 debe usar next(iterator)(que por cierto también funciona bien en Python 2.xy, por lo tanto, es la forma canónica que debe usarse). Consulte ¿Es generator.next () visible en python 3.0? para una explicación más detallada. Actualicé mi respuesta en consecuencia.
Lukas Graf
54

La respuesta correcta es usar itertools.cycle . Pero supongamos que la función de biblioteca no existe. ¿Cómo lo implementaría?

Utilice un generador :

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

Luego, puede usar una fordeclaración para iterar infinitamente, o puede llamar next()para obtener el siguiente valor único del iterador del generador:

connections = circular()
next(connections) # 'a'
next(connections) # 'b'
next(connections) # 'c'
next(connections) # 'a'
next(connections) # 'b'
next(connections) # 'c'
next(connections) # 'a'
#....
Jacob Krall
fuente
¡Agradable! ¿Cómo sabe volver a empezar cuando se agota la lista?
user443854
1
@ user443854 los while Truemedios para repetir para siempre
Jacob Krall
2
@juanchopanza: Sí; itertools.cyclees una mejor respuesta. Esto muestra cómo podría escribir la misma funcionalidad si itertoolsno está disponible :)
Jacob Krall
¿El generador simple también guarda una copia de cada elemento como lo itertools.cyclehace? ¿O el generador simple sería un diseño más eficiente en memoria? Según los cycledocumentos :Note, this member of the toolkit may require significant auxiliary storage (depending on the length of the iterable).
2016
2
@dthor este generador crea una lista con tres elementos y la lee, luego destruye la lista y crea una nueva, a perpetuidad. Esa documentación para cycleimplica que la entrada iterable se convierte listantes de que se inicie su generador, ya iterableque solo es "buena para una pasada sobre el conjunto de valores".
Jacob Krall
9

O puedes hacer esto:

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

imprime abcdefab c ... para siempre

viky.pat
fuente
3

puedes lograr esto con append(pop())loop:

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

o for i in range()bucle:

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

o simplemente:

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

while 1:
    for i in l:
       print i

todos los cuales imprimen:

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

de los tres, sería propenso al método append (pop ()) como función

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]
presencia ligera
fuente
Votar esto porque me ayudó con un caso de uso completamente diferente en el que simplemente quiero iterar sobre una lista varias veces, cada vez con el elemento de inicio avanzando un paso. Mi caso de uso es iterar sobre los jugadores en un juego de póquer, haciendo avanzar el disco del crupier un jugador hacia adelante en cada ronda.
Johan
2

Necesita un iterador personalizado; adaptaré el iterador de esta respuesta .

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
Ethan Furman
fuente
2

Si desea ciclos de ntiempos, implemente la ncycles receta de 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']
pylang
fuente