Wenn Sie fragen, wie es RecursiveIteratorIteratorfunktioniert, haben Sie bereits verstanden, wie es IteratorIteratorfunktioniert? Ich meine, es ist im Grunde das gleiche, nur die Schnittstelle, die von den beiden verwendet wird, ist unterschiedlich. Und interessieren Sie sich mehr für einige Beispiele oder möchten Sie den Unterschied der zugrunde liegenden C-Code-Implementierung sehen?
hakre
@ Gordon Ich war nicht sicher, wie eine einzelne foreach-Schleife alle Elemente in der Baumstruktur durchlaufen kann
varuog
@hakra Ich versuche jetzt, alle eingebauten Schnittstellen sowie die Implementierung von Spl-Schnittstellen und Iteratoren zu untersuchen. Ich war interessiert zu wissen, wie es im Hintergrund mit Forach-Schleife mit einigen Beispielen funktioniert.
Varuog
@hakre Sie sind beide tatsächlich sehr unterschiedlich. IteratorIteratorKarten Iteratorund IteratorAggregatein eine Iterator, wo REcusiveIteratorIteratorverwendet wird, um rekusiv aRecursiveIterator
Adam
Antworten:
247
RecursiveIteratorIteratorist eine konkrete Iteratorimplementierende Baumdurchquerung . Es ermöglicht einem Programmierer, ein Containerobjekt zu durchlaufen, das die RecursiveIteratorSchnittstelle implementiert. Die allgemeinen Prinzipien, Typen, Semantiken und Muster von Iteratoren finden Sie unter Iterator in Wikipedia .
Im Gegensatz dazu IteratorIteratorhandelt es sich um eine konkrete Iteratorimplementierende Objektdurchquerung in linearer Reihenfolge (und standardmäßig wird jede Art von Objekt Traversablein ihrem Konstruktor akzeptiert ). Das RecursiveIteratorIteratorerlaubt das Schleifen über alle Knoten in einem geordneten Baum von Objekten, und sein Konstruktor benötigt a RecursiveIterator.
Kurz gesagt: RecursiveIteratorIteratorErmöglicht das Durchlaufen eines Baums und IteratorIteratordas Durchlaufen einer Liste. Ich zeige das mit einigen Codebeispielen unten in Kürze.
Technisch funktioniert dies, indem die Linearität durch Durchlaufen aller untergeordneten Knoten eines Knotens (falls vorhanden) durchbrochen wird. Dies ist möglich, weil per Definition alle Kinder eines Knotens wieder a sind RecursiveIterator. Die oberste Ebene Iteratorstapelt dann intern die verschiedenen RecursiveIterators nach ihrer Tiefe und behält einen Zeiger auf das aktuell aktive Sub Iteratorfür die Durchquerung.
Dadurch können alle Knoten eines Baums besucht werden.
Die zugrunde liegenden Prinzipien sind dieselben wie bei IteratorIterator: Eine Schnittstelle gibt den Typ der Iteration an und die Basisiteratorklasse ist die Implementierung dieser Semantik. Vergleichen Sie mit den folgenden Beispielen, denn bei linearen Schleifen foreachdenken Sie normalerweise nicht viel über die Implementierungsdetails nach, es sei denn, Sie müssen eine neue definieren Iterator(z. B. wenn ein konkreter Typ selbst nicht implementiert wird Traversable).
Für rekursive Traversal - es sei denn , Sie nicht über einen vordefinierten verwenden , Traversaldass bereits rekursiven Traversal Iteration - Sie normalerweise benötigen die bestehende zu instanziiert RecursiveIteratorIteratorIteration oder sogar schreiben eine rekursive Traversal Iteration , die eine ist TraversableIhre eigene diese Art von Traversal Iteration mit haben foreach.
Tipp: Sie haben wahrscheinlich weder das eine noch das andere selbst implementiert. Dies ist möglicherweise eine wertvolle Maßnahme für Ihre praktische Erfahrung mit den Unterschieden, die sie aufweisen. Sie finden einen DIY-Vorschlag am Ende der Antwort.
Technische Unterschiede kurz:
Während IteratorIteratorjede dauert Traversablefür lineare Traversal, RecursiveIteratorIteratorbenötigt eine präziser RecursiveIteratorüber einen Baum - Schleife.
Wenn IteratorIteratordas Haupt- IteratorVia verfügbar gemacht wird getInnerIerator(), RecursiveIteratorIteratorwird das aktuell aktive Sub Iteratornur über diese Methode bereitgestellt.
Während er IteratorIteratorsich so etwas wie Eltern oder Kinder überhaupt nicht bewusst ist, RecursiveIteratorIteratorweiß er auch, wie man Kinder bekommt und durchquert.
IteratorIteratorbenötigt keinen Stapel von Iteratoren, RecursiveIteratorIteratorhat einen solchen Stapel und kennt den aktiven Unteriterator.
Wo IteratorIteratorhat seine Reihenfolge aufgrund der Linearität und keine Wahl, RecursiveIteratorIteratorhat eine Wahl für die weitere Durchquerung und muss pro Knoten entscheiden (entschieden über Modus proRecursiveIteratorIterator ).
RecursiveIteratorIteratorhat mehr Methoden als IteratorIterator.
Zusammenfassend: RecursiveIteratorist eine konkrete Art der Iteration (Schleife über einen Baum), die mit ihren eigenen Iteratoren arbeitet, nämlich RecursiveIterator. Das ist das gleiche Grundprinzip wie bei IteratorIerator, aber die Art der Iteration ist unterschiedlich (lineare Reihenfolge).
Idealerweise können Sie auch Ihr eigenes Set erstellen. Das einzige, was notwendig ist, ist, dass Ihr Iterator implementiert, Traversablewas über Iteratoroder möglich ist IteratorAggregate. Dann können Sie es mit verwenden foreach. Zum Beispiel eine Art rekursives Iterationsobjekt für die ternäre Baumdurchquerung zusammen mit der entsprechenden Iterationsschnittstelle für die Containerobjekte.
Lassen Sie uns einige Beispiele aus der Praxis betrachten, die nicht so abstrakt sind. Zwischen Schnittstellen, konkreten Iteratoren, Containerobjekten und Iterationssemantik ist dies möglicherweise keine so schlechte Idee.
Nehmen Sie als Beispiel eine Verzeichnisliste. Angenommen, Sie haben den folgenden Datei- und Verzeichnisbaum auf der Festplatte:
Während ein Iterator mit linearer Reihenfolge nur den Ordner und die Dateien der obersten Ebene durchläuft (eine einzelne Verzeichnisliste), durchläuft der rekursive Iterator auch die Unterordner und listet alle Ordner und Dateien auf (eine Verzeichnisliste mit Listen seiner Unterverzeichnisse):
Non-RecursiveRecursive======================[tree][tree]├ dirA ├ dirA
└ fileA │├ dirB
││└ fileD
│├ fileB
│└ fileC
└ fileA
Sie können dies leicht vergleichen, mit IteratorIteratordem keine Rekursion zum Durchlaufen des Verzeichnisbaums erfolgt. Und das, RecursiveIteratorIteratorwas in den Baum gelangen kann, wie die rekursive Auflistung zeigt.
Zunächst ein sehr einfaches Beispiel mit einem DirectoryIterator, das implementiert, Traversabledas es ermöglicht, darüber foreachzu iterieren :
Die beispielhafte Ausgabe für die obige Verzeichnisstruktur lautet dann:
[tree]├.├..├ dirA
├ fileA
Wie Sie sehen, wird dies noch nicht verwendet IteratorIteratoroder RecursiveIteratorIterator. Stattdessen foreachfunktioniert nur die Verwendung auf der TraversableSchnittstelle.
Da foreachstandardmäßig nur der Iterationstyp mit dem Namen lineare Reihenfolge bekannt ist, möchten wir den Iterationstyp möglicherweise explizit angeben. Auf den ersten Blick mag es zu ausführlich erscheinen, aber zu Demonstrationszwecken (und um den Unterschied RecursiveIteratorIteratorspäter besser sichtbar zu machen ) können Sie den linearen Iterationstyp angeben, indem Sie den IteratorIteratorIterationstyp für die Verzeichnisliste explizit angeben :
$files =newIteratorIterator($dir);
echo "[$path]\n";foreach($files as $file){
echo " ├ $file\n";}
Dieses Beispiel ist fast identisch mit dem ersten. Der Unterschied besteht darin, dass $fileses sich nun um eine IteratorIteratorArt Iteration handelt für Traversable$dir:
$files =newIteratorIterator($dir);
Wie üblich wird der Iterationsvorgang ausgeführt durch foreach:
foreach($files as $file){
Die Ausgabe ist genau gleich. Was ist also anders? Anders ist das Objekt, das in der foreach. Im ersten Beispiel ist es ein, DirectoryIteratorim zweiten Beispiel ist es das IteratorIterator. Dies zeigt die Flexibilität, die Iteratoren haben: Sie können sie durch andere ersetzen, der darin enthaltene Code foreachfunktioniert einfach wie erwartet weiter.
Beginnen wir mit dem Abrufen der gesamten Liste, einschließlich der Unterverzeichnisse.
Da wir jetzt den Iterationstyp angegeben haben, sollten Sie ihn in einen anderen Iterationstyp ändern.
Wir wissen, dass wir jetzt den ganzen Baum durchqueren müssen, nicht nur die erste Ebene. Damit dies mit einem einfachen funktioniert, foreachbenötigen wir einen anderen Iteratortyp:RecursiveIteratorIterator . Und das kann man nur über Containerobjekte iterieren, die die RecursiveIteratorSchnittstelle haben .
Die Schnittstelle ist ein Vertrag. Jede Klasse, die es implementiert, kann zusammen mit dem verwendet werdenRecursiveIteratorIterator . Ein Beispiel für eine solche Klasse ist die RecursiveDirectoryIterator, die so etwas wie die rekursive Variante von ist DirectoryIterator.
Sehen wir uns ein erstes Codebeispiel an, bevor wir einen anderen Satz mit dem I-Wort schreiben:
$dir =newRecursiveDirectoryIterator($path);
echo "[$path]\n";foreach($dir as $file){
echo " ├ $file\n";}
Dieses dritte Beispiel ist fast identisch mit dem ersten, erzeugt jedoch eine andere Ausgabe:
[tree]├ tree\.
├ tree\..├ tree\dirA
├ tree\fileA
Okay, nicht so anders, der Dateiname enthält jetzt den Pfadnamen vorne, aber der Rest sieht auch ähnlich aus.
Wie das Beispiel zeigt, implementiert selbst das Verzeichnisobjekt die RecursiveIteratorSchnittstelle bereits. Dies reicht noch nicht aus, um foreachden gesamten Verzeichnisbaum zu durchlaufen. Hier RecursiveIteratorIteratorkommt das zum Tragen. Beispiel 4 zeigt wie:
$files =newRecursiveIteratorIterator($dir);
echo "[$path]\n";foreach($files as $file){
echo " ├ $file\n";}
Wenn Sie das Objekt RecursiveIteratorIteratoranstelle des vorherigen $dirObjekts verwenden, werden foreachalle Dateien und Verzeichnisse rekursiv durchlaufen. Dies listet dann alle Dateien auf, da der Typ der Objektiteration jetzt angegeben wurde:
Dies sollte bereits den Unterschied zwischen Flach- und Baumdurchquerung zeigen. Der RecursiveIteratorIteratorkann jede baumartige Struktur als Liste von Elementen durchlaufen. Da es mehr Informationen gibt (wie die Ebene, auf der die Iteration gerade stattfindet), ist es möglich, auf das Iteratorobjekt zuzugreifen, während Sie es durchlaufen, und beispielsweise die Ausgabe einzurücken:
Sicher, dies gewinnt keinen Schönheitswettbewerb, aber es zeigt, dass mit dem rekursiven Iterator mehr Informationen verfügbar sind als nur die lineare Reihenfolge von Schlüssel und Wert . Selbst foreachwenn diese Art von Linearität nur ausgedrückt werden kann, können Sie durch Zugriff auf den Iterator selbst mehr Informationen erhalten.
Ähnlich wie bei den Metainformationen gibt es auch verschiedene Möglichkeiten, den Baum zu durchlaufen und daher die Ausgabe zu ordnen. Dies ist der Modus vonRecursiveIteratorIterator und kann mit dem Konstruktor festgelegt werden.
Das nächste Beispiel zeigt RecursiveDirectoryIteratoran, dass die Punkteinträge ( .und ..) entfernt werden sollen, da wir sie nicht benötigen. Aber auch der Rekursionsmodus wird geändert, um das übergeordnete Element (das Unterverzeichnis) zuerst ( SELF_FIRST) vor die untergeordneten Elemente (die Dateien und Unterverzeichnisse im Unterverzeichnis) zu setzen:
Die Ausgabe zeigt jetzt die ordnungsgemäß aufgelisteten Unterverzeichniseinträge, wenn Sie sie mit der vorherigen Ausgabe vergleichen, waren diese nicht vorhanden:
Wenn Sie das mit der Standarddurchquerung vergleichen, sind all diese Dinge nicht verfügbar. Rekursive Iteration ist daher etwas komplexer, wenn Sie Ihren Kopf darum wickeln müssen. Sie ist jedoch einfach zu verwenden, da sie sich wie ein Iterator verhältforeach und fertig.
Ich denke, das sind genug Beispiele für eine Antwort. Den vollständigen Quellcode sowie ein Beispiel für die Anzeige gut aussehender ASCII-Bäume finden Sie in dieser Übersicht: https://gist.github.com/3599532
Machen Sie es sich selbst: Machen Sie die RecursiveTreeIteratorArbeit Zeile für Zeile.
Beispiel 5 hat gezeigt, dass Metainformationen über den Status des Iterators verfügbar sind. Dies wurde jedoch in der foreachIteration gezielt demonstriert . Im wirklichen Leben gehört dies natürlich in die RecursiveIterator.
Ein besseres Beispiel ist das RecursiveTreeIterator, es kümmert sich um das Einrücken, Präfixieren und so weiter. Siehe folgendes Codefragment:
In Kombination mit a RecursiveDirectoryIteratorwird der gesamte Pfadname und nicht nur der Dateiname angezeigt. Der Rest sieht gut aus. Dies liegt daran, dass die Dateinamen von generiert werden SplFileInfo. Diese sollten stattdessen als Basisname angezeigt werden. Die gewünschte Ausgabe ist die folgende:
Erstellen Sie eine Dekorationsklasse, die RecursiveTreeIteratoranstelle von verwendet werden kann RecursiveDirectoryIterator. Es sollte den Basisnamen des aktuellen SplFileInfoanstelle des Pfadnamens enthalten. Das endgültige Codefragment könnte dann folgendermaßen aussehen:
Diese Fragmente $unicodeTreePrefixsind Teil des Kerns im Anhang: Machen Sie es sich selbst: Machen Sie die RecursiveTreeIteratorArbeit Zeile für Zeile. .
Es beantwortet nicht die gestellten Fragen, enthält sachliche Fehler und verfehlt Kernpunkte, wenn Sie mit dem Anpassen der Iteration fortfahren. Alles in allem sieht es nach einem schlechten Versuch aus, die Prämie für ein Thema zu erhalten, über das Sie nicht viel wissen oder das Sie in diesem Fall nicht in eine Antwort auf die gestellten Fragen umwandeln können.
salathe
2
Nun, was meine "Warum" -Frage nicht beantwortet, Sie machen einfach mehr Worte, ohne viel zu sagen. Vielleicht fangen Sie tatsächlich mit einem Fehler an, der zählt? Zeigen Sie darauf, halten Sie es nicht geheim.
hakre
Der allererste Satz ist falsch: "RecursiveIteratorIterator ist ein IteratorIterator, der ... unterstützt", dies ist nicht wahr.
salathe
1
@salathe: Tank dich für dein Feedback. Ich habe die Antwort bearbeitet, um sie zu adressieren. Der erste Satz war in der Tat falsch und unvollständig. Ich habe die konkreten Implementierungsdetails immer noch weggelassen, RecursiveIteratorIteratorda dies mit anderen Typen gemeinsam ist, aber ich habe einige technische Informationen darüber gegeben, wie es tatsächlich funktioniert. Die Beispiele, die ich denke, zeigen gut die Unterschiede: Die Art der Iteration ist der Hauptunterschied zwischen den beiden. Keine Ahnung, wenn Sie die Art der Iteration kaufen, prägen Sie sie ein wenig anders, aber meiner Meinung nach ist es mit den semantischen Iterationstypen nicht einfach.
hakre
1
Der erste Teil ist etwas verbessert, aber sobald Sie sich den Beispielen zuwenden, werden immer noch sachliche Ungenauigkeiten festgestellt. Wenn Sie die Antwort nach der horizontalen Regel beschneiden, wird sie erheblich verbessert.
salathe
31
Was ist der Unterschied von IteratorIteratorund RecursiveIteratorIterator?
Um den Unterschied zwischen diesen beiden Iteratoren zu verstehen, muss man zunächst ein wenig über die verwendeten Namenskonventionen und die Bedeutung von "rekursiven" Iteratoren verstehen.
Rekursive und nicht rekursive Iteratoren
PHP hat nicht "rekursive" Iteratoren wie ArrayIteratorund FilesystemIterator. Es gibt auch "rekursive" Iteratoren wie RecursiveArrayIteratorundRecursiveDirectoryIterator . Letztere verfügen über Methoden, mit denen sie aufgeschlüsselt werden können, erstere nicht.
Wenn Instanzen dieser Iteratoren selbst durchlaufen werden, selbst die rekursiven, kommen die Werte nur von der "obersten" Ebene, selbst wenn sie ein verschachteltes Array oder Verzeichnis mit Unterverzeichnissen durchlaufen.
Die rekursiven Iteratoren implementieren rekursives Verhalten (via hasChildren(), getChildren()), nutzen es jedoch nicht aus.
Es könnte besser sein, sich die rekursiven Iteratoren als "rekursive" Iteratoren vorzustellen, sie haben die Fähigkeit rekursiv iteriert werden , sondern nur eine Instanz einer dieser Klassen iterieren wird das nicht tun. Lesen Sie weiter, um das rekursive Verhalten auszunutzen.
RecursiveIteratorIterator
Hier RecursiveIteratorIteratorkommt der ins Spiel. Es hat das Wissen, wie man die "rekursiven" Iteratoren so aufruft, dass sie in einer normalen, flachen Schleife in die Struktur eindringen. Es setzt das rekursive Verhalten in die Tat um. Es erledigt im Wesentlichen die Arbeit, über jeden der Werte im Iterator zu gehen, zu prüfen, ob es "Kinder" gibt, in die man zurückgreifen kann oder nicht, und in diese Sammlungen von Kindern hinein- und herauszugehen. Sie stecken eine Instanz von RecursiveIteratorIteratorin ein foreach und es taucht in die Struktur ein, so dass Sie nicht müssen.
Wenn das RecursiveIteratorIteratornicht verwendet wurde, müssten Sie Ihre eigenen rekursiven Schleifen schreiben, um das rekursive Verhalten auszunutzen, gegen die "rekursiven" Iteratoren zu prüfen hasChildren()und zu verwenden getChildren().
Das ist also ein kurzer Überblick darüber RecursiveIteratorIterator, wie es sich unterscheidet von IteratorIterator? Nun, Sie stellen im Grunde die gleiche Frage wie Was ist der Unterschied zwischen einem Kätzchen und einem Baum? Nur weil beide in derselben Enzyklopädie (oder im Handbuch für die Iteratoren) erscheinen, heißt das nicht, dass Sie zwischen den beiden verwechselt werden sollten.
IteratorIterator
Die Aufgabe von IteratorIteratorist es, ein beliebiges TraversableObjekt zu nehmen und es so zu verpacken, dass es der IteratorSchnittstelle entspricht. Eine Verwendung hierfür besteht darin, dann iterator-spezifisches Verhalten auf das Nicht-Iterator-Objekt anwenden zu können.
Um ein praktisches Beispiel zu geben, die DatePeriodKlasse ist Traversableaber keine Iterator. Als solches können wir seine Werte mit foreach()durchlaufen, aber keine anderen Dinge tun, die wir normalerweise mit einem Iterator tun würden, wie z. B. Filtern.
AUFGABE : Montags, mittwochs und freitags der nächsten vier Wochen.
Ja, dies ist trivial, indem Sie foreachüber DatePeriodund if()innerhalb der Schleife arbeiten. Aber darum geht es in diesem Beispiel nicht!
$period =newDatePeriod(newDateTime,newDateInterval('P1D'),28);
$dates =newCallbackFilterIterator($period,function($date){return in_array($date->format('l'), array('Monday','Wednesday','Friday'));});foreach($dates as $date){…}
Das obige Snippet funktioniert nicht, da es CallbackFilterIteratoreine Instanz einer Klasse erwartet, die die IteratorSchnittstelle implementiert , was DatePeriodjedoch nicht der Fall ist. Da dies jedoch der TraversableFall ist , können wir diese Anforderung leicht erfüllen, indem wir verwenden IteratorIterator.
$period =newIteratorIterator(newDatePeriod(…));
Wie Sie sehen können, hat dies überhaupt nichts mit Iteration über Iteratorklassen oder Rekursion zu tun, und darin liegt der Unterschied zwischen IteratorIteratorund RecursiveIteratorIterator.
Zusammenfassung
RecursiveIteraratorIteratordient zum Iterieren über einen RecursiveIterator("rekursiven" Iterator), wobei das verfügbare rekursive Verhalten ausgenutzt wird.
IteratorIteratordient zum Anwenden von IteratorVerhalten auf Nicht-Iterator- TraversableObjekte.
Ist das nicht IteratorIteratornur die Standardart der linearen Ordnungsdurchquerung für TraversableObjekte? Diejenigen, die ohne es verwendet werden könnten, so foreachwie es ist? Und noch weiter, ist nicht RecursiveIteratorimmer ein Traversableund daher nicht nurIteratorIterator sondern auch RecursiveIteratorIteratorimmer "zum Anwenden von IteratorVerhalten auf nicht iteratorische, durchquerbare Objekte" ? (Ich würde jetzt sagen, foreachwendet den Iterationstyp über das Iteratorobjekt auf Containerobjekte an, die eine Iterator-Typ-Schnittstelle implementieren, so dass dies immer Iterator-Container-Objekte sind. Traversable)
hakre
Wie meine Antwort besagt, IteratorIterator handelt es sich bei einer Klasse um das Umschließen von TraversableObjekten in eine Iterator. Nichts mehr . Sie scheinen den Begriff allgemeiner anzuwenden.
salathe
Scheinbar informative Antwort. Eine Frage: Würde RecursiveIteratorIterator nicht auch Objekte umbrechen, damit sie auch Zugriff auf das Iterator-Verhalten haben? Der einzige Unterschied zwischen den beiden wäre, dass der RecursiveIteratorIterator einen Drilldown durchführen kann, während der IteratorIterator dies nicht kann.
Mike Purcell
@salathe, wissen Sie, warum der rekursive Iterator (RecursiveDirectoryIterator) das Verhalten von hasChildren (), getChildren () nicht implementiert?
bis
7
+1 für "rekursiv". Der Name hat mich lange in die Irre geführt, weil dieRecursive in RecursiveIteratorVerhalten impliziert, während ein passenderer Name einer gewesen wäre, der Fähigkeiten beschreibt, wie RecursibleIterator.
Ziege
0
Wenn mit verwendet iterator_to_array(), RecursiveIteratorIteratorwird rekursiv die Array gehen alle Werte zu finden. Dies bedeutet, dass das ursprüngliche Array abgeflacht wird.
IteratorIterator behält die ursprüngliche hierarchische Struktur bei.
Dieses Beispiel zeigt Ihnen deutlich den Unterschied:
Das ist völlig irreführend. new IteratorIterator(new ArrayIterator($array))ist gleichbedeutend damit new ArrayIterator($array), dass das Äußere IteratorIteratornichts tut. Darüber hinaus hat die Abflachung der Ausgabe nichts damit zu tun iterator_to_array- sie konvertiert lediglich den Iterator in ein Array. Die Abflachung ist eine Eigenschaft des Weges RecursiveArrayIterator, den sein innerer Iterator geht.
Quolonel Fragen
0
RecursiveDirectoryIterator zeigt den gesamten Pfadnamen und nicht nur den Dateinamen an. Der Rest sieht gut aus. Dies liegt daran, dass die Dateinamen von SplFileInfo generiert werden. Diese sollten stattdessen als Basisname angezeigt werden. Die gewünschte Ausgabe ist die folgende:
RecursiveIteratorIterator
funktioniert, haben Sie bereits verstanden, wie esIteratorIterator
funktioniert? Ich meine, es ist im Grunde das gleiche, nur die Schnittstelle, die von den beiden verwendet wird, ist unterschiedlich. Und interessieren Sie sich mehr für einige Beispiele oder möchten Sie den Unterschied der zugrunde liegenden C-Code-Implementierung sehen?IteratorIterator
KartenIterator
undIteratorAggregate
in eineIterator
, woREcusiveIteratorIterator
verwendet wird, um rekusiv aRecursiveIterator
Antworten:
RecursiveIteratorIterator
ist eine konkreteIterator
implementierende Baumdurchquerung . Es ermöglicht einem Programmierer, ein Containerobjekt zu durchlaufen, das dieRecursiveIterator
Schnittstelle implementiert. Die allgemeinen Prinzipien, Typen, Semantiken und Muster von Iteratoren finden Sie unter Iterator in Wikipedia .Im Gegensatz dazu
IteratorIterator
handelt es sich um eine konkreteIterator
implementierende Objektdurchquerung in linearer Reihenfolge (und standardmäßig wird jede Art von ObjektTraversable
in ihrem Konstruktor akzeptiert ). DasRecursiveIteratorIterator
erlaubt das Schleifen über alle Knoten in einem geordneten Baum von Objekten, und sein Konstruktor benötigt aRecursiveIterator
.Kurz gesagt:
RecursiveIteratorIterator
Ermöglicht das Durchlaufen eines Baums undIteratorIterator
das Durchlaufen einer Liste. Ich zeige das mit einigen Codebeispielen unten in Kürze.Technisch funktioniert dies, indem die Linearität durch Durchlaufen aller untergeordneten Knoten eines Knotens (falls vorhanden) durchbrochen wird. Dies ist möglich, weil per Definition alle Kinder eines Knotens wieder a sind
RecursiveIterator
. Die oberste EbeneIterator
stapelt dann intern die verschiedenenRecursiveIterator
s nach ihrer Tiefe und behält einen Zeiger auf das aktuell aktive SubIterator
für die Durchquerung.Dadurch können alle Knoten eines Baums besucht werden.
Die zugrunde liegenden Prinzipien sind dieselben wie bei
IteratorIterator
: Eine Schnittstelle gibt den Typ der Iteration an und die Basisiteratorklasse ist die Implementierung dieser Semantik. Vergleichen Sie mit den folgenden Beispielen, denn bei linearen Schleifenforeach
denken Sie normalerweise nicht viel über die Implementierungsdetails nach, es sei denn, Sie müssen eine neue definierenIterator
(z. B. wenn ein konkreter Typ selbst nicht implementiert wirdTraversable
).Für rekursive Traversal - es sei denn , Sie nicht über einen vordefinierten verwenden ,
Traversal
dass bereits rekursiven Traversal Iteration - Sie normalerweise benötigen die bestehende zu instanziiertRecursiveIteratorIterator
Iteration oder sogar schreiben eine rekursive Traversal Iteration , die eine istTraversable
Ihre eigene diese Art von Traversal Iteration mit habenforeach
.Technische Unterschiede kurz:
IteratorIterator
jede dauertTraversable
für lineare Traversal,RecursiveIteratorIterator
benötigt eine präziserRecursiveIterator
über einen Baum - Schleife.IteratorIterator
das Haupt-Iterator
Via verfügbar gemacht wirdgetInnerIerator()
,RecursiveIteratorIterator
wird das aktuell aktive SubIterator
nur über diese Methode bereitgestellt.IteratorIterator
sich so etwas wie Eltern oder Kinder überhaupt nicht bewusst ist,RecursiveIteratorIterator
weiß er auch, wie man Kinder bekommt und durchquert.IteratorIterator
benötigt keinen Stapel von Iteratoren,RecursiveIteratorIterator
hat einen solchen Stapel und kennt den aktiven Unteriterator.IteratorIterator
hat seine Reihenfolge aufgrund der Linearität und keine Wahl,RecursiveIteratorIterator
hat eine Wahl für die weitere Durchquerung und muss pro Knoten entscheiden (entschieden über Modus proRecursiveIteratorIterator
).RecursiveIteratorIterator
hat mehr Methoden alsIteratorIterator
.Zusammenfassend:
RecursiveIterator
ist eine konkrete Art der Iteration (Schleife über einen Baum), die mit ihren eigenen Iteratoren arbeitet, nämlichRecursiveIterator
. Das ist das gleiche Grundprinzip wie beiIteratorIerator
, aber die Art der Iteration ist unterschiedlich (lineare Reihenfolge).Idealerweise können Sie auch Ihr eigenes Set erstellen. Das einzige, was notwendig ist, ist, dass Ihr Iterator implementiert,
Traversable
was überIterator
oder möglich istIteratorAggregate
. Dann können Sie es mit verwendenforeach
. Zum Beispiel eine Art rekursives Iterationsobjekt für die ternäre Baumdurchquerung zusammen mit der entsprechenden Iterationsschnittstelle für die Containerobjekte.Lassen Sie uns einige Beispiele aus der Praxis betrachten, die nicht so abstrakt sind. Zwischen Schnittstellen, konkreten Iteratoren, Containerobjekten und Iterationssemantik ist dies möglicherweise keine so schlechte Idee.
Nehmen Sie als Beispiel eine Verzeichnisliste. Angenommen, Sie haben den folgenden Datei- und Verzeichnisbaum auf der Festplatte:
Während ein Iterator mit linearer Reihenfolge nur den Ordner und die Dateien der obersten Ebene durchläuft (eine einzelne Verzeichnisliste), durchläuft der rekursive Iterator auch die Unterordner und listet alle Ordner und Dateien auf (eine Verzeichnisliste mit Listen seiner Unterverzeichnisse):
Sie können dies leicht vergleichen, mit
IteratorIterator
dem keine Rekursion zum Durchlaufen des Verzeichnisbaums erfolgt. Und das,RecursiveIteratorIterator
was in den Baum gelangen kann, wie die rekursive Auflistung zeigt.Zunächst ein sehr einfaches Beispiel mit einem
DirectoryIterator
, das implementiert,Traversable
das es ermöglicht, darüberforeach
zu iterieren :Die beispielhafte Ausgabe für die obige Verzeichnisstruktur lautet dann:
Wie Sie sehen, wird dies noch nicht verwendet
IteratorIterator
oderRecursiveIteratorIterator
. Stattdessenforeach
funktioniert nur die Verwendung auf derTraversable
Schnittstelle.Da
foreach
standardmäßig nur der Iterationstyp mit dem Namen lineare Reihenfolge bekannt ist, möchten wir den Iterationstyp möglicherweise explizit angeben. Auf den ersten Blick mag es zu ausführlich erscheinen, aber zu Demonstrationszwecken (und um den UnterschiedRecursiveIteratorIterator
später besser sichtbar zu machen ) können Sie den linearen Iterationstyp angeben, indem Sie denIteratorIterator
Iterationstyp für die Verzeichnisliste explizit angeben :Dieses Beispiel ist fast identisch mit dem ersten. Der Unterschied besteht darin, dass
$files
es sich nun um eineIteratorIterator
Art Iteration handelt fürTraversable
$dir
:Wie üblich wird der Iterationsvorgang ausgeführt durch
foreach
:Die Ausgabe ist genau gleich. Was ist also anders? Anders ist das Objekt, das in der
foreach
. Im ersten Beispiel ist es ein,DirectoryIterator
im zweiten Beispiel ist es dasIteratorIterator
. Dies zeigt die Flexibilität, die Iteratoren haben: Sie können sie durch andere ersetzen, der darin enthaltene Codeforeach
funktioniert einfach wie erwartet weiter.Beginnen wir mit dem Abrufen der gesamten Liste, einschließlich der Unterverzeichnisse.
Da wir jetzt den Iterationstyp angegeben haben, sollten Sie ihn in einen anderen Iterationstyp ändern.
Wir wissen, dass wir jetzt den ganzen Baum durchqueren müssen, nicht nur die erste Ebene. Damit dies mit einem einfachen funktioniert,
foreach
benötigen wir einen anderen Iteratortyp:RecursiveIteratorIterator
. Und das kann man nur über Containerobjekte iterieren, die dieRecursiveIterator
Schnittstelle haben .Die Schnittstelle ist ein Vertrag. Jede Klasse, die es implementiert, kann zusammen mit dem verwendet werden
RecursiveIteratorIterator
. Ein Beispiel für eine solche Klasse ist dieRecursiveDirectoryIterator
, die so etwas wie die rekursive Variante von istDirectoryIterator
.Sehen wir uns ein erstes Codebeispiel an, bevor wir einen anderen Satz mit dem I-Wort schreiben:
Dieses dritte Beispiel ist fast identisch mit dem ersten, erzeugt jedoch eine andere Ausgabe:
Okay, nicht so anders, der Dateiname enthält jetzt den Pfadnamen vorne, aber der Rest sieht auch ähnlich aus.
Wie das Beispiel zeigt, implementiert selbst das Verzeichnisobjekt die
RecursiveIterator
Schnittstelle bereits. Dies reicht noch nicht aus, umforeach
den gesamten Verzeichnisbaum zu durchlaufen. HierRecursiveIteratorIterator
kommt das zum Tragen. Beispiel 4 zeigt wie:Wenn Sie das Objekt
RecursiveIteratorIterator
anstelle des vorherigen$dir
Objekts verwenden, werdenforeach
alle Dateien und Verzeichnisse rekursiv durchlaufen. Dies listet dann alle Dateien auf, da der Typ der Objektiteration jetzt angegeben wurde:Dies sollte bereits den Unterschied zwischen Flach- und Baumdurchquerung zeigen. Der
RecursiveIteratorIterator
kann jede baumartige Struktur als Liste von Elementen durchlaufen. Da es mehr Informationen gibt (wie die Ebene, auf der die Iteration gerade stattfindet), ist es möglich, auf das Iteratorobjekt zuzugreifen, während Sie es durchlaufen, und beispielsweise die Ausgabe einzurücken:Und Ausgabe von Beispiel 5 :
Sicher, dies gewinnt keinen Schönheitswettbewerb, aber es zeigt, dass mit dem rekursiven Iterator mehr Informationen verfügbar sind als nur die lineare Reihenfolge von Schlüssel und Wert . Selbst
foreach
wenn diese Art von Linearität nur ausgedrückt werden kann, können Sie durch Zugriff auf den Iterator selbst mehr Informationen erhalten.Ähnlich wie bei den Metainformationen gibt es auch verschiedene Möglichkeiten, den Baum zu durchlaufen und daher die Ausgabe zu ordnen. Dies ist der Modus von
RecursiveIteratorIterator
und kann mit dem Konstruktor festgelegt werden.Das nächste Beispiel zeigt
RecursiveDirectoryIterator
an, dass die Punkteinträge (.
und..
) entfernt werden sollen, da wir sie nicht benötigen. Aber auch der Rekursionsmodus wird geändert, um das übergeordnete Element (das Unterverzeichnis) zuerst (SELF_FIRST
) vor die untergeordneten Elemente (die Dateien und Unterverzeichnisse im Unterverzeichnis) zu setzen:Die Ausgabe zeigt jetzt die ordnungsgemäß aufgelisteten Unterverzeichniseinträge, wenn Sie sie mit der vorherigen Ausgabe vergleichen, waren diese nicht vorhanden:
Der Rekursionsmodus steuert daher, was und wann ein Brach oder Blatt im Baum zurückgegeben wird, für das Verzeichnisbeispiel:
LEAVES_ONLY
(Standard): Nur Listendateien, keine Verzeichnisse.SELF_FIRST
(oben): Verzeichnis auflisten und dann die Dateien dort.CHILD_FIRST
(ohne Beispiel): Listet zuerst die Dateien im Unterverzeichnis und dann im Verzeichnis auf.Ausgabe von Beispiel 5 mit den beiden anderen Modi:
Wenn Sie das mit der Standarddurchquerung vergleichen, sind all diese Dinge nicht verfügbar. Rekursive Iteration ist daher etwas komplexer, wenn Sie Ihren Kopf darum wickeln müssen. Sie ist jedoch einfach zu verwenden, da sie sich wie ein Iterator verhält
foreach
und fertig.Ich denke, das sind genug Beispiele für eine Antwort. Den vollständigen Quellcode sowie ein Beispiel für die Anzeige gut aussehender ASCII-Bäume finden Sie in dieser Übersicht: https://gist.github.com/3599532
Beispiel 5 hat gezeigt, dass Metainformationen über den Status des Iterators verfügbar sind. Dies wurde jedoch in der
foreach
Iteration gezielt demonstriert . Im wirklichen Leben gehört dies natürlich in dieRecursiveIterator
.Ein besseres Beispiel ist das
RecursiveTreeIterator
, es kümmert sich um das Einrücken, Präfixieren und so weiter. Siehe folgendes Codefragment:Das
RecursiveTreeIterator
soll Zeile für Zeile arbeiten, die Ausgabe ist ziemlich einfach mit einem kleinen Problem:In Kombination mit a
RecursiveDirectoryIterator
wird der gesamte Pfadname und nicht nur der Dateiname angezeigt. Der Rest sieht gut aus. Dies liegt daran, dass die Dateinamen von generiert werdenSplFileInfo
. Diese sollten stattdessen als Basisname angezeigt werden. Die gewünschte Ausgabe ist die folgende:Erstellen Sie eine Dekorationsklasse, die
RecursiveTreeIterator
anstelle von verwendet werden kannRecursiveDirectoryIterator
. Es sollte den Basisnamen des aktuellenSplFileInfo
anstelle des Pfadnamens enthalten. Das endgültige Codefragment könnte dann folgendermaßen aussehen:Diese Fragmente
$unicodeTreePrefix
sind Teil des Kerns im Anhang: Machen Sie es sich selbst: Machen Sie dieRecursiveTreeIterator
Arbeit Zeile für Zeile. .quelle
RecursiveIteratorIterator
da dies mit anderen Typen gemeinsam ist, aber ich habe einige technische Informationen darüber gegeben, wie es tatsächlich funktioniert. Die Beispiele, die ich denke, zeigen gut die Unterschiede: Die Art der Iteration ist der Hauptunterschied zwischen den beiden. Keine Ahnung, wenn Sie die Art der Iteration kaufen, prägen Sie sie ein wenig anders, aber meiner Meinung nach ist es mit den semantischen Iterationstypen nicht einfach.Um den Unterschied zwischen diesen beiden Iteratoren zu verstehen, muss man zunächst ein wenig über die verwendeten Namenskonventionen und die Bedeutung von "rekursiven" Iteratoren verstehen.
Rekursive und nicht rekursive Iteratoren
PHP hat nicht "rekursive" Iteratoren wie
ArrayIterator
undFilesystemIterator
. Es gibt auch "rekursive" Iteratoren wieRecursiveArrayIterator
undRecursiveDirectoryIterator
. Letztere verfügen über Methoden, mit denen sie aufgeschlüsselt werden können, erstere nicht.Wenn Instanzen dieser Iteratoren selbst durchlaufen werden, selbst die rekursiven, kommen die Werte nur von der "obersten" Ebene, selbst wenn sie ein verschachteltes Array oder Verzeichnis mit Unterverzeichnissen durchlaufen.
Die rekursiven Iteratoren implementieren rekursives Verhalten (via
hasChildren()
,getChildren()
), nutzen es jedoch nicht aus.Es könnte besser sein, sich die rekursiven Iteratoren als "rekursive" Iteratoren vorzustellen, sie haben die Fähigkeit rekursiv iteriert werden , sondern nur eine Instanz einer dieser Klassen iterieren wird das nicht tun. Lesen Sie weiter, um das rekursive Verhalten auszunutzen.
RecursiveIteratorIterator
Hier
RecursiveIteratorIterator
kommt der ins Spiel. Es hat das Wissen, wie man die "rekursiven" Iteratoren so aufruft, dass sie in einer normalen, flachen Schleife in die Struktur eindringen. Es setzt das rekursive Verhalten in die Tat um. Es erledigt im Wesentlichen die Arbeit, über jeden der Werte im Iterator zu gehen, zu prüfen, ob es "Kinder" gibt, in die man zurückgreifen kann oder nicht, und in diese Sammlungen von Kindern hinein- und herauszugehen. Sie stecken eine Instanz vonRecursiveIteratorIterator
in ein foreach und es taucht in die Struktur ein, so dass Sie nicht müssen.Wenn das
RecursiveIteratorIterator
nicht verwendet wurde, müssten Sie Ihre eigenen rekursiven Schleifen schreiben, um das rekursive Verhalten auszunutzen, gegen die "rekursiven" Iteratoren zu prüfenhasChildren()
und zu verwendengetChildren()
.Das ist also ein kurzer Überblick darüber
RecursiveIteratorIterator
, wie es sich unterscheidet vonIteratorIterator
? Nun, Sie stellen im Grunde die gleiche Frage wie Was ist der Unterschied zwischen einem Kätzchen und einem Baum? Nur weil beide in derselben Enzyklopädie (oder im Handbuch für die Iteratoren) erscheinen, heißt das nicht, dass Sie zwischen den beiden verwechselt werden sollten.IteratorIterator
Die Aufgabe von
IteratorIterator
ist es, ein beliebigesTraversable
Objekt zu nehmen und es so zu verpacken, dass es derIterator
Schnittstelle entspricht. Eine Verwendung hierfür besteht darin, dann iterator-spezifisches Verhalten auf das Nicht-Iterator-Objekt anwenden zu können.Um ein praktisches Beispiel zu geben, die
DatePeriod
Klasse istTraversable
aber keineIterator
. Als solches können wir seine Werte mitforeach()
durchlaufen, aber keine anderen Dinge tun, die wir normalerweise mit einem Iterator tun würden, wie z. B. Filtern.AUFGABE : Montags, mittwochs und freitags der nächsten vier Wochen.
Ja, dies ist trivial, indem Sie
foreach
überDatePeriod
undif()
innerhalb der Schleife arbeiten. Aber darum geht es in diesem Beispiel nicht!Das obige Snippet funktioniert nicht, da es
CallbackFilterIterator
eine Instanz einer Klasse erwartet, die dieIterator
Schnittstelle implementiert , wasDatePeriod
jedoch nicht der Fall ist. Da dies jedoch derTraversable
Fall ist , können wir diese Anforderung leicht erfüllen, indem wir verwendenIteratorIterator
.Wie Sie sehen können, hat dies überhaupt nichts mit Iteration über Iteratorklassen oder Rekursion zu tun, und darin liegt der Unterschied zwischen
IteratorIterator
undRecursiveIteratorIterator
.Zusammenfassung
RecursiveIteraratorIterator
dient zum Iterieren über einenRecursiveIterator
("rekursiven" Iterator), wobei das verfügbare rekursive Verhalten ausgenutzt wird.IteratorIterator
dient zum Anwenden vonIterator
Verhalten auf Nicht-Iterator-Traversable
Objekte.quelle
IteratorIterator
nur die Standardart der linearen Ordnungsdurchquerung fürTraversable
Objekte? Diejenigen, die ohne es verwendet werden könnten, soforeach
wie es ist? Und noch weiter, ist nichtRecursiveIterator
immer einTraversable
und daher nicht nurIteratorIterator
sondern auchRecursiveIteratorIterator
immer "zum Anwenden vonIterator
Verhalten auf nicht iteratorische, durchquerbare Objekte" ? (Ich würde jetzt sagen,foreach
wendet den Iterationstyp über das Iteratorobjekt auf Containerobjekte an, die eine Iterator-Typ-Schnittstelle implementieren, so dass dies immer Iterator-Container-Objekte sind.Traversable
)IteratorIterator
handelt es sich bei einer Klasse um das Umschließen vonTraversable
Objekten in eineIterator
. Nichts mehr . Sie scheinen den Begriff allgemeiner anzuwenden.Recursive
inRecursiveIterator
Verhalten impliziert, während ein passenderer Name einer gewesen wäre, der Fähigkeiten beschreibt, wieRecursibleIterator
.Wenn mit verwendet
iterator_to_array()
,RecursiveIteratorIterator
wird rekursiv die Array gehen alle Werte zu finden. Dies bedeutet, dass das ursprüngliche Array abgeflacht wird.IteratorIterator
behält die ursprüngliche hierarchische Struktur bei.Dieses Beispiel zeigt Ihnen deutlich den Unterschied:
quelle
new IteratorIterator(new ArrayIterator($array))
ist gleichbedeutend damitnew ArrayIterator($array)
, dass das ÄußereIteratorIterator
nichts tut. Darüber hinaus hat die Abflachung der Ausgabe nichts damit zu tuniterator_to_array
- sie konvertiert lediglich den Iterator in ein Array. Die Abflachung ist eine Eigenschaft des WegesRecursiveArrayIterator
, den sein innerer Iterator geht.RecursiveDirectoryIterator zeigt den gesamten Pfadnamen und nicht nur den Dateinamen an. Der Rest sieht gut aus. Dies liegt daran, dass die Dateinamen von SplFileInfo generiert werden. Diese sollten stattdessen als Basisname angezeigt werden. Die gewünschte Ausgabe ist die folgende:
Ausgabe:
quelle