Jak działa RecursiveIteratorIterator w PHP?

Jak działa RecursiveIteratorIterator?

Podręcznik PHP nie ma nic udokumentowanego ani wyjaśnionego. Jaka jest różnica między IteratorIterator A RecursiveIteratorIterator?

Author: fuxia, 2012-08-22

4 answers

RecursiveIteratorIterator jest betonem Iterator implementacja trawersowania drzewa . Pozwala programistom na poruszanie się po obiekcie kontenera, który implementuje interfejs RecursiveIterator, zobacz Iterator w Wikipedii, gdzie znajdują się ogólne zasady, typy, semantyka i wzorce iteratorów.

In different to IteratorIterator która jest konkretną Iterator realizacją obiektów w porządku liniowym (i domyślnie przyjmującą każdy rodzaj Traversable w konstruktorze), RecursiveIteratorIterator pozwala na zapętlenie wszystkich węzłów w uporządkowanym drzewie obiektów, a jego konstruktor przyjmuje RecursiveIterator.

W skrócie: RecursiveIteratorIterator pozwala na zapętlenie drzewa, IteratorIterator pozwala na zapętlenie listy. Pokażę to w kilku przykładach kodu poniżej.

Technicznie działa to poprzez zerwanie z liniowości przez przemierzanie wszystkich dzieci węzłów (jeśli takie istnieją). Jest to możliwe, ponieważ z definicji wszystkie dzieci węzła są ponownie RecursiveIterator. Na toplevel Iterator następnie wewnętrznie układa różne RecursiveIterator s według ich głębokości i utrzymuje wskaźnik do bieżącego aktywnego sub Iterator dla trawersalu.

Pozwala to na odwiedzenie wszystkich węzłów drzewa.

Podstawowe zasady są takie same jak w IteratorIterator: interfejs określa typ iteracji, a podstawowa klasa iteratora jest implementacją tych semantyki. Porównaj z poniższymi przykładami, dla pętli liniowej z foreach zwykle nie myślisz o implementacja wiele szczegółów, chyba że trzeba zdefiniować nowy Iterator (np. gdy jakiś konkretny typ nie implementuje Traversable).

Dla rekurencyjnego trawersu-chyba że nie używasz predefiniowanego Traversal, który ma już rekurencyjną iterację trawersu - Zwykle musisz utworzyć instancję istniejącej iteracji RecursiveIteratorIterator lub nawet napisać rekurencyjną iterację trawersu, która jest Traversable twoją własną, aby mieć tego typu iterację trawersu z foreach.

Wskazówka: prawdopodobnie nie wdrożyłeś ani jednego, ani drugiego własnego, więc może to być coś, co warto zrobić dla praktycznego doświadczenia różnic, które mają. Na końcu odpowiedzi znajdziesz sugestię DIY.

Różnice techniczne w skrócie:

  • podczas gdy IteratorIterator zajmuje dowolne Traversable dla ruchu liniowego, RecursiveIteratorIterator potrzebuje bardziej szczegółowego RecursiveIterator, aby pętla wokół drzewa.
  • gdzie IteratorIterator ujawnia swoją główną Iterator poprzez getInnerIerator(), RecursiveIteratorIterator dostarcza aktualną aktywną sub - Iterator tylko za pomocą tej metody.
  • podczas gdy IteratorIterator nie jest świadomy niczego takiego jak rodzic lub dzieci, RecursiveIteratorIterator wie, jak zdobyć i przemierzać dzieci, jak również.
  • IteratorIterator nie potrzebuje stosu iteratorów, RecursiveIteratorIterator ma taki stos i zna aktywny pod-iterator.
  • gdzie IteratorIterator ma swój porządek ze względu na liniowość i brak wyboru, RecursiveIteratorIterator ma wybór do dalszego trawersowania i musi decydować na każdy węzeł (decyduje tryb na RecursiveIteratorIterator).
  • RecursiveIteratorIterator ma więcej metod niż IteratorIterator.

Podsumowując: RecursiveIterator to konkretny rodzaj iteracji (zapętlenia drzewa), która działa na własnych iteratorach, a mianowicie RecursiveIterator. Jest to ta sama podstawowa zasada, co w przypadku IteratorIerator, ale Typ iteracji jest inny (kolejność liniowa).

Idealnie możesz stworzyć swój własny zestaw. Jedyną konieczną rzeczą jest to, że Twój iterator implementuje Traversable, co jest możliwe poprzez Iterator lub IteratorAggregate. Wtedy możesz użyj go z foreach. Na przykład pewnego rodzaju obiekt iteracji rekurencyjnej w drzewie trójdzielnym wraz z interfejsem iteracji dla obiektu(obiektów) kontenera (kontenerów).


Przyjrzyjmy się przykładom z prawdziwego życia, które nie są tak abstrakcyjne. Między interfejsami, konkretnymi iteratorami, obiektami kontenerów i semantyką iteracji to może nie jest taki zły pomysł.

Weźmy jako przykład listę katalogów. Rozważ, że masz następujące drzewo plików i katalogów na dysku:

Drzewo Katalogów

Iterator o kolejności liniowej przemieszcza się po folderze toplevel i plikach (lista pojedynczych katalogów), natomiast iterator rekurencyjny przemieszcza się również przez podfoldery i wyświetla listę wszystkich folderów i plików (Lista katalogów z listami jego podkatalogów):
Non-Recursive        Recursive
=============        =========

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

Można łatwo porównać to z IteratorIterator, który nie wykonuje rekursji dla przemierzania drzewa katalogów. I RecursiveIteratorIterator, które mogą trawersować w drzewo jak pokazuje lista rekurencyjna.

Na początku bardzo podstawowy przykład z DirectoryIterator że realizuje Traversable co pozwala foreach to iterate over it:

$path = 'tree';
$dir  = new DirectoryIterator($path);

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

Przykładowe wyjście dla powyższej struktury katalogów to:

[tree]
 ├ .
 ├ ..
 ├ dirA
 ├ fileA

Jak widzisz to nie jest jeszcze za pomocą IteratorIterator lub RecursiveIteratorIterator. Zamiast tego po prostu używa foreach, który działa na interfejsie Traversable.

As foreach domyślnie zna tylko Typ iteracji nazwanej porządkiem liniowym, możemy chcieć jawnie określić typ iteracji. Na pierwszy rzut oka może to wydawać się zbyt gadatliwe, ale dla celów demonstracyjnych (i aby różnica z RecursiveIteratorIterator bardziej widoczna później), pozwala określić liniowy Typ iteracji jawnie określając typ iteracji IteratorIterator dla listy katalogów:

$files = new IteratorIterator($dir);

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

Ten przykład jest prawie identyczny z pierwszym, różnica polega na tym, że $files jest teraz typem IteratorIterator iteracja dla Traversable $dir:

$files = new IteratorIterator($dir);

Jak zwykle czynność iteracji wykonuje foreach:

foreach ($files as $file) {

Wyjście jest dokładnie to samo. Czym się różni? Inny jest obiekt używany w foreach. W pierwszym przykładzie jest to DirectoryIterator w drugim przykładzie jest to IteratorIterator. To pokazuje elastyczność iteratorów: można je zastąpić sobą, kod wewnątrz foreach po prostu kontynuować pracę zgodnie z oczekiwaniami.

Lets start to get the cała lista, łącznie z podkatalogami.

Ponieważ teraz określiliśmy Typ iteracji, rozważmy zmianę jej na inny typ iteracji.

Wiemy, że musimy teraz przejść całe drzewo, nie tylko pierwszy poziom. Aby mieć taką pracę z prostym foreach, potrzebujemy innego typu iteratora: RecursiveIteratorIterator. I że można tylko iterować nad obiektami kontenera, które mają RecursiveIterator interfejs .

Interfejs jest kontraktem. Dowolne Klasa implementująca to może być używana razem z RecursiveIteratorIterator. Przykładem takiej klasy jest RecursiveDirectoryIterator, co jest czymś w rodzaju rekurencyjnego wariantu DirectoryIterator.

Zobaczmy pierwszy przykład kodu przed napisaniem innego zdania ze słowem I:

$dir  = new RecursiveDirectoryIterator($path);

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

Ten trzeci przykład jest prawie identyczny z pierwszym, jednak tworzy inny wynik:

[tree]
 ├ tree\.
 ├ tree\..
 ├ tree\dirA
 ├ tree\fileA

OK, nie tak różnie, nazwa pliku zawiera teraz ścieżkę w przód, ale reszta też wygląda podobnie.

Jak pokazuje przykład, nawet obiekt directory jest już połączony z interfejsem RecursiveIterator, to jeszcze nie wystarczy, aby foreach przemierzał całe drzewo katalogów. W tym miejscu RecursiveIteratorIterator zaczyna działać. przykład 4 pokazuje jak:

$files = new RecursiveIteratorIterator($dir);

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

Użycie RecursiveIteratorIterator zamiast tylko poprzedniego $dir spowoduje, że foreach będzie przemierzać wszystkie pliki i katalogi w sposób rekurencyjny. Następnie wyświetla wszystkie pliki, jako Typ iteracji obiektu został określony teraz:

[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

To powinno już wykazać różnicę między trawersem płaskim A drzewnym. {[19] } jest w stanie przemierzać dowolną strukturę przypominającą drzewo jako listę elementów. Ponieważ jest więcej informacji (jak poziom, na którym obecnie odbywa się iteracja), można uzyskać dostęp do obiektu iterator podczas iteracji nad nim i na przykład wciąć wyjście:

echo "[$path]\n";
foreach ($files as $file) {
    $indent = str_repeat('   ', $files->getDepth());
    echo $indent, " ├ $file\n";
}

I wyjście przykład 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

Jasne, że to nie wygra konkursu piękności, ale pokazuje, że z iteratorem rekurencyjnym jest więcej dostępnych informacji niż tylko kolejność liniowa klucza i wartości . Nawet foreach może wyrazić tylko ten rodzaj liniowości, dostęp do iteratora pozwala uzyskać więcej informacji.

Podobnie jak meta-informacje, istnieją również różne sposoby poruszania się po drzewie i tym samym porządkowania danych wyjściowych. To jest na Mode of the RecursiveIteratorIterator i można go ustawić za pomocą konstruktora.

Następny przykład powie RecursiveDirectoryIterator, aby usunąć wpisy z kropkami (. i ..), ponieważ ich nie potrzebujemy. Ale również tryb rekurencji zostanie zmieniony tak, aby element nadrzędny (podkatalog) był pierwszy (SELF_FIRST) przed potomkami (pliki i podkatalog w podkatalogu):

$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";
}

Wyjście pokazuje wpisy podkatalogu poprawnie wymienione, jeśli porównasz z poprzedni wynik tych nie było:

[tree]
 ├ tree\dirA
    ├ tree\dirA\dirB
       ├ tree\dirA\dirB\fileD
    ├ tree\dirA\fileB
    ├ tree\dirA\fileC
 ├ tree\fileA
Tryb rekurencyjny steruje tym, co i kiedy zwracany jest brach lub liść w drzewie, na przykładzie katalogu:
  • LEAVES_ONLY (domyślnie): tylko listy plików, bez katalogów.
  • SELF_FIRST (powyżej): Lista katalogów, a następnie pliki tam.
  • CHILD_FIRST (w / O przykład): najpierw Wyświetla listę plików w podkatalogu, a następnie katalogu.

Wyjście przykład 5 z dwoma inne tryby:

  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

Kiedy porównasz to ze standardowym trawersem, wszystkie te rzeczy nie są dostępne. Iteracja rekurencyjna jest więc nieco bardziej skomplikowana, gdy trzeba ją ogarnąć, jednak jest łatwa w użyciu, ponieważ zachowuje się jak iterator, wkładasz go do foreach i gotowe.

Myślę, że to wystarczająco dużo przykładów na jedną odpowiedź. Możesz znaleźć Pełny kod źródłowy, a także przykład wyświetlania ładnie wyglądających drzew ascii w tym gist: https://gist.github.com/3599532

zrób to sam: wykonaj RecursiveTreeIterator pracę linia po linii.

przykład 5 wykazał, że dostępne są meta-informacje o stanie iteratora. Jednak zostało to celowo zademonstrowane w iteracji foreach. W prawdziwym życiu to naturalnie należy do RecursiveIterator.

Lepszym przykładem jest RecursiveTreeIterator, it takes care wcięcia, prefiksu i tak dalej. Zobacz następujący fragment kodu:

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

{[110] } jest przeznaczony do pracy linia po linii, wyjście jest dość proste z jednym małym problemem:

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

W połączeniu z RecursiveDirectoryIterator wyświetla całą ścieżkę, a nie tylko nazwę pliku. Reszta wygląda dobrze. Dzieje się tak dlatego, że nazwy plików są generowane przez SplFileInfo. Powinny one być wyświetlane jako nazwy podstawowe. Pożądanym wyjściem jest po:

/// Solved ///

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

Utwórz klasę dekoratora, która może być używana z RecursiveTreeIterator zamiast RecursiveDirectoryIterator. Powinna ona zawierać nazwę podstawową bieżącego SplFileInfo zamiast nazwy ścieżki. Ostateczny fragment kodu może wtedy wyglądać następująco:

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

Te fragmenty, w tym $unicodeTreePrefix są częścią gist w dodatek: zrób to sam: wykonaj RecursiveTreeIterator praca linia po linii..

 218
Author: hakre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-02 22:24:46

Jaka jest różnica między IteratorIterator i RecursiveIteratorIterator?

Aby zrozumieć różnicę między tymi dwoma iteratorami, należy najpierw zrozumieć trochę o używanych konwencjach nazewniczych i o tym, co rozumiemy przez Iteratory "rekurencyjne".

Iteratory rekurencyjne i nie rekurencyjne

PHP ma nie - "rekurencyjne" Iteratory, takie jak ArrayIterator i FilesystemIterator. Istnieją również Iteratory "rekurencyjne", takie jak RecursiveArrayIterator i RecursiveDirectoryIterator. Te ostatnie mają metody umożliwiające ich wiercenie w dół, te pierwsze nie.

Gdy instancje tych iteratorów są zapętlone samodzielnie, nawet rekurencyjne, wartości pochodzą tylko z "górnego" poziomu, nawet jeśli są zapętlone nad zagnieżdżoną tablicą lub katalogiem z podkatalogami.

Iteratory rekurencyjne implementują zachowanie rekurencyjne (poprzez hasChildren(), getChildren()) ale nie wykorzystuj tego.

Może lepiej pomyśleć o iteratorach rekurencyjnych jako iteratorach "rekurencyjnych", mają one zdolność do iteracji rekurencyjnej, ale po prostu iteracja nad instancją jednej z tych klas tego nie zrobi. Aby wykorzystać rekurencyjne zachowanie, Czytaj dalej.

RecursiveIteratorIterator

To tutaj RecursiveIteratorIterator wchodzi do gry. Posiada wiedzę o tym, jak wywoływać Iteratory "rekurencyjne" w taki sposób, aby wwiercić się w strukturę w normalnej, płaskiej pętli. Wprowadza rekurencyjne zachowanie w działanie. Zasadniczo wykonuje pracę stepping nad każdą z wartości w iteratorze, sprawdzając, czy istnieją "dzieci" do rekurencji, czy nie, i wchodząc do i z tych kolekcji dzieci. Przyklejasz instancję RecursiveIteratorIterator do foreach, aona zanurza się w strukturze, abyś nie musiał.

Jeśli RecursiveIteratorIterator nie został użyty, musiałbyś napisać własne pętle rekurencyjne, aby wykorzystać rekurencyjne zachowanie, sprawdzając w stosunku do iteratora" recursible " hasChildren() i używając getChildren().

Więc to a krótki przegląd RecursiveIteratorIterator, Czym się różni od IteratorIterator? Zadajesz to samo pytanie co Jaka jest różnica między kotkiem a drzewkiem? tylko dlatego, że oba pojawiają się w tej samej encyklopedii (lub podręczniku, dla iteratorów), nie oznacza, że powinieneś się mylić między tymi dwoma.

IteratorIterator

Zadaniem IteratorIterator jest wzięcie dowolnego Traversable obiektu i zawinięcie go w taki sposób, aby spełniał on interfejs Iterator. A use for this jest wtedy w stanie zastosować zachowanie specyficzne dla iteratora na obiekcie innym niż iterator.

Aby dać praktyczny przykład, DatePeriod klasa jest Traversable, ale nie Iterator. Jako takie, możemy zapętlać jego wartości za pomocą foreach(), ale nie możemy robić innych rzeczy, które normalnie zrobilibyśmy z iteratorem, takich jak filtrowanie.

Zadanie: pętla w poniedziałki, środy i piątki w ciągu najbliższych czterech tygodni.

tak, jest to trywialne przez foreach - ing nad DatePeriod i za pomocą if() W pętli; ale nie o to chodzi w tym przykładzie!

$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) { … }

Powyższy fragment nie zadziała, ponieważ CallbackFilterIterator oczekuje instancji klasy, która implementuje interfejs Iterator, a DatePeriod Nie działa. Ponieważ jednak jest Traversable, możemy łatwo spełnić ten wymóg używając IteratorIterator.

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

Jak widzisz, nie ma to nic wspólnego z iteracją nad klasami iteratorowymi ani z rekurencją, i w tym tkwi różnica między IteratorIterator a RecursiveIteratorIterator.

Podsumowanie

RecursiveIteraratorIterator jest do iteracji przez RecursiveIterator ("recursible" iterator), wykorzystując dostępne rekurencyjne zachowanie.

IteratorIterator jest do stosowania zachowania Iterator do obiektów nie-iteratora, Traversable.

 30
Author: salathe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-02 14:39:32

RecursiveDirectoryIterator wyświetla całą ścieżkę, a nie tylko nazwę pliku. Reszta wygląda dobrze. Dzieje się tak, ponieważ nazwy plików są generowane przez SplFileInfo. Powinny one być wyświetlane jako nazwy podstawowe. Pożądane wyjście jest następujące:

$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";
    }
}

Wyjście:

tree
 ├ dirA
 │ ├ dirB
 │ │ └ fileD
 │ ├ fileB
 │ └ fileC
 └ fileA
 0
Author: javad shariaty,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-11 09:14:12

W przypadku stosowania z iterator_to_array(), RecursiveIteratorIterator będzie rekurencyjnie chodził po tablicy, aby znaleźć wszystkie wartości. Co oznacza, że spłaszczy oryginalną tablicę.

IteratorIterator zachowa oryginalną strukturę hierarchiczną.

Ten przykład pokaże ci wyraźnie różnicę:

$array = array(
               'ford',
               'model' => 'F150',
               'color' => 'blue', 
               'options' => array('radio' => 'satellite')
               );

$recursiveIterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
var_dump(iterator_to_array($recursiveIterator, true));

$iterator = new IteratorIterator(new ArrayIterator($array));
var_dump(iterator_to_array($iterator,true));
 -1
Author: Tchoupi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-30 20:56:22