Iterator listy cyklicznej w Pythonie


99

Muszę powtarzać listę cykliczną, możliwie wiele razy, za każdym razem zaczynając od ostatnio odwiedzanego elementu.

Przypadek użycia to pula połączeń. Klient prosi o połączenie, iterator sprawdza, czy wskazane połączenie jest dostępne i zwraca je, w przeciwnym razie wykonuje pętlę, dopóki nie znajdzie takiego, które jest dostępne.

Czy jest na to zgrabny sposób w Pythonie?

Odpowiedzi:


159

Użyj itertools.cycle, to jest jego dokładny cel:

from itertools import cycle

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

pool = cycle(lst)

for item in pool:
    print item,

Wynik:

a b c a b c ...

(Oczywiście pętle na zawsze)


Aby ręcznie przesuwać iterator i wyciągać z niego wartości pojedynczo, po prostu wywołaj next(pool):

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

1
Drukujesz elementy w pętli. Co chcę opuścić pętlę i wrócić później? (Chcę zacząć od miejsca, w którym skończyłem).
user443854

7
@ user443854 użyj, pool.next()aby uzyskać kolejny pojedynczy element z cyklu
Jacob Krall

4
@ user443854 FWIW to znacznie lepsza odpowiedź niż moja. Nie ma powodu, aby ponownie wdrażać funkcje biblioteki!
Jacob Krall

5
pool.next () nie działał dla mnie, tylko next (pool). Prawdopodobnie z powodu Pythona 3?
fjsj

6
@fjsj, to prawda, w Pythonie 3 musisz użyć next(iterator)(który BTW działa również dobrze w Pythonie 2.x, a zatem jest to forma kanoniczna, której należy użyć). Zobacz Czy generator.next () jest widoczny w Pythonie 3.0? aby uzyskać bardziej szczegółowe wyjaśnienie. Odpowiednio zaktualizowałem moją odpowiedź.
Lukas Graf,

54

Prawidłowa odpowiedź to użycie itertools.cycle . Ale załóżmy, że funkcja biblioteki nie istnieje. Jak byś to zaimplementował?

Użyj generatora :

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

Następnie możesz użyć forinstrukcji, aby iterować w nieskończoność, lub możesz wywołać, next()aby uzyskać pojedynczą następną wartość z iteratora generatora:

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

Miły! Skąd wie, że ma zacząć od nowa, gdy lista jest wyczerpana?
user443854

1
@ user443854 while Truesposób na wieczne powtarzanie
Jacob Krall

2
@juanchopanza: Tak; itertools.cycleto lepsza odpowiedź. To pokazuje, jak możesz napisać tę samą funkcjonalność, jeśli itertoolsnie jest dostępna :)
Jacob Krall

Czy prosty generator również zapisuje kopię każdego elementu, tak jak itertools.cyclerobi? A może prosty generator byłby bardziej wydajną pamięcią? Zgodnie z cycledokumentami :Note, this member of the toolkit may require significant auxiliary storage (depending on the length of the iterable).
dthor

2
@dthor ten generator tworzy listę z trzema elementami i pisze na niej, a następnie niszczy listę i tworzy nową, na zawsze. Ta dokumentacja dla cyclezakłada, że ​​wejście iterowalne jest konwertowane na wejście listprzed uruchomieniem jego generatora, ponieważ iterablejest „dobre tylko dla jednego przejścia przez zbiór wartości”.
Jacob Krall

9

Lub możesz to zrobić:

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

wypisuje abcdefab c ... na zawsze


3

możesz to osiągnąć za pomocą append(pop())pętli:

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

lub for i in range()pętla:

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

lub po prostu:

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

while 1:
    for i in l:
       print i

z których wszystkie drukują:

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

z tych trzech byłbym podatny na podejście append (pop ()) jako funkcję

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]

Głosowanie za tym, ponieważ pomogło mi to w zupełnie innym przypadku użycia, w którym po prostu chcę powtórzyć listę kilka razy, za każdym razem z elementem początkowym przesuwającym się o jeden krok. Moim przypadkiem użycia jest iteracja graczy w grze w pokera, przesuwanie krążka krupiera o jednego gracza do przodu w każdej rundzie.
Johan

2

Potrzebujesz niestandardowego iteratora - dostosuję iterator na podstawie tej odpowiedzi .

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

Jeśli chcesz zmieniać nczasy cykli , zastosuj ncycles przepis 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']
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.