Zirkularlisten-Iterator in Python

99

Ich muss eine zirkuläre Liste durchlaufen, möglicherweise viele Male, jedes Mal beginnend mit dem zuletzt besuchten Element.

Der Anwendungsfall ist ein Verbindungspool. Ein Client fragt nach einer Verbindung, ein Iterator prüft, ob eine Verbindung verfügbar ist, und gibt sie zurück. Andernfalls wird eine Schleife ausgeführt, bis eine verfügbare gefunden wird.

Gibt es eine gute Möglichkeit, dies in Python zu tun?

user443854
quelle

Antworten:

159

Verwenden Sie itertools.cycle, das ist der genaue Zweck:

from itertools import cycle

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

pool = cycle(lst)

for item in pool:
    print item,

Ausgabe:

a b c a b c ...

(Schleifen für immer, offensichtlich)


Um den Iterator manuell vorzuschieben und nacheinander Werte daraus abzurufen, rufen Sie einfach Folgendes auf next(pool):

>>> next(pool)
'a'
>>> next(pool)
'b'
Lukas Graf
quelle
1
Sie drucken Elemente in einer Schleife. Was möchte ich die Schleife verlassen und später zurückkommen? (Ich möchte dort anfangen, wo ich aufgehört habe).
user443854
7
@ user443854 verwenden pool.next(), um das nächste einzelne Element aus dem Zyklus zu erhalten
Jacob Krall
4
@ user443854 FWIW das ist eine viel bessere Antwort als meine. Kein Grund, Bibliotheksfunktionen neu zu implementieren!
Jacob Krall
5
pool.next () hat bei mir nicht funktioniert, nur next (pool). Wahrscheinlich wegen Python 3?
FJSJ
6
@fjsj das ist richtig, unter Python 3 müssen Sie verwenden next(iterator)(was übrigens auch unter Python 2.x gut funktioniert und daher die kanonische Form ist, die verwendet werden sollte). Siehe Ist generator.next () in Python 3.0 sichtbar? für eine ausführlichere Erklärung. Meine Antwort wurde entsprechend aktualisiert.
Lukas Graf
54

Die richtige Antwort ist die Verwendung von itertools.cycle . Nehmen wir jedoch an, dass die Bibliotheksfunktion nicht vorhanden ist. Wie würden Sie es implementieren?

Verwenden Sie einen Generator :

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

Dann können Sie entweder eine forAnweisung verwenden, um unendlich zu iterieren, oder Sie können aufrufen next(), um den einzelnen nächsten Wert vom Generator-Iterator abzurufen:

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
quelle
Nett! Woher weiß es, von vorne zu beginnen, wenn die Liste erschöpft ist?
user443854
1
@ user443854 die while TrueMittel, um für immer zu wiederholen
Jacob Krall
2
@juanchopanza: Ja; itertools.cycleist eine bessere Antwort. Dies zeigt, wie Sie die gleiche Funktionalität schreiben können, wenn sie itertoolsnicht verfügbar ist :)
Jacob Krall
Speichert der einfache Generator auch eine Kopie jedes Elements wie itertools.cycle? Oder wäre der einfache Generator ein speichereffizienteres Design? cycleNote, this member of the toolkit may require significant auxiliary storage (depending on the length of the iterable).
Gemäß
2
@dthor Dieser Generator erstellt eine Liste mit drei Elementen und liest darüber, zerstört dann die Liste und erstellt auf Dauer eine neue. Diese Dokumentation für cycleimpliziert, dass die iterierbare Eingabe listvor dem Start des Generators konvertiert wird , da dies iterablenur "gut für einen Durchgang über den Wertesatz" ist.
Jacob Krall
9

Oder Sie können so tun:

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

druckt abcdefab c ... für immer

viky.pat
quelle
3

Sie können dies mit append(pop())Schleife erreichen:

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

oder for i in range()Schleife:

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

oder einfach:

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

while 1:
    for i in l:
       print i

alle davon drucken:

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

Von den dreien wäre ich anfällig für den Ansatz append (pop ()) als Funktion

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]
Präsenz
quelle
Dies zu verbessern, weil es mir bei einem völlig anderen Anwendungsfall geholfen hat, bei dem ich einfach mehrmals über eine Liste iterieren möchte, wobei jedes Mal das Startelement einen Schritt weitergeht. Mein Anwendungsfall ist es, die Spieler in einem Pokerspiel zu durchlaufen und den Dealer für jede Runde einen Spieler nach vorne zu bringen.
Johan
2

Sie benötigen einen benutzerdefinierten Iterator - ich werde den Iterator aus dieser Antwort anpassen .

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
quelle
2

Wenn Sie Zykluszeiten möchten n, implementieren Sie das ncycles itertools-Rezept :

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
quelle