webentwicklung-frage-antwort-db.com.de

Wie funktioniert RecursiveIteratorIterator in PHP?

Wie funktioniert RecursiveIteratorIterator

Das PHP Handbuch hat nichts dokumentiert oder erklärt. Was ist der Unterschied zwischen IteratorIterator und RecursiveIteratorIterator?

78
varuog

RecursiveIteratorIterator ist ein konkretes Iterator implementierendes Baumdurchquerung . Es ermöglicht einem Programmierer, ein Containerobjekt zu durchlaufen, das die Schnittstelle RecursiveIterator implementiert. Die allgemeinen Prinzipien, Typen, Semantiken und Muster von Iteratoren finden Sie unter Iterator in Wikipedia .

Im Unterschied zu IteratorIterator , bei dem es sich um eine konkrete Iterator Implementierung der Objektdurchquerung in linearer Reihenfolge handelt (und standardmäßig jede Art von Traversable akzeptiert) ~] _ in seinem Konstruktor) erlaubt der RecursiveIteratorIterator das Durchlaufen aller Knoten in einem geordneten Baum von Objekten und sein Konstruktor nimmt einen RecursiveIterator.

Kurz gesagt: RecursiveIteratorIterator ermöglicht das Durchlaufen eines Baums, IteratorIterator das Durchlaufen einer Liste. Das zeige ich Ihnen in Kürze anhand einiger Codebeispiele.

Technisch funktioniert dies, indem die Linearität durchbrochen wird, indem alle untergeordneten Knoten (falls vorhanden) durchlaufen werden. Dies ist möglich, weil per Definition alle Kinder eines Knotens wieder ein RecursiveIterator sind. Die oberste Ebene Iterator stapelt dann intern die verschiedenen RecursiveIterators nach ihrer Tiefe und behält einen Zeiger auf das aktuell aktive Unter Iterator zum Durchlaufen bei.

Dies ermöglicht es, alle Knoten eines Baumes zu besuchen.

Die zugrunde liegenden Prinzipien sind dieselben wie bei IteratorIterator: Eine Schnittstelle gibt die Art der Iteration an, und die Basisiteratorklasse ist die Implementierung dieser Semantik. Vergleichen Sie mit den nachstehenden Beispielen, bei linearer Schleife mit foreach denken Sie normalerweise nicht viel über die Implementierungsdetails nach, es sei denn, Sie müssen einen neuen Iterator definieren (z. B. wenn ein konkreter Typ dies nicht tut) implementiere Traversable).

Für rekursives Durchlaufen - es sei denn, Sie verwenden kein vordefiniertes Traversal, das bereits über eine rekursive Durchlaufiteration verfügt - müssen Sie normalerweise das vorhandene RecursiveIteratorIterator instanziieren. Iteration oder schreiben Sie sogar eine rekursive Traversal-Iteration, die eine Traversable eigene ist, um diese Art von Traversal-Iteration mit foreach zu erhalten.

Tipp: Wahrscheinlich haben Sie weder das eine noch das andere implementiert, sodass dies für Ihre praktische Erfahrung mit den Unterschieden, die sie aufweisen, eine wertvolle Maßnahme sein könnte. Am Ende der Antwort finden Sie einen DIY-Vorschlag.

Technische Unterschiede in Kürze:

  • Während IteratorIterator ein beliebiges Traversable zum linearen Durchlaufen benötigt, benötigt RecursiveIteratorIterator ein spezifischeres RecursiveIterator, um eine Schleife über einen Baum auszuführen.
  • Wo IteratorIterator sein Haupt Iterator über getInnerIerator() verfügbar macht, liefert RecursiveIteratorIterator das aktuell aktive Unter-Iterator nur über dieses Methode.
  • Während IteratorIterator nichts über Eltern oder Kinder weiß, weiß RecursiveIteratorIterator 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 seine Ordnung aufgrund der Linearität hat und keine Wahl, hat RecursiveIteratorIterator eine Wahl für die weitere Durchquerung und muss sich für jeden Knoten entscheiden (entschieden über mode per RecursiveIteratorIterator ).
  • RecursiveIteratorIterator hat mehr Methoden als IteratorIterator.

Um zusammenzufassen: RecursiveIterator ist eine konkrete Art von Iteration (Schleifen über einen Baum), die mit eigenen Iteratoren arbeitet, nämlich RecursiveIterator. Das ist das gleiche Prinzip 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 Traversable implementiert, was über Iterator oder IteratorAggregate möglich ist. Dann können Sie es mit foreach verwenden. Zum Beispiel eine Art rekursives ternäres Tree Traversal-Iterationsobjekt zusammen mit der entsprechenden Iterationsschnittstelle für das oder die Containerobjekte.


Lassen Sie uns einige Beispiele aus der Praxis betrachten, die nicht so abstrakt sind. Zwischen Interfaces, konkreten Iteratoren, Containerobjekten und Iterationssemantiken ist dies möglicherweise keine so schlechte Idee.

Nehmen Sie ein Verzeichnis als Beispiel. Angenommen, Sie haben den folgenden Datei- und Verzeichnisbaum auf der Festplatte:

Directory Tree

Während ein Iterator mit linearer Reihenfolge nur den Ordner und die Dateien auf oberster Ebene durchläuft (eine einzelne Verzeichnisliste), durchläuft der rekursive Iterator auch die Unterordner und listet alle Ordner und Dateien auf (eine Verzeichnisliste mit Auflistungen der Unterverzeichnisse):

Non-Recursive        Recursive
=============        =========

   [tree]            [tree]
    ├ dirA            ├ dirA
    └ fileA           │ ├ dirB
                      │ │ └ fileD
                      │ ├ fileB
                      │ └ fileC
                      └ fileA

Sie können dies leicht mit IteratorIterator vergleichen, das keine Rekursion für das Durchlaufen des Verzeichnisbaums ausführt. Und das RecursiveIteratorIterator, das in den Baum übergehen kann, wie die rekursive Auflistung zeigt.

Zunächst ein sehr einfaches Beispiel mit einem DirectoryIterator , das Traversable implementiert, mit dem foreach to iterate drüber hinweg:

$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 IteratorIterator oder RecursiveIteratorIterator noch nicht verwendet. Stattdessen wird nur foreach verwendet, das auf der Schnittstelle Traversable ausgeführt wird.

Da foreach standardmäßig nur die Art der Iteration kennt, die als lineare Reihenfolge bezeichnet wird, möchten wir die Art der Iteration möglicherweise explizit angeben. Auf den ersten Blick mag es zu ausführlich erscheinen, aber zu Demonstrationszwecken (und um den Unterschied mit RecursiveIteratorIterator später besser sichtbar zu machen) können Sie den linearen Typ der Iteration angeben, indem Sie explizit den Typ IteratorIterator angeben der Iteration für die Verzeichnisliste:

$files = new IteratorIterator($dir);

echo "[$path]\n";
foreach ($files as $file) {
    echo " ├ $file\n";
}

Dieses Beispiel ist fast identisch mit dem ersten, der Unterschied ist, dass $files ist jetzt ein IteratorIterator Iterationstyp für Traversable$dir:

$files = new IteratorIterator($dir);

Der Iterationsvorgang wird wie üblich vom foreach ausgeführt:

foreach ($files as $file) {

Die Ausgabe ist genau gleich. Was ist anders? Unterschiedlich ist das Objekt, das im foreach verwendet wird. Im ersten Beispiel ist es ein DirectoryIterator im zweiten Beispiel ist es das IteratorIterator. Dies zeigt die Flexibilität der Iteratoren: Sie können sie untereinander austauschen, der Code in foreach funktioniert einfach weiter wie erwartet.

Beginnen wir mit dem Abrufen der gesamten Liste, einschließlich der Unterverzeichnisse.

Da wir nun den Iterationstyp angegeben haben, sollten wir 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 foreach funktioniert, benötigen wir einen anderen Typ von Iterator: RecursiveIteratorIterator . Und das kann man nur über Containerobjekte iterieren, die die Schnittstelle RecursiveIterator haben.

Die Schnittstelle ist ein Vertrag. Jede Klasse, die es implementiert, kann zusammen mit dem RecursiveIteratorIterator verwendet werden. Ein Beispiel für eine solche Klasse ist RecursiveDirectoryIterator , was so etwas wie die rekursive Variante von DirectoryIterator ist.

Sehen wir uns ein erstes Codebeispiel an, bevor wir einen anderen Satz mit dem I-Word 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 unterschiedlich, der Dateiname enthält jetzt den Pfadnamen im Vordergrund, aber der Rest sieht auch ähnlich aus.

Wie das Beispiel zeigt, implementiert das Verzeichnisobjekt bereits die Schnittstelle RecursiveIterator. Dies reicht jedoch noch nicht aus, um foreach den gesamten Verzeichnisbaum zu durchlaufen. Hier setzt die RecursiveIteratorIterator an. Beispiel 4 zeigt wie:

$files = new RecursiveIteratorIterator($dir);

echo "[$path]\n";
foreach ($files as $file) {
    echo " ├ $file\n";
}

Verwenden des RecursiveIteratorIterator anstelle des vorherigen $dir object veranlasst foreach, alle Dateien und Verzeichnisse rekursiv zu durchlaufen. Dies listet dann alle Dateien auf, da die Art der Objektiteration nun festgelegt 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 aufzeigen. Das RecursiveIteratorIterator kann jede baumartige Struktur als Liste von Elementen durchlaufen. Da es mehr Informationen gibt (wie die Ebene, auf der die Iteration aktuell stattfindet), ist es möglich, auf das Iterator-Objekt zuzugreifen, während Sie darüber iterieren und zum Beispiel 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. Sogar foreach kann nur diese Art von Linearität ausdrücken. Wenn Sie auf den Iterator selbst zugreifen, erhalten Sie weitere Informationen.

Ähnlich wie bei den Metainformationen gibt es auch verschiedene Möglichkeiten, den Baum zu durchlaufen und damit die Ausgabe zu ordnen. Dies ist das Mode des RecursiveIteratorIterator und kann mit dem Konstruktor gesetzt werden.

Im nächsten Beispiel wird der RecursiveDirectoryIterator angewiesen, die Punkteinträge zu entfernen (. und ..) da wir sie nicht brauchen. Aber auch der Rekursionsmodus wird so geändert, dass zuerst das übergeordnete Element (das Unterverzeichnis) verwendet wird (SELF_FIRST) vor den Kindern (den Dateien und Unterverzeichnissen im Unterverzeichnis):

$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 nun die korrekt aufgelisteten Unterverzeichniseinträge an, wenn Sie diese mit der vorherigen Ausgabe vergleichen:

[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 ein Blatt im Baum zurückgegeben wird, für das Verzeichnisbeispiel:

  • LEAVES_ONLY (Standard): Nur Dateien auflisten, keine Verzeichnisse.
  • SELF_FIRST (oben): Verzeichnis und die darin enthaltenen Dateien auflisten.
  • CHILD_FIRST (ohne Beispiel): Zuerst Dateien im Unterverzeichnis auflisten, dann das Verzeichnis.

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 Standard-Traversal vergleichen, sind all diese Dinge nicht verfügbar. Die rekursive Iteration ist daher etwas komplexer, wenn Sie den Kopf darum wickeln müssen. Sie ist jedoch einfach zu verwenden, da sie sich wie ein Iterator verhält. Sie haben sie in ein foreach eingefügt und fertig.

Ich denke, das sind genug Beispiele für eine Antwort. In diesem Gist finden Sie den vollständigen Quellcode sowie ein Beispiel für die Anzeige gut aussehender ASCII-Bäume: https://Gist.github.com/3599532

Do It Yourself: Machen Sie das RecursiveTreeIterator Zeile für Zeile.

Beispiel 5 hat gezeigt, dass Metainformationen zum Status des Iterators verfügbar sind. Dies wurde jedoch gezielt demonstriert innerhalb der foreach Iteration. Im wirklichen Leben gehört dies natürlich in den RecursiveIterator.

Ein besseres Beispiel ist 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));

Die 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 einem RecursiveDirectoryIterator wird der gesamte Pfadname und nicht nur der Dateiname angezeigt. 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:

/// Solved ///

[tree]
 ├ dirA
 │ ├ dirB
 │ │ └ fileD
 │ ├ fileB
 │ └ fileC
 └ fileA

Erstellen Sie eine Decorator-Klasse, die mit RecursiveTreeIterator anstelle von RecursiveDirectoryIterator verwendet werden kann. Es sollte den Basisnamen des aktuellen SplFileInfo anstelle des Pfadnamens angeben. Das endgültige Codefragment könnte dann so aussehen:

$lines = new RecursiveTreeIterator(
    new DiyRecursiveDecorator($dir)
);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));

Diese Fragmente einschließlich $unicodeTreePrefix sind Teil des Inhalts von Anhang: Mach es dir selbst: Mache das RecursiveTreeIterator Zeile für Zeile..

233
hakre

Was ist der Unterschied zwischen IteratorIterator und RecursiveIteratorIterator?

Um den Unterschied zwischen diesen beiden Iteratoren zu verstehen, muss man sich zunächst ein wenig mit den verwendeten Namenskonventionen und dem, was wir unter "rekursiven" Iteratoren verstehen, vertraut machen.

Rekursive und nicht rekursive Iteratoren

PHP hat nicht "rekursive" Iteratoren wie ArrayIterator und FilesystemIterator. Es gibt auch "rekursive" Iteratoren wie RecursiveArrayIterator und RecursiveDirectoryIterator. Letztere verfügen über Methoden, in die sie eingearbeitet werden können, erstere nicht.

Wenn Instanzen dieser Iteratoren für sich alleine durchlaufen werden, selbst wenn es sich um rekursive Iteratoren handelt, stammen die Werte nur von der obersten Ebene, selbst wenn sie über ein verschachteltes Array oder Verzeichnis mit Unterverzeichnissen durchlaufen werden.

Die rekursiven Iteratoren implementieren rekursives Verhalten (über hasChildren(), getChildren()), aber nicht ausnutzen .

Es ist vielleicht besser, sich die rekursiven Iteratoren als "rekursive" Iteratoren vorzustellen. Sie haben die Fähigkeit, rekursiv zu iterieren, aber ein einfaches Iterieren über eine Instanz einer dieser Klassen reicht nicht aus Das. Lesen Sie weiter, um das rekursive Verhalten auszunutzen.

RecursiveIteratorIterator

Hier kommt der RecursiveIteratorIterator 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 Aktion. Es erledigt im Wesentlichen die Arbeit, über jeden der Werte im Iterator zu springen, zu prüfen, ob es "Kinder" gibt, in die man zurückkehren kann oder nicht, und in diese Sammlungen von Kindern und aus diesen heraus zu springen. Sie stecken eine Instanz von RecursiveIteratorIterator in ein foreach, und it taucht in die Struktur ein, damit Sie nicht müssen.

Wenn das RecursiveIteratorIterator nicht verwendet wurde, müssten Sie Ihre eigenen rekursiven Schleifen schreiben, um das rekursive Verhalten auszunutzen, gegen das hasChildren() des "rekursiblen" Iterators zu prüfen und getChildren() zu verwenden.

Das ist also eine kurze Übersicht über RecursiveIteratorIterator, wie unterscheidet es sich von IteratorIterator? Nun, Sie stellen im Grunde die gleiche Art von Frage wie Was ist der Unterschied zwischen einem Kätzchen und einem Baum? Nur weil beide in der gleichen Enzyklopädie (oder im gleichen Handbuch) für das Iteratoren) bedeutet nicht, dass Sie zwischen den beiden verwechselt werden sollten.

IteratorIterator

Die Aufgabe von IteratorIterator besteht darin, ein beliebiges Traversable -Objekt so zu verpacken, dass es die Iterator -Schnittstelle erfüllt. Eine Verwendung hierfür besteht darin, dann das iteratorspezifische Verhalten auf das Nicht-Iterator-Objekt anwenden zu können.

Um ein praktisches Beispiel zu geben: Die Klasse DatePeriod ist Traversable, aber keine Iterator. Als solches können wir seine Werte mit foreach() durchlaufen, aber wir können keine anderen Dinge tun, die wir normalerweise mit einem Iterator tun würden, wie zum Beispiel Filtern.

TASK: Schleife über die Montage, Mittwochs und Freitags der nächsten vier Wochen.

Ja, dies ist trivial, wenn foreach- über DatePeriod gewechselt wird und if() in der Schleife verwendet wird. Aber darum geht es in diesem Beispiel nicht!

$period = new DatePeriod(new DateTime, new DateInterval('P1D'), 28);
$dates  = new CallbackFilterIterator($period, function ($date) {
    return in_array($date->format('l'), array('Monday', 'Wednesday', 'Friday'));
});
foreach ($dates as $date) { … }

Das obige Snippet funktioniert nicht, da CallbackFilterIterator eine Instanz einer Klasse erwartet, die die Iterator -Schnittstelle implementiert, was DatePeriod nicht tut. Da es sich jedoch um Traversable handelt, können wir diese Anforderung leicht erfüllen, indem wir IteratorIterator verwenden.

$period = new IteratorIterator(new DatePeriod(…));

Wie Sie sehen, hat dies nichts damit zu tun, über Iteratorklassen oder Rekursionen zu iterieren, und darin liegt der Unterschied zwischen IteratorIterator und RecursiveIteratorIterator.

Zusammenfassung

RecursiveIteraratorIterator dient zum Durchlaufen eines RecursiveIterator ("rekursiver" Iterator) unter Ausnutzung des verfügbaren rekursiven Verhaltens.

IteratorIterator dient zum Anwenden des Iterator-Verhaltens auf Nicht-Iterator-, Traversable -Objekte.

30
salathe

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 folgende:

$path =__DIR__;
$dir = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($dir,RecursiveIteratorIterator::SELF_FIRST);
while ($files->valid()) {
    $file = $files->current();
    $filename = $file->getFilename();
    $deep = $files->getDepth();
    $indent = str_repeat('│ ', $deep);
    $files->next();
    $valid = $files->valid();
    if ($valid and ($files->getDepth() - 1 == $deep or $files->getDepth() == $deep)) {
        echo $indent, "├ $filename\n";
    } else {
        echo $indent, "└ $filename\n";
    }
}

ausgabe:

tree
 ├ dirA
 │ ├ dirB
 │ │ └ fileD
 │ ├ fileB
 │ └ fileC
 └ fileA
0
javad shariaty