In Go ist es möglich, Durchlaufen Sie einen benutzerdefinierten Typ?
Ich habe einen benutzerdefinierten Datentyps, die intern eine Scheibe von Daten.
Ist es möglich, durch die Implementierung einiger Funktionen oder eine Schnittstelle, die den range-operator braucht, um Durchlaufen (mit range) über meine eigene Art?
Du musst angemeldet sein, um einen Kommentar abzugeben.
Die kurze Antwort ist Nein.
Die lange Antwort ist immer noch Nein, aber es ist möglich, es zu hacken, in einer Weise, dass es irgendwie funktioniert. Aber klar zu sein, das ist den meisten sicherlich ein hack.
Gibt es ein paar Möglichkeiten, Sie können es tun, aber das gemeinsame Thema zwischen Ihnen ist, dass Sie wollen, um irgendwie verwandeln Sie Ihre Daten in einer Art, die Gehen, ist in der Lage bis über.
Ansatz 1: Scheiben
Da Sie erwähnt, dass Sie ein slice-intern, dies kann am einfachsten für Ihren Anwendungsfall. Die Idee ist einfach: Ihre Art haben sollten, eine
Iterate()
Methode (oder ähnliche), deren Rückgabewert ist ein Stück des entsprechenden Typs. Wenn Sie aufgerufen werden, eine neue Scheibe erstellt, die alle Elemente der Datenstruktur in der Reihenfolge, die Sie möchten, dass Sie zu der Iteration. So zum Beispiel:Gibt es ein paar Bedenken hier. Erste, Vergabe -, es sei denn, Sie wollen zu setzen Verweise auf die internen Daten (welche, im Allgemeinen, Sie wahrscheinlich nicht), haben Sie, um eine neue Scheibe und kopieren Sie alle Elemente über. Vom big-O-Standpunkt aus ist das nicht schlecht (Sie tun eine lineare Menge von Arbeit, die Durchlaufen ja eh alles), aber für praktische Zwecke, es kann von Bedeutung sein.
Zusätzlich, diese nicht handhaben Iteration über die Daten verändert werden. Dies ist wahrscheinlich nicht ein Problem die meisten der Zeit, aber wenn Sie wirklich wollen, zu unterstützen gleichzeitige updates und bestimmte Arten von iteration Semantik, könnten Sie Pflege.
Ansatz 2: Kanäle
Kanäle sind auch etwas, das sein kann, reichten über in Gehen. Die Idee ist Ihr
Iterate()
Methode spawn eine goroutine, der Iteration über die Elemente in der Datenstruktur, und schreiben Sie Sie auf einem Kanal. Dann, wenn die iteration erfolgt, die Kanäle geschlossen werden können, wodurch sich die Schleife zu beenden. Zum Beispiel:Es gibt zwei Vorteile dieser Methode gegenüber der slice-Methode: Erstens, Sie müssen nicht reservieren, eine lineare Größe des Speichers (obwohl möchten Sie vielleicht, um Ihrem Kanal ein wenig Puffer für die performance-Gründen), und zweitens können Sie Ihre iterator schön spielen mit gleichzeitigen updates, wenn Sie in diese Art der Sache.
Den großen Nachteil dieses Ansatzes ist, dass, wenn Sie nicht vorsichtig sind, können Sie Leck goroutines. Die einzige Weise um dieses ist, um Ihren Kanal einen Puffer tief genug, um alle halten der Elemente in der Datenstruktur, so dass die goroutine kann, füllen es aus und kehren dann auch, wenn keine Elemente Lesen aus dem Kanal (und der Kanal kann dann später werden, Müll gesammelt). Das problem hier ist, dass, a) Sie sind jetzt zurück in die lineare Zuteilung und b) Sie müssen wissen, up-front, wie viele Elemente, die Sie gehen zu schreiben, welche Art von Unterbinde das ganze Auger-updates Ding.
Die moral von der Geschichte ist, dass die Kanäle sind nett zum Durchlaufen, aber Sie wahrscheinlich nicht wollen, um tatsächlich mit Ihnen.
Vorgehensweise 3: Interne Iteratoren
Kredit hobbs für bekommen, um dieses vor mir, aber ich werde es hier der Vollständigkeit halber (und weil ich will, zu sagen, ein bisschen mehr darüber).
Die Idee hier ist, um erstellen Sie ein iterator-Objekt der Art (oder einfach nur Ihr Objekt unterstützt nur ein iterator gleichzeitig, und beurteilen Sie es direkt), genauso wie in Sprachen, die diese unterstützen, mehr direkt. Was Sie tun, ist rufen Sie einen
Next()
Methode, die, a) setzt den iterator auf das nächste element und b) gibt einen booleschen Wert zurück, der angibt, ob oder nicht es ist alles andere Links. Dann benötigen Sie eine separateGet()
Methode, um tatsächlich den Wert des aktuellen Elements. Die Nutzung dieser nicht tatsächlich dierange
keyword, aber es sieht ziemlich natürlich trotzdem:Gibt es einige Vorteile dieser Technik über die letzten zwei. Erstens, es sich nicht um die Zuweisung von Speicher nach vorne. Zweitens, es unterstützt Fehler sehr natürlich. Es ist zwar nicht wirklich ein iterator, das ist genau das, was
bufio.Scanner
tut. Im Grunde ist die Idee, eineError()
Methode, die Sie nennen, nach der die iteration abgeschlossen ist, um zu sehen, ob die iteration terminiert, da es gemacht wurde, oder weil ein Fehler festgestellt wurde in der Mitte durch. Für Reine in-memory-Datenstrukturen kann dies egal, aber für diejenigen, die beinhalten IO (z.B. walking ein Dateisystem-Baum iteriert Datenbank-Abfrage-Ergebnisse, etc.), es ist wirklich schön. So, um das code-snippet oben:Abschluss
Gehen nicht unterstützt, angefangen über beliebige Daten-Strukturen - oder benutzerdefinierte Iteratoren - aber Sie können es zu hacken. Wenn Sie haben, dies zu tun in der Produktion code, der Dritte Ansatz ist 100% der Weg zu gehen, wie es sowohl der sauberste und am wenigsten von einem hack (die standard-Bibliothek enthält dieses Muster).
select
einedefer close
, und ein bisschen Pflege.Nein, nicht mit
range
.range
akzeptiert arrays, slices, strings, maps und Kanäle, und das ist es.Den üblichen idiom für iterierbar Dinge (zum Beispiel ein
bufio.Scanner
) scheint zu sein,aber es gibt keine universal-Schnittstelle (wäre nicht sehr sinnvoll, da das Typ-system sowieso) und verschiedenen Arten, die Umsetzung der Muster in der Regel haben unterschiedliche Namen für Ihre
More
undItem
Methoden (zum BeispielScan
undText
für einebufio.Scanner
)joshlf gab eine ausgezeichnete Antwort, aber ich möchte noch hinzufügen, ein paar Dinge:
Kanäle
Einem typischen problem mit Kanal-Iteratoren ist, dass Sie die Reichweite durch die gesamte Datenstruktur oder die goroutine Fütterung der Kanal sein wird, bleibt hängen für immer. Dies kann man aber ganz leicht umgangen werden, hier ist eine Möglichkeit:
In diesem Fall schreiben zurück der iterator-Kanal unterbricht die iteration vorzeitig:
Hier ist es sehr wichtig, dass Sie nicht einfach
break
aus der for-Schleife. Sie kann brechen, aber Sie müssen muss schreiben Sie den ersten Kanal, um sicherzustellen, die goroutine verlassen.Mit Verschlüssen
Methode der iteration, die ich neigen oft dazu, zu begünstigen, ist die Verwendung eines iterator-Verschluss. In diesem Fall wird der iterator ist ein Wert für die Funktion, die, wenn Sie wiederholt aufgerufen, gibt das nächste element und zeigt an, ob die iteration kann fortgesetzt:
Verwenden Sie es wie diese:
In diesem Fall ist es völlig in Ordnung, Sie zu brechen aus der Schleife früh
iter
wird schließlich von der garbage Collection freigegeben.Spielplatz
Hier ist der link zu den Implementierungen vor: http://play.golang.org/p/JC2EpBDQKA
Es ist eine weitere option, die war nicht erwähnt.
Können Sie festlegen, Iter(fn func(int)) Funktion akzeptiert eine Funktion, die aufgerufen wird für jedes Element in Ihrem benutzerdefinierten Typ.
Und es kann z.B. so verwendet werden:
Spielplatz
Link zu working-Umsetzung: https://play.golang.org/p/S3CTQmGXj79