Was sind die Vorteile des nächsten Iterators gegenüber diesem Iterator?

8

Ich arbeite nicht zu oft direkt mit Java / C # -Iteratoren, aber wenn ich das tue, frage ich mich immer, was der Grund war, Iteratoren auf "nächste" Weise zu entwerfen.

Um zu starten, müssen Sie den Iterator verschieben. Um zu überprüfen, ob Daten vorhanden sind, müssen Sie überprüfen, ob das nächste Element vorhanden ist.

Ein ansprechenderes Konzept für mich ist dieser Iterator - er "beginnt" von vorne, und Sie können diesen Zustand überprüfen, sagen wir isValid. Die Schleife über die gesamte Sammlung würde also folgendermaßen aussehen:

while (iter.isValid())
{
  println(iter.current());
  iter.next();
}

Wenn Sie hier sind, um zu überprüfen, ob es das nächste Element gibt, gehen Sie dorthin und prüfen dann, ob der Status gültig ist.

YMMV aber trotzdem - gibt es einen Vorteil des nächsten Iterators (wie in Java / C #) gegenüber diesem Iterator?

Hinweis: Dies ist eine konzeptionelle Frage zum Design der Sprache.

Greenoldman
quelle
Frage ist für Java oder C # bzoc isValid Methode existiert nicht in Java
@NiksTyagi Es handelt sich um eine konzeptionelle Frage, die für beide gleichermaßen gilt, da sich das vorgeschlagene Design von der Implementierung beider Sprachen unterscheidet.
Servy
Jeder virtuelle Anruf ist teuer. Ihr Beispiel hat drei Aufrufe, .net nur zwei. Persönlich würde ich es vorziehen, es auf einen Anruf zu reduzieren.
CodesInChaos

Antworten:

6

Ich denke, ein Vorteil des C # /. NET- MoveNext()Modells gegenüber dem Java- hasNext()Modell besteht darin, dass das erstere impliziert, dass einige Arbeiten durchgeführt werden können. Die hasNext()Methode impliziert eine einfache Zustandsprüfung. Aber was ist, wenn Sie einen faulen Stream iterieren und in eine Datenbank gehen müssen, um festzustellen, ob Ergebnisse vorliegen? In diesem Fall kann der erste Aufruf hasNext()eine lange Blockierungsoperation sein. Die Benennung der hasNext()Methode kann sehr irreführend sein, was tatsächlich vor sich geht.

Für ein Beispiel aus der Praxis hat mich dieses Designproblem bei der Implementierung der Rx-APIs (Reactive Extensions) von Microsoft in Java gebissen. Insbesondere für den Iterator, der von erstellt wurde, IObservable.asIterable()um ordnungsgemäß zu funktionieren, hasNext()müsste die Methode blockieren und auf das Eintreffen der nächsten Element- / Fehler- / Abschlussbenachrichtigung warten. Das ist kaum intuitiv: Der Name impliziert eine einfache Zustandsprüfung. Vergleichen Sie dies mit dem C # -Design, bei MoveNext()dem blockiert werden müsste, was kein völlig unerwartetes Ergebnis ist, wenn Sie mit einem träge ausgewerteten Stream arbeiten.

Mein Punkt ist folgender: Das Modell "nächster Iterator" ist dem Modell "dieser Iterator" vorzuziehen, da das Modell "dieser Iterator" häufig eine Lüge ist: Möglicherweise müssen Sie das nächste Element vorab abrufen, um es zu überprüfen der Zustand des Iterators. Ein Design, das diese Möglichkeit klar kommuniziert, ist meiner Meinung nach vorzuziehen. Ja, die Namenskonventionen von Java folgen zwar dem "nächsten" Modell, aber die Auswirkungen auf das Verhalten ähneln denen des "this-iterator" -Modells.


Klarstellung: Vielleicht war es eine schlechte Wahl, Java und C # gegenüberzustellen, weil Javas Modell täuscht. Ich betrachte die wichtigste Unterscheidung in den vom OP vorgestellten Modellen darin, dass das "this-iterator" -Modell die "is valid" -Logik von der Logik "current / next element abrufen" entkoppelt, während es sich um eine ideale "next-iterator" -Implementierung handelt kombiniert diese Operationen. Ich halte es für angebracht, sie zu kombinieren, da in einigen Fällen das Vorabrufen des nächsten Elements erforderlich ist, um festzustellen, ob der Status des Iterators gültig ist , sodass die Möglichkeit so explizit wie möglich gemacht werden sollte.

Ich sehe keinen signifikanten Designunterschied zwischen:

while (i.isValid()) { // do we have an element?  (implied as fast, non-blocking)
    doSomething(i.current()); // retrieve the element (may be slow!)
    i.next();
}
// ...and:
while (i.hasNext()) { // do we have an element?  (implied as fast, non-blocking)
    doSomething(i.next()); // retrieve the element (may be slow!)
}

Aber ich sehe hier einen bedeutenden Unterschied:

while (i.hasNext()) { // do we have an element?  (implied as fast, non-blocking)
    doSomething(i.next()); // retrieve the element (may be slow!)
}
// ...and:
while (i.moveNext()) { // fetch the next element if it exists (may be slow!)
    doSomething(i.current()); // get the element  (implied as fast, non-blocking)
}

In den beiden isValid()und hasNext()Beispielen wird die Operation , die eine schnelle und nicht-blockierenden Zustand Prüfung impliziert werden , kann in der Tat eine langsame, Sperrbetrieb . In diesem moveNext()Beispiel wird der größte Teil der Arbeit in der erwarteten Methode ausgeführt, unabhängig davon, ob es sich um einen eifrig oder träge bewerteten Stream handelt.

Mike Strobel
quelle
1
+0: ​​Ich denke, OP hat versucht, Java / C # mit einem anderen Iterator zu vergleichen (sowohl C # IEnumrable als auch Java Iterator zeigen beim Erstellen auf "Vorher" -Elemente). Mein Verständnis der Frage ist, warum ich nicht auf das erste Element verweise, anstatt C # mit Java zu vergleichen.
Alexei Levenkov
Vielen Dank für die Antwort, aber C # und Java verwenden dasselbe Modell (beide konzentrieren sich auf das nächste Element).
Greenoldman
Das C # - und das Java-Modell sind definitiv nicht dasselbe. Siehe mein hinzugefügtes Beispiel. Das Modell von C # kombiniert die Operationen zum Überprüfen des Vorhandenseins eines Elements mit dem Abrufen dieses Elements. Java teilt diese in separate Vorgänge auf, wobei die Anwesenheitsprüfung manchmal das tatsächliche Abrufen des Elements unter der Haube erforderlich macht . Es ist dem Beispiel des OP eigentlich ziemlich ähnlich: Wenn Sie die Namen der Methoden ignorieren, hasNext()ist es ähnlich isValid()und next()ähnlich current().
Mike Strobel
Ich meinte Modell im Sinne der DIESE Frage. Ok, sie sind nicht identisch, aber beide basieren auf dem nächsten Element. Wenn Sie in beiden iterieren, müssen Sie beim Start zum nächsten Element wechseln, da beide vor der eigentlichen Sammlung beginnen.
Greenoldman
Ich behaupte, dass das Java-Modell dem "this-iterator" -Modell in der Frage, wie es tatsächlich funktioniert, sehr ähnlich ist . Ich behaupte, dass die Verhaltensimplikationen zwischen "diesem Iterator" und "nächsten Iterator" wichtiger sind als die Namenskonventionen, und das Java-Modell hat ähnliche Verhaltensimplikationen wie Ihr Beispiel "dieser Iterator". Obwohl meine Antwort möglicherweise in ein Gebiet verirrt ist, das das OP bei der Beantwortung der Frage nicht berücksichtigt hatte, glaube ich nicht, dass meine Antwort dadurch weniger gültig wird.
Mike Strobel
7

Wenn eine Berechnung erforderlich ist, um jeden Wert zu berechnen, der den nächsten Wert erhalten muss, bevor Sie nach dem ersten Wert fragen, können Sie die Verarbeitung dieses ersten Werts über die Konstruktion des Iterators hinaus verschieben.

Beispielsweise könnte der Iterator eine Abfrage darstellen, die noch nicht ausgeführt wurde. Wenn das erste Element angefordert wird, wird die Datenbank nach den Ergebnissen abgefragt. Unter Verwendung Ihres vorgeschlagenen Entwurfs müsste diese Berechnung entweder beim Aufbau des Iterators (was es schwieriger macht, die Ausführung zu verschieben) oder beim Aufrufen durchgeführt IsValidwerden. In diesem Fall handelt es sich tatsächlich um eine Fehlbezeichnung, da der Aufruf IsValidtatsächlich den nächsten Wert erhalten würde.

Servieren
quelle
Gute Antwort. Du hast den gleichen Punkt wie ich erreicht, mich aber um ein paar Sekunden geschlagen. Habe eine positive Bewertung :).
Mike Strobel
1
Vielen Dank für die Antwort, aber ich glaube, es ist falsch. Um zu überprüfen, ob der Iterator gültig ist, müssen Sie nicht das reale Element abrufen, sondern nur die Position des Iterators. IOW hat die gleichen Auswirkungen wie hasNextnur sein Zustand über den aktuellen Zustand, nicht der zukünftige. Oder nehmen Sie eine andere Ansicht - vergleichen Sie diese mit C ++ - Iteratoren. Sie überprüfen, ob der Status gültig ist, (current!=end)ohne die tatsächlichen Daten zu berühren.
Greenoldman
PS. Lassen Sie uns Ihr Szenario festhalten - ich möchte die Ergebnisse ausdrucken, also muss ich zu hasNextBeginn aufrufen , hier wird die Abfrage ausgeführt, Daten werden abgerufen. Im zweiten Modell, das ich aufrufe isValid, wird hier die Abfrage ausgeführt. Der Punkt der Abfrageausführung ist also der gleiche.
Greenoldman
@greenoldman Wenn Sie also anrufen, IsValidwas Sie tatsächlich tun, gehen Sie und erhalten den nächsten Wert. Dies bedeutet, dass es genau das Gleiche tut wie der vorhandene Iterator (er hat nicht von Anfang an den aktuellen Wert, ohne dass eine Methode aufgerufen werden muss, um ihn abzurufen). Er hat nur einen falsch führenden Namen.
Servy
1
Genau. Das Modell "Nächster Iterator" ist dem Modell "Dieser Iterator" vorzuziehen, da das Modell "Dieser Iterator" häufig eine Lüge ist: Möglicherweise müssen Sie das nächste Element vorab abrufen, um den Status des Iterators zu überprüfen .
Mike Strobel