RecursiveIteratorIterator
ist eine konkrete Iterator
implementierende Baumdurchquerung . Es ermöglicht einem Programmierer, ein Containerobjekt zu durchlaufen, das die RecursiveIterator
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 konkrete Iterator
implementierende Objektdurchquerung in linearer Reihenfolge (und standardmäßig wird jede Art von Objekt Traversable
in ihrem Konstruktor akzeptiert ). Das RecursiveIteratorIterator
erlaubt das Schleifen über alle Knoten in einem geordneten Baum von Objekten, und sein Konstruktor benötigt a RecursiveIterator
.
Kurz gesagt: RecursiveIteratorIterator
Ermöglicht das Durchlaufen eines Baums und IteratorIterator
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 Ebene Iterator
stapelt dann intern die verschiedenen RecursiveIterator
s nach ihrer Tiefe und behält einen Zeiger auf das aktuell aktive Sub Iterator
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 Schleifen foreach
denken 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 , Traversal
dass bereits rekursiven Traversal Iteration - Sie normalerweise benötigen die bestehende zu instanziiert RecursiveIteratorIterator
Iteration oder sogar schreiben eine rekursive Traversal Iteration , die eine ist Traversable
Ihre 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
IteratorIterator
jede dauert Traversable
für lineare Traversal, RecursiveIteratorIterator
benötigt eine präziser RecursiveIterator
über einen Baum - Schleife.
- Wenn
IteratorIterator
das Haupt- Iterator
Via verfügbar gemacht wird getInnerIerator()
, RecursiveIteratorIterator
wird das aktuell aktive Sub Iterator
nur über diese Methode bereitgestellt.
- Während er
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.
- Wo
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 als IteratorIterator
.
Zusammenfassend: RecursiveIterator
ist 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, Traversable
was über Iterator
oder 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 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über foreach
zu 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 IteratorIterator
oder RecursiveIteratorIterator
. Stattdessen foreach
funktioniert nur die Verwendung auf der Traversable
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 Unterschied RecursiveIteratorIterator
später besser sichtbar zu machen ) können Sie den linearen Iterationstyp angeben, indem Sie den IteratorIterator
Iterationstyp 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 $files
es sich nun um eine IteratorIterator
Art 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, DirectoryIterator
im 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 foreach
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 die RecursiveIterator
Schnittstelle 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 RecursiveIterator
Schnittstelle bereits. Dies reicht noch nicht aus, um foreach
den gesamten Verzeichnisbaum zu durchlaufen. Hier RecursiveIteratorIterator
kommt 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 RecursiveIteratorIterator
anstelle des vorherigen $dir
Objekts verwenden, werden foreach
alle 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 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:
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 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 vonRecursiveIteratorIterator
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:
$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 RecursiveTreeIterator
Arbeit Zeile für Zeile.
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 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 RecursiveTreeIterator
soll 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 RecursiveDirectoryIterator
wird 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 RecursiveTreeIterator
anstelle von verwendet werden kann RecursiveDirectoryIterator
. Es sollte den Basisnamen des aktuellen SplFileInfo
anstelle 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 $unicodeTreePrefix
sind Teil des Kerns im Anhang: Machen Sie es sich selbst: Machen Sie die RecursiveTreeIterator
Arbeit Zeile für Zeile. .