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-Recursive Recursive
============= =========
[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 :
$path = 'tree';
$dir = new DirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
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 = new IteratorIterator($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 = new IteratorIterator($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 = new RecursiveDirectoryIterator($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 = new RecursiveIteratorIterator($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:
[tree]
├ tree\.
├ tree\..
├ tree\dirA\.
├ tree\dirA\..
├ tree\dirA\dirB\.
├ tree\dirA\dirB\..
├ tree\dirA\dirB\fileD
├ tree\dirA\fileB
├ tree\dirA\fileC
├ tree\fileA
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:
echo "[$path]\n";
foreach ($files as $file) {
$indent = str_repeat(' ', $files->getDepth());
echo $indent, " ├ $file\n";
}
Und Ausgabe von Beispiel 5 :
[tree]
├ tree\.
├ tree\..
├ tree\dirA\.
├ tree\dirA\..
├ tree\dirA\dirB\.
├ tree\dirA\dirB\..
├ tree\dirA\dirB\fileD
├ tree\dirA\fileB
├ tree\dirA\fileC
├ tree\fileA
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:
$dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::SELF_FIRST);
echo "[$path]\n";
foreach ($files as $file) {
$indent = str_repeat(' ', $files->getDepth());
echo $indent, " ├ $file\n";
}
Die Ausgabe zeigt jetzt die ordnungsgemäß aufgelisteten Unterverzeichniseinträge, wenn Sie sie mit der vorherigen Ausgabe vergleichen, waren diese nicht vorhanden:
[tree]
├ tree\dirA
├ tree\dirA\dirB
├ tree\dirA\dirB\fileD
├ tree\dirA\fileB
├ tree\dirA\fileC
├ tree\fileA
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:
LEAVES_ONLY CHILD_FIRST
[tree] [tree]
├ tree\dirA\dirB\fileD ├ tree\dirA\dirB\fileD
├ tree\dirA\fileB ├ tree\dirA\dirB
├ tree\dirA\fileC ├ tree\dirA\fileB
├ tree\fileA ├ tree\dirA\fileC
├ tree\dirA
├ tree\fileA
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:
$dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$lines = new RecursiveTreeIterator($dir);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));
Das RecursiveTreeIteratorsoll Zeile für Zeile arbeiten, die Ausgabe ist ziemlich einfach mit einem kleinen Problem:
[tree]
├ tree\dirA
│ ├ tree\dirA\dirB
│ │ └ tree\dirA\dirB\fileD
│ ├ tree\dirA\fileB
│ └ tree\dirA\fileC
└ tree\fileA
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:
/// Solved ///
[tree]
├ dirA
│ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
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:
$lines = new RecursiveTreeIterator(
new DiyRecursiveDecorator($dir)
);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));
Diese Fragmente $unicodeTreePrefixsind Teil des Kerns im Anhang: Machen Sie es sich selbst: Machen Sie die RecursiveTreeIteratorArbeit Zeile für Zeile. .