Itérateur de liste circulaire en Python

99

Je dois parcourir une liste circulaire, peut-être plusieurs fois, chaque fois en commençant par le dernier élément visité.

Le cas d'utilisation est un pool de connexions. Un client demande une connexion, un itérateur vérifie si la connexion pointée est disponible et la renvoie, sinon boucle jusqu'à ce qu'il en trouve une disponible.

Existe-t-il un moyen efficace de le faire en Python?

user443854
la source

Réponses:

159

Utilisation itertools.cycle, c'est son but exact:

from itertools import cycle

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

pool = cycle(lst)

for item in pool:
    print item,

Production:

a b c a b c ...

(Boucle pour toujours, évidemment)


Pour faire avancer manuellement l'itérateur et en extraire les valeurs une par une, appelez simplement next(pool):

>>> next(pool)
'a'
>>> next(pool)
'b'
Lukas Graf
la source
1
Vous imprimez des éléments en boucle. Qu'est-ce que je veux quitter la boucle et revenir plus tard? (Je veux commencer là où j'ai laissé).
user443854
7
@ user443854 utilise pool.next()pour obtenir l'élément suivant du cycle
Jacob Krall
4
@ user443854 FWIW c'est une bien meilleure réponse que la mienne. Aucune raison de réimplémenter les fonctions de la bibliothèque!
Jacob Krall
5
pool.next () n'a pas fonctionné pour moi, seulement à côté (pool). Probablement à cause de Python 3?
fjsj
6
@fjsj c'est correct, sur Python 3, vous devez utiliser next(iterator)(ce qui fonctionne également très bien BTW sur Python 2.x, et est donc la forme canonique à utiliser). Voir Generator.next () est-il visible dans Python 3.0? pour une explication plus approfondie. J'ai mis à jour ma réponse en conséquence.
Lukas Graf
54

La bonne réponse est d'utiliser itertools.cycle . Mais supposons que la fonction de bibliothèque n'existe pas. Comment le mettriez-vous en œuvre?

Utilisez un générateur :

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

Ensuite, vous pouvez soit utiliser une forinstruction pour itérer à l'infini, soit appeler next()pour obtenir la valeur suivante unique de l'itérateur du générateur:

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
la source
Agréable! Comment sait-il recommencer lorsque la liste est épuisée?
user443854
1
@ user443854 le while Truemoyen de répéter pour toujours
Jacob Krall
2
@juanchopanza: Oui; itertools.cycleest une meilleure réponse. Cela montre comment vous pouvez écrire la même fonctionnalité si elle itertoolsn'est pas disponible :)
Jacob Krall
Le générateur simple enregistre-t-il également une copie de chaque élément comme le itertools.cyclefait? Ou le générateur simple serait-il une conception plus efficace en mémoire? Par la cycledocumentation :Note, this member of the toolkit may require significant auxiliary storage (depending on the length of the iterable).
dthor
2
@dthor ce générateur crée une liste avec trois éléments et la lit, puis détruit la liste et en crée une nouvelle, à perpétuité. Cette documentation pour cycleimplique que l'entrée itérable est convertie en listavant que son générateur ne démarre, car elle iterablen'est "bonne que pour un passage sur l'ensemble de valeurs".
Jacob Krall
9

Ou vous pouvez faire comme ceci:

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 ... pour toujours

viky.pat
la source
3

vous pouvez accomplir cela avec append(pop())loop:

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

ou for i in range()boucle:

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

ou simplement:

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

while 1:
    for i in l:
       print i

tous imprimés:

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

des trois je serais enclin à l'approche append (pop ()) en tant que fonction

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]
litepresence
la source
Le vote favorable parce que cela m'a aidé avec un cas d'utilisation complètement différent où je veux simplement parcourir une liste plusieurs fois, chaque fois avec l'élément de départ avançant d'une étape. Mon cas d'utilisation est d'itérer les joueurs dans une partie de poker, en faisant avancer la rondelle du donneur d'un joueur à chaque tour.
Johan
2

Vous avez besoin d'un itérateur personnalisé - je vais adapter l'itérateur à partir de cette réponse .

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
la source
2

Si vous souhaitez faire des cycles de ntemps, appliquez la ncycles recette 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
la source