Jeśli zapytasz, jak RecursiveIteratorIteratordziała, czy już zrozumiałeś, jak IteratorIteratordziała? Mam na myśli to, że jest w zasadzie taki sam, tylko interfejs, który jest używany przez te dwa, jest inny. Czy jesteś bardziej zainteresowany kilkoma przykładami, czy chcesz zobaczyć różnice między implementacją kodu C?
hakre
@Gordon Nie byłem pewien, w jaki sposób pojedyncza pętla foreach może przejść przez wszystkie elementy w strukturze drzewa
varuog
@hakra Próbuję teraz przestudiować wszystkie wbudowane interfejsy, a także interfejs spl i implementację iteratora. Byłem zainteresowany, aby wiedzieć, jak to działa w tle z pętlą forach z kilkoma przykładami.
varuog,
@hakre Oba są w rzeczywistości bardzo różne. IteratorIteratormapy Iteratori IteratorAggregatedo miejsca Iterator, gdzie REcusiveIteratorIteratorjest używany do przemierzania recusivly aRecursiveIterator
W odróżnieniu od IteratorIteratorkonkretnej Iteratorimplementacji przechodzenia przez obiekt w porządku liniowym (i domyślnie akceptującej dowolny rodzaj Traversablew swoim konstruktorze), RecursiveIteratorIteratorumożliwia pętlę po wszystkich węzłach uporządkowanego drzewa obiektów, a jego konstruktor przyjmuje plik RecursiveIterator.
W skrócie: RecursiveIteratorIteratorpozwala na pętlę po drzewie,IteratorIterator pozwala na zapętlenie listy. Pokażę to wkrótce z kilkoma przykładami kodu poniżej.
Technicznie rzecz biorąc, działa to przez wyrwanie się z liniowości przez przejście przez wszystkie elementy potomne węzłów (jeśli takie istnieją). Jest to możliwe, ponieważ z definicji wszystkie dzieci węzła są ponownie RecursiveIterator. Górny poziom Iteratornastępnie wewnętrznie układa różne stosy RecursiveIteratorwedług ich głębokości i utrzymuje wskaźnik na bieżącym aktywnym podrzędnym Iteratorcelu przejścia.
Pozwala to na odwiedzenie wszystkich węzłów drzewa.
Podstawowe zasady są takie same jak w przypadku IteratorIterator: Interfejs określa typ iteracji, a podstawowa klasa iteratora jest implementacją tej semantyki. Porównaj z poniższymi przykładami, w przypadku pętli liniowej foreachzwykle nie myśl o szczegółach implementacji, chyba że musisz zdefiniować nowy Iterator(np. Gdy jakiś konkretny typ sam w sobie nie jestTraversable ).
W przypadku przemierzania rekurencyjnego - chyba że nie używasz predefiniowanej Traversaliteracji przechodzenia, która już ma rekurencyjną iterację przechodzenia - zwykle musisz utworzyć wystąpienie istniejącej RecursiveIteratorIteratoriteracji lub nawet napisać rekurencyjną iterację przechodzenia, która jest Traversabletwoją własną, aby mieć tego typu iterację przechodzenia foreach.
Wskazówka: prawdopodobnie nie zaimplementowałeś jednego ani drugiego własnego, więc może to być coś, co warto zrobić, aby poznać praktyczne różnice, jakie mają. Na końcu odpowiedzi znajdziesz sugestię zrób to sam.
Krótko mówiąc, różnice techniczne:
Chociaż IteratorIteratorpobiera dowolne Traversabledla przechodzenia liniowego, RecursiveIteratorIteratorpotrzebuje bardziej szczegółowej RecursiveIteratorpętli po drzewie.
Tam, gdzie IteratorIteratoreksponuje swoją główną Iteratorprzelotkę getInnerIerator(), RecursiveIteratorIteratorudostępnia bieżące aktywne sub- Iteratortylko za pomocą tej metody.
Chociaż IteratorIteratornie jest świadomy niczego takiego jak rodzic lub dzieci, RecursiveIteratorIteratorwie również, jak zdobyć i przemierzać dzieci.
IteratorIteratornie potrzebuje stosu iteratorów, RecursiveIteratorIteratorma taki stos i zna aktywny pod-iterator.
Gdzie IteratorIteratorma swoją kolejność ze względu na liniowość i nie ma wyboru, RecursiveIteratorIteratorma wybór dla dalszego przejścia i musi zdecydować dla każdego węzła (decyduje tryb naRecursiveIteratorIterator ).
RecursiveIteratorIteratorma więcej metod niż IteratorIterator.
Podsumowując: RecursiveIteratorto konkretny typ iteracji (pętla po drzewie), który działa na własnych iteratorach, a mianowicie RecursiveIterator. Jest to ta sama podstawowa zasada jak w przypadku IteratorIerator, ale typ iteracji jest inny (kolejność liniowa).
Idealnie byłoby również stworzyć własny zestaw. Jedyną konieczną rzeczą jest to, że Twój iterator implementuje, Traversableco jest możliwe za pośrednictwem Iteratorlub IteratorAggregate. Następnie możesz go używać z foreach. Na przykład jakiś obiekt iteracji rekurencyjnej przechodzenia przez drzewo trójskładnikowe wraz z odpowiednim interfejsem iteracji dla obiektów kontenera.
Przyjrzyjmy się kilku przykładom z życia, które nie są tak abstrakcyjne. Pomiędzy interfejsami, konkretnymi iteratorami, obiektami kontenerów i semantyką iteracji może nie jest to zły pomysł.
Weźmy jako przykład listę katalogów. Weź pod uwagę, że masz na dysku następujące drzewo plików i katalogów:
Podczas gdy iterator z porządkiem liniowym po prostu przechodzi przez folder i pliki najwyższego poziomu (lista pojedynczego katalogu), iterator rekurencyjny przechodzi również przez podfoldery i wyświetla listę wszystkich folderów i plików (lista katalogów z listą jego podkatalogów):
Możesz łatwo porównać to, z IteratorIteratorktórym nie ma rekursji przy przechodzeniu po drzewie katalogów. I RecursiveIteratorIteratorktóry może przejść do drzewa, jak pokazuje lista Rekurencyjna.
Przykładowy wynik dla powyższej struktury katalogów to:
[tree]
├ .
├ ..
├ dirA
├ fileA
Jak widać, nie jest to jeszcze używane IteratorIteratorani RecursiveIteratorIterator. Zamiast tego po prostu używa foreachtego, który działa na Traversableinterfejsie.
Ponieważ foreachdomyślnie zna tylko typ iteracji o nazwie porządek liniowy, możemy chcieć jawnie określić typ iteracji. Na pierwszy rzut oka może się to wydawać zbyt rozwlekłe, ale dla celów demonstracyjnych (i aby różnica była RecursiveIteratorIteratorwidoczna później), określmy liniowy typ iteracji, wyraźnie określając IteratorIteratortyp iteracji dla listingu katalogów:
Ten przykład jest prawie identyczny z pierwszym, różnica polega na tym, że $filesjest to teraz IteratorIteratortyp iteracji dla Traversable$dir:
$files = newIteratorIterator($dir);
Jak zwykle czynność iteracji jest wykonywana przez foreach:
foreach ($files as $file) {
Wynik jest dokładnie taki sam. Więc co się zmieniło? Inny jest obiekt używany w foreach. W pierwszym przykładzie jest to DirectoryIteratoraw drugim przykładzie jest to IteratorIterator. Pokazuje to elastyczność, jaką mają iteratory: możesz je zastąpić innymi, kod wewnątrz foreachpo prostu nadal działa zgodnie z oczekiwaniami.
Zacznijmy od całej listy, łącznie z podkatalogami.
Ponieważ określiliśmy teraz typ iteracji, rozważmy zmianę go na inny typ iteracji.
Wiemy, że musimy teraz przejść całe drzewo, a nie tylko pierwszy poziom. Aby wykonać tę pracę z prostym foreach, potrzebujemy innego typu iteratora:RecursiveIteratorIterator . I to można tylko iterować po obiektach kontenerów, które mają RecursiveIteratorinterfejs .
Interfejs jest umową. Każda klasa implementująca ją może być używana razem z RecursiveIteratorIterator. Przykładem takiej klasy jestRecursiveDirectoryIterator , która jest czymś w rodzaju rekurencyjnej odmiany DirectoryIterator.
Zobaczmy pierwszy przykład kodu przed napisaniem jakiegokolwiek innego zdania ze słowem I:
Okej, nie tak inaczej, nazwa pliku zawiera teraz nazwę ścieżki z przodu, ale reszta również wygląda podobnie.
Jak pokazuje przykład, nawet obiekt katalogu już implementuje RecursiveIteratorinterfejs, ale to nie wystarczy, aby foreachprzejść przez całe drzewo katalogów. Tutaj RecursiveIteratorIteratorzaczyna się akcja. Przykład 4 pokazuje, jak:
Użycie RecursiveIteratorIteratorzamiast tylko poprzedniego $dirobiektu spowoduje foreachprzechodzenie przez wszystkie pliki i katalogi w sposób rekurencyjny. Następnie wyświetla listę wszystkich plików, ponieważ typ iteracji obiektu został określony teraz:
Powinno to już pokazać różnicę między przechodzeniem płaskim i przechodzeniem po drzewie. RecursiveIteratorIteratorJest w stanie przechodzić żadnego drzewiastą strukturę w postaci listy elementów. Ponieważ jest więcej informacji (takich jak poziom, na którym obecnie odbywa się iteracja), można uzyskać dostęp do obiektu iteratora podczas iteracji po nim i na przykład wcinać dane wyjściowe:
Oczywiście to nie wygrywa konkursu piękności, ale pokazuje, że w przypadku iteratora rekurencyjnego dostępnych jest więcej informacji niż tylko liniowy porządek klucza i wartości . Nawet foreachmoże wyrazić ten rodzaj liniowości, dostęp do samego iteratora pozwala uzyskać więcej informacji.
Podobnie jak w przypadku metainformacji, istnieją również różne sposoby poruszania się po drzewie, a tym samym porządkowania wyników. To jest tryb programuRecursiveIteratorIterator i można go ustawić za pomocą konstruktora.
W następnym przykładzie RecursiveDirectoryIteratorpolecenie usunie kropki ( .i ..), ponieważ ich nie potrzebujemy. Ale także tryb rekursji zostanie zmieniony, aby najpierw zająć element nadrzędny (podkatalog) (SELF_FIRST ) przed dziećmi (pliki i podkatalogi w podkatalogu):
Porównując to ze standardowym przechodzeniem, wszystkie te rzeczy nie są dostępne. Dlatego iteracja rekurencyjna jest nieco bardziej złożona, gdy trzeba ją owinąć, jednak jest łatwa w użyciu, ponieważ zachowuje się jak iterator, umieszcza się ją w a foreachi gotowe.
Myślę, że to wystarczające przykłady na jedną odpowiedź. Możesz znaleźć pełny kod źródłowy, a także przykład, aby wyświetlić ładnie wyglądające drzewa ascii w tym skrócie: https://gist.github.com/3599532
Zrób to sam: stwórz RecursiveTreeIteratorpracę wiersz po wierszu.
Przykład 5 wykazał, że są dostępne metainformacje o stanie iteratora. Jednak zostało to celowo zademonstrowane w ramach foreachiteracji. W prawdziwym życiu to naturalnie należy doRecursiveIterator .
Lepszym przykładem jest RecursiveTreeIterator, zajmuje się wcięciami, przedrostkami i tak dalej. Zobacz następujący fragment kodu:
W połączeniu z a RecursiveDirectoryIteratorwyświetla całą nazwę ścieżki, a nie tylko nazwę pliku. Reszta wygląda dobrze. Dzieje się tak, ponieważ nazwy plików są generowane przez SplFileInfo. Zamiast tego powinny być wyświetlane jako nazwa basenowa. Żądane wyjście jest następujące:
Utwórz klasę dekoratora, której można używać RecursiveTreeIteratorzamiast RecursiveDirectoryIterator. Powinien zawierać nazwę podstawową prądu SplFileInfozamiast ścieżki. Ostateczny fragment kodu mógłby wtedy wyglądać następująco:
$lines = newRecursiveTreeIterator(
new DiyRecursiveDecorator($dir)
);
$unicodeTreePrefix($lines);
echo"[$path]\n", implode("\n", iterator_to_array($lines));
Te fragmenty, w tym, $unicodeTreePrefixsą częścią istoty w Dodatku: Zrób to sam: Stwórz RecursiveTreeIteratorpracę wiersz po wierszu. .
Nie odpowiada na zadane pytania, zawiera błędy rzeczowe i pomija kluczowe punkty, gdy przechodzisz do dostosowywania iteracji. Podsumowując, wygląda to na kiepską próbę zdobycia nagrody za temat, o którym niewiele wiesz lub, jeśli tak, nie możesz udzielić odpowiedzi na postawione pytania.
salathe
2
Cóż, co nie odpowiada na moje pytanie „dlaczego”, po prostu piszesz więcej słów, nie mówiąc zbyt wiele. Może zaczynasz od błędu, który się liczy? Wskaż to, nie ukrywaj tego.
hakre
Pierwsze zdanie jest niepoprawne: „RecursiveIteratorIterator to IteratorIterator obsługujący…”, to nieprawda.
salathe
1
@salathe: Dziękuję za opinie. Zredagowałem odpowiedź, aby ją adresować. Pierwsze zdanie rzeczywiście było błędne i niekompletne. Nadal pominąłem konkretne szczegóły implementacji, RecursiveIteratorIteratorponieważ jest to wspólne z innymi typami, ale podałem kilka informacji technicznych, jak to faktycznie działa. Myślę, że przykłady dobrze pokazują różnice: rodzaj iteracji jest główną różnicą między nimi. Nie mam pojęcia, jeśli kupisz typ iteracji, który ubijesz trochę inaczej, ale IMHO nie jest łatwe z semantyką typów iteracji.
hakre
1
Pierwsza część została nieco ulepszona, ale gdy zaczniesz przechodzić do przykładów, nadal skręca ona w faktyczne nieścisłości. Gdyby przyciąć odpowiedź na zasadzie poziomej, byłoby to znacznie lepsze.
salathe
32
Jaka jest różnica między IteratorIteratori RecursiveIteratorIterator?
Aby zrozumieć różnicę między tymi dwoma iteratorami, należy najpierw trochę zrozumieć stosowane konwencje nazewnictwa i co rozumiemy przez iteratory „rekurencyjne”.
Iteratory rekurencyjne i nierekurencyjne
PHP ma nie „rekurencyjne” iteratory, takie jak ArrayIteratori FilesystemIterator. Istnieją również „rekurencyjne” iteratory, takie jak RecursiveArrayIteratoriRecursiveDirectoryIterator . Ci drudzy mają metody umożliwiające ich wnikanie, ci pierwsi nie.
Gdy instancje tych iteratorów są zapętlone samodzielnie, nawet te rekurencyjne, wartości pochodzą tylko z „najwyższego” poziomu, nawet jeśli są zapętlone po zagnieżdżonej tablicy lub katalogu z podkatalogami.
Iteratory rekurencyjne implementują zachowanie rekurencyjne (via hasChildren(), getChildren()), ale tego nie robią wykorzystują .
Lepiej byłoby myśleć o iteratorach rekurencyjnych jako o iteratorach „rekurencyjnych”, mają one rozszerzenie zdolność do iteracji rekurencyjnej, ale zwykłe iterowanie po instancji jednej z tych klas tego nie zrobi. Aby wykorzystać zachowanie rekurencyjne, czytaj dalej.
RecursiveIteratorIterator
Tutaj właśnie RecursiveIteratorIteratorpojawia się gra. Posiada wiedzę na temat wywoływania „rekurencyjnych” iteratorów w taki sposób, aby drążyć strukturę w normalnej, płaskiej pętli. Wprowadza rekurencyjne zachowanie do działania. Zasadniczo wykonuje pracę polegającą na przechodzeniu przez każdą z wartości w iteratorze, sprawdzaniu, czy są „dzieci”, do których można się powrócić, czy nie, oraz wchodzeniu i wychodzeniu z tych zbiorów dzieci. Wbijasz wystąpienie RecursiveIteratorIteratorw foreach, a on nurkuje w strukturze, więc nie musisz tego robić.
Gdyby RecursiveIteratorIteratornie zostało użyte, musiałbyś napisać własne pętle rekurencyjne, aby wykorzystać zachowanie rekurencyjne, sprawdzając iterator „rekurencyjny” hasChildren()i używając getChildren().
Więc to jest krótkie omówienie RecursiveIteratorIterator, czym się różni od IteratorIterator? Cóż, w zasadzie zadajesz to samo pytanie, co Jaka jest różnica między kotkiem a drzewem? To, że oba pojawiają się w tej samej encyklopedii (lub podręczniku dla iteratorów), nie oznacza, że powinieneś się pomylić między nimi.
IteratorIterator
Zadaniem tego IteratorIteratorjest pobranie dowolnego Traversableobiektu i owinięcie go tak, aby spełniał wymagania Iteratorinterfejsu. Służy do tego możliwość zastosowania zachowania specyficznego dla iteratora na obiekcie niebędącym iteratorem.
Aby dać praktyczny przykład, DatePeriodklasa jest, Traversableale nie jest Iterator. W związku z tym możemy zapętlić jego wartości za pomocą, foreach()ale nie możemy zrobić innych rzeczy, które normalnie wykonalibyśmy za pomocą iteratora, takich jak filtrowanie.
ZADANIE : Powtarzaj w poniedziałki, środy i piątki przez następne cztery tygodnie.
Tak, jest to trywialne foreachomijanie DatePeriodi używanie if()w pętli; ale nie o to chodzi w tym przykładzie!
$period = new DatePeriod(new DateTime, new DateInterval('P1D'), 28);
$dates = newCallbackFilterIterator($period, function ($date) {
return in_array($date->format('l'), array('Monday', 'Wednesday', 'Friday'));
});
foreach ($dates as $date) { … }
Powyższy fragment kodu nie zadziała, ponieważ CallbackFilterIteratoroczekuje wystąpienia klasy implementującej Iteratorinterfejs, a DatePeriodnie. Ponieważ jednak tak jest Traversable, możemy łatwo spełnić to wymaganie, używając IteratorIterator.
$period = newIteratorIterator(new DatePeriod(…));
Jak widać, nie ma to nic wspólnego z iteracją po klasach iteratorów ani rekursją, i na tym polega różnica między IteratorIteratori RecursiveIteratorIterator.
Podsumowanie
RecursiveIteraratorIteratorsłuży do iteracji po RecursiveIterator(„rekurencyjnym” iteratorze), wykorzystując dostępne zachowanie rekurencyjne.
IteratorIteratorsłuży do stosowania Iteratorzachowania do Traversableobiektów , które nie są iteratorami .
Czy nie jest to IteratorIteratortylko standardowy typ przechodzenia w kolejności liniowej dla Traversableobiektów? Te, które mogłyby być użyte bez tego, tak foreachjak jest? A nawet dalej, czy nie jest RecursiveIteratorzawsze a, Traversablea zatem nie tylko, IteratorIteratorale także RecursiveIteratorIteratorzawsze „do stosowania Iteratorzachowania do obiektów nie-iteracyjnych, przemieszczalnych” ? (Powiedziałbym teraz, że foreachstosuje typ iteracji za pośrednictwem obiektu iteratora na obiektach kontenera, które implementują interfejs typu iteratora, więc są to obiekty-kontenera-iteratora, zawsze Traversable)
hakre
Jak stwierdza moja odpowiedź, IteratorIteratorto klasa, która polega na zawijaniu Traversableobiektów w plik Iterator. Nic więcej . Wydaje się, że używasz tego terminu bardziej ogólnie.
salathe
Pozornie pouczająca odpowiedź. Jedno pytanie, czy RecursiveIteratorIterator również nie zawijałby obiektów, aby miały również dostęp do zachowania Iteratora? Jedyna różnica między nimi polegałaby na tym, że RecursiveIteratorIterator może drążyć w dół, podczas gdy IteratorIterator nie może?
Mike Purcell,
@ salathe, czy wiesz, dlaczego iterator rekurencyjny (RecursiveDirectoryIterator) nie implementuje zachowania hasChildren (), getChildren ()?
anru
8
+1 za powiedzenie „rekurencyjny”. Nazwa przez długi czas zwodziła mnie, ponieważ Recursivein RecursiveIteratorsugeruje zachowanie, podczas gdy bardziej odpowiednia byłaby nazwa opisująca zdolności, np RecursibleIterator.
koza
0
W przypadku użycia z iterator_to_array(), RecursiveIteratorIteratorbędzie rekurencyjnie przeszukiwać tablicę, aby znaleźć wszystkie wartości. Oznacza to, że spłaszczy oryginalną tablicę.
To jest całkowicie mylące. new IteratorIterator(new ArrayIterator($array))to znaczy new ArrayIterator($array), że strona zewnętrzna IteratorIteratornic nie robi. Co więcej, spłaszczanie wyniku nie ma z tym nic wspólnego iterator_to_array- po prostu przekształca iterator w tablicę. Spłaszczenie jest właściwością sposobu, w jaki RecursiveArrayIteratorprzechodzi jego wewnętrzny iterator.
Pytania dotyczące Quolonel
0
RecursiveDirectoryIterator wyświetla całą nazwę ścieżki, a nie tylko nazwę pliku. Reszta wygląda dobrze. Dzieje się tak, ponieważ nazwy plików są generowane przez SplFileInfo. Zamiast tego powinny być wyświetlane jako nazwa basenowa. Żądane wyjście jest następujące:
RecursiveIteratorIterator
działa, czy już zrozumiałeś, jakIteratorIterator
działa? Mam na myśli to, że jest w zasadzie taki sam, tylko interfejs, który jest używany przez te dwa, jest inny. Czy jesteś bardziej zainteresowany kilkoma przykładami, czy chcesz zobaczyć różnice między implementacją kodu C?IteratorIterator
mapyIterator
iIteratorAggregate
do miejscaIterator
, gdzieREcusiveIteratorIterator
jest używany do przemierzania recusivly aRecursiveIterator
Odpowiedzi:
RecursiveIteratorIterator
jest konkretnymIterator
wdrażaniu przechodzenie drzewa . Umożliwia programiście przechodzenie przez obiekt kontenera, który implementujeRecursiveIterator
interfejs. Ogólne zasady, typy, semantyka i wzorce iteratorów można znaleźć w sekcji Iterator w Wikipedii .W odróżnieniu od
IteratorIterator
konkretnejIterator
implementacji przechodzenia przez obiekt w porządku liniowym (i domyślnie akceptującej dowolny rodzajTraversable
w swoim konstruktorze),RecursiveIteratorIterator
umożliwia pętlę po wszystkich węzłach uporządkowanego drzewa obiektów, a jego konstruktor przyjmuje plikRecursiveIterator
.W skrócie:
RecursiveIteratorIterator
pozwala na pętlę po drzewie,IteratorIterator
pozwala na zapętlenie listy. Pokażę to wkrótce z kilkoma przykładami kodu poniżej.Technicznie rzecz biorąc, działa to przez wyrwanie się z liniowości przez przejście przez wszystkie elementy potomne węzłów (jeśli takie istnieją). Jest to możliwe, ponieważ z definicji wszystkie dzieci węzła są ponownie
RecursiveIterator
. Górny poziomIterator
następnie wewnętrznie układa różne stosyRecursiveIterator
według ich głębokości i utrzymuje wskaźnik na bieżącym aktywnym podrzędnymIterator
celu przejścia.Pozwala to na odwiedzenie wszystkich węzłów drzewa.
Podstawowe zasady są takie same jak w przypadku
IteratorIterator
: Interfejs określa typ iteracji, a podstawowa klasa iteratora jest implementacją tej semantyki. Porównaj z poniższymi przykładami, w przypadku pętli liniowejforeach
zwykle nie myśl o szczegółach implementacji, chyba że musisz zdefiniować nowyIterator
(np. Gdy jakiś konkretny typ sam w sobie nie jestTraversable
).W przypadku przemierzania rekurencyjnego - chyba że nie używasz predefiniowanej
Traversal
iteracji przechodzenia, która już ma rekurencyjną iterację przechodzenia - zwykle musisz utworzyć wystąpienie istniejącejRecursiveIteratorIterator
iteracji lub nawet napisać rekurencyjną iterację przechodzenia, która jestTraversable
twoją własną, aby mieć tego typu iterację przechodzeniaforeach
.Krótko mówiąc, różnice techniczne:
IteratorIterator
pobiera dowolneTraversable
dla przechodzenia liniowego,RecursiveIteratorIterator
potrzebuje bardziej szczegółowejRecursiveIterator
pętli po drzewie.IteratorIterator
eksponuje swoją głównąIterator
przelotkęgetInnerIerator()
,RecursiveIteratorIterator
udostępnia bieżące aktywne sub-Iterator
tylko za pomocą tej metody.IteratorIterator
nie jest świadomy niczego takiego jak rodzic lub dzieci,RecursiveIteratorIterator
wie również, jak zdobyć i przemierzać dzieci.IteratorIterator
nie potrzebuje stosu iteratorów,RecursiveIteratorIterator
ma taki stos i zna aktywny pod-iterator.IteratorIterator
ma swoją kolejność ze względu na liniowość i nie ma wyboru,RecursiveIteratorIterator
ma wybór dla dalszego przejścia i musi zdecydować dla każdego węzła (decyduje tryb naRecursiveIteratorIterator
).RecursiveIteratorIterator
ma więcej metod niżIteratorIterator
.Podsumowując:
RecursiveIterator
to konkretny typ iteracji (pętla po drzewie), który działa na własnych iteratorach, a mianowicieRecursiveIterator
. Jest to ta sama podstawowa zasada jak w przypadkuIteratorIerator
, ale typ iteracji jest inny (kolejność liniowa).Idealnie byłoby również stworzyć własny zestaw. Jedyną konieczną rzeczą jest to, że Twój iterator implementuje,
Traversable
co jest możliwe za pośrednictwemIterator
lubIteratorAggregate
. Następnie możesz go używać zforeach
. Na przykład jakiś obiekt iteracji rekurencyjnej przechodzenia przez drzewo trójskładnikowe wraz z odpowiednim interfejsem iteracji dla obiektów kontenera.Przyjrzyjmy się kilku przykładom z życia, które nie są tak abstrakcyjne. Pomiędzy interfejsami, konkretnymi iteratorami, obiektami kontenerów i semantyką iteracji może nie jest to zły pomysł.
Weźmy jako przykład listę katalogów. Weź pod uwagę, że masz na dysku następujące drzewo plików i katalogów:
Podczas gdy iterator z porządkiem liniowym po prostu przechodzi przez folder i pliki najwyższego poziomu (lista pojedynczego katalogu), iterator rekurencyjny przechodzi również przez podfoldery i wyświetla listę wszystkich folderów i plików (lista katalogów z listą jego podkatalogów):
Możesz łatwo porównać to, z
IteratorIterator
którym nie ma rekursji przy przechodzeniu po drzewie katalogów. IRecursiveIteratorIterator
który może przejść do drzewa, jak pokazuje lista Rekurencyjna.Na początek bardzo prosty przykład z
DirectoryIterator
implementacją,Traversable
która pozwalaforeach
na iterację :$path = 'tree'; $dir = new DirectoryIterator($path); echo "[$path]\n"; foreach ($dir as $file) { echo " ├ $file\n"; }
Przykładowy wynik dla powyższej struktury katalogów to:
Jak widać, nie jest to jeszcze używane
IteratorIterator
aniRecursiveIteratorIterator
. Zamiast tego po prostu używaforeach
tego, który działa naTraversable
interfejsie.Ponieważ
foreach
domyślnie zna tylko typ iteracji o nazwie porządek liniowy, możemy chcieć jawnie określić typ iteracji. Na pierwszy rzut oka może się to wydawać zbyt rozwlekłe, ale dla celów demonstracyjnych (i aby różnica byłaRecursiveIteratorIterator
widoczna później), określmy liniowy typ iteracji, wyraźnie określającIteratorIterator
typ iteracji dla listingu 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 to terazIteratorIterator
typ iteracji dlaTraversable
$dir
:$files = new IteratorIterator($dir);
Jak zwykle czynność iteracji jest wykonywana przez
foreach
:foreach ($files as $file) {
Wynik jest dokładnie taki sam. Więc co się zmieniło? Inny jest obiekt używany w
foreach
. W pierwszym przykładzie jest toDirectoryIterator
aw drugim przykładzie jest toIteratorIterator
. Pokazuje to elastyczność, jaką mają iteratory: możesz je zastąpić innymi, kod wewnątrzforeach
po prostu nadal działa zgodnie z oczekiwaniami.Zacznijmy od całej listy, łącznie z podkatalogami.
Ponieważ określiliśmy teraz typ iteracji, rozważmy zmianę go na inny typ iteracji.
Wiemy, że musimy teraz przejść całe drzewo, a nie tylko pierwszy poziom. Aby wykonać tę pracę z prostym
foreach
, potrzebujemy innego typu iteratora:RecursiveIteratorIterator
. I to można tylko iterować po obiektach kontenerów, które mająRecursiveIterator
interfejs .Interfejs jest umową. Każda klasa implementująca ją może być używana razem z
RecursiveIteratorIterator
. Przykładem takiej klasy jestRecursiveDirectoryIterator
, która jest czymś w rodzaju rekurencyjnej odmianyDirectoryIterator
.Zobaczmy pierwszy przykład kodu przed napisaniem jakiegokolwiek 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, ale tworzy inne wyniki:
Okej, nie tak inaczej, nazwa pliku zawiera teraz nazwę ścieżki z przodu, ale reszta również wygląda podobnie.
Jak pokazuje przykład, nawet obiekt katalogu już implementuje
RecursiveIterator
interfejs, ale to nie wystarczy, abyforeach
przejść przez całe drzewo katalogów. TutajRecursiveIteratorIterator
zaczyna się akcja. 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
obiektu spowodujeforeach
przechodzenie przez wszystkie pliki i katalogi w sposób rekurencyjny. Następnie wyświetla listę wszystkich plików, ponieważ typ iteracji obiektu został określony teraz:Powinno to już pokazać różnicę między przechodzeniem płaskim i przechodzeniem po drzewie.
RecursiveIteratorIterator
Jest w stanie przechodzić żadnego drzewiastą strukturę w postaci listy elementów. Ponieważ jest więcej informacji (takich jak poziom, na którym obecnie odbywa się iteracja), można uzyskać dostęp do obiektu iteratora podczas iteracji po nim i na przykład wcinać dane wyjściowe:echo "[$path]\n"; foreach ($files as $file) { $indent = str_repeat(' ', $files->getDepth()); echo $indent, " ├ $file\n"; }
Wynik przykładu 5 :
Oczywiście to nie wygrywa konkursu piękności, ale pokazuje, że w przypadku iteratora rekurencyjnego dostępnych jest więcej informacji niż tylko liniowy porządek klucza i wartości . Nawet
foreach
może wyrazić ten rodzaj liniowości, dostęp do samego iteratora pozwala uzyskać więcej informacji.Podobnie jak w przypadku metainformacji, istnieją również różne sposoby poruszania się po drzewie, a tym samym porządkowania wyników. To jest tryb programu
RecursiveIteratorIterator
i można go ustawić za pomocą konstruktora.W następnym przykładzie
RecursiveDirectoryIterator
polecenie usunie kropki (.
i..
), ponieważ ich nie potrzebujemy. Ale także tryb rekursji zostanie zmieniony, aby najpierw zająć element nadrzędny (podkatalog) (SELF_FIRST
) przed dziećmi (pliki i podkatalogi 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"; }
Dane wyjściowe pokazują teraz poprawnie wymienione pozycje podkatalogów, jeśli porównasz z poprzednim wyjściem, których tam nie było:
Dlatego tryb rekurencyjny kontroluje, co i kiedy zwracane jest ramię lub liść w drzewie, dla przykładu katalogu:
LEAVES_ONLY
(domyślnie): Tylko lista plików, bez katalogów.SELF_FIRST
(powyżej): Lista katalogu, a następnie plików w nim.CHILD_FIRST
(bez przykładu): Najpierw wyświetla listę plików w podkatalogu, a następnie w katalogu.Dane wyjściowe przykładu 5 z dwoma innymi trybami:
Porównując to ze standardowym przechodzeniem, wszystkie te rzeczy nie są dostępne. Dlatego iteracja rekurencyjna jest nieco bardziej złożona, gdy trzeba ją owinąć, jednak jest łatwa w użyciu, ponieważ zachowuje się jak iterator, umieszcza się ją w a
foreach
i gotowe.Myślę, że to wystarczające przykłady na jedną odpowiedź. Możesz znaleźć pełny kod źródłowy, a także przykład, aby wyświetlić ładnie wyglądające drzewa ascii w tym skrócie: https://gist.github.com/3599532
Przykład 5 wykazał, że są dostępne metainformacje o stanie iteratora. Jednak zostało to celowo zademonstrowane w ramach
foreach
iteracji. W prawdziwym życiu to naturalnie należy doRecursiveIterator
.Lepszym przykładem jest
RecursiveTreeIterator
, zajmuje się wcięciami, przedrostkami 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));
RecursiveTreeIterator
Przeznaczony jest do pracy przy linii linii, wyjście jest dość proste z jednym małym problemem:W połączeniu z a
RecursiveDirectoryIterator
wyświetla całą nazwę ścieżki, a nie tylko nazwę pliku. Reszta wygląda dobrze. Dzieje się tak, ponieważ nazwy plików są generowane przezSplFileInfo
. Zamiast tego powinny być wyświetlane jako nazwa basenowa. Żądane wyjście jest następujące:/// Solved /// [tree] ├ dirA │ ├ dirB │ │ └ fileD │ ├ fileB │ └ fileC └ fileA
Utwórz klasę dekoratora, której można używać
RecursiveTreeIterator
zamiastRecursiveDirectoryIterator
. Powinien zawierać nazwę podstawową prąduSplFileInfo
zamiast ścieżki. Ostateczny fragment kodu mógłby 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ą istoty w Dodatku: Zrób to sam: StwórzRecursiveTreeIterator
pracę wiersz po wierszu. .źródło
RecursiveIteratorIterator
ponieważ jest to wspólne z innymi typami, ale podałem kilka informacji technicznych, jak to faktycznie działa. Myślę, że przykłady dobrze pokazują różnice: rodzaj iteracji jest główną różnicą między nimi. Nie mam pojęcia, jeśli kupisz typ iteracji, który ubijesz trochę inaczej, ale IMHO nie jest łatwe z semantyką typów iteracji.Aby zrozumieć różnicę między tymi dwoma iteratorami, należy najpierw trochę zrozumieć stosowane konwencje nazewnictwa i co rozumiemy przez iteratory „rekurencyjne”.
Iteratory rekurencyjne i nierekurencyjne
PHP ma nie „rekurencyjne” iteratory, takie jak
ArrayIterator
iFilesystemIterator
. Istnieją również „rekurencyjne” iteratory, takie jakRecursiveArrayIterator
iRecursiveDirectoryIterator
. Ci drudzy mają metody umożliwiające ich wnikanie, ci pierwsi nie.Gdy instancje tych iteratorów są zapętlone samodzielnie, nawet te rekurencyjne, wartości pochodzą tylko z „najwyższego” poziomu, nawet jeśli są zapętlone po zagnieżdżonej tablicy lub katalogu z podkatalogami.
Iteratory rekurencyjne implementują zachowanie rekurencyjne (via
hasChildren()
,getChildren()
), ale tego nie robią wykorzystują .Lepiej byłoby myśleć o iteratorach rekurencyjnych jako o iteratorach „rekurencyjnych”, mają one rozszerzenie zdolność do iteracji rekurencyjnej, ale zwykłe iterowanie po instancji jednej z tych klas tego nie zrobi. Aby wykorzystać zachowanie rekurencyjne, czytaj dalej.
RecursiveIteratorIterator
Tutaj właśnie
RecursiveIteratorIterator
pojawia się gra. Posiada wiedzę na temat wywoływania „rekurencyjnych” iteratorów w taki sposób, aby drążyć strukturę w normalnej, płaskiej pętli. Wprowadza rekurencyjne zachowanie do działania. Zasadniczo wykonuje pracę polegającą na przechodzeniu przez każdą z wartości w iteratorze, sprawdzaniu, czy są „dzieci”, do których można się powrócić, czy nie, oraz wchodzeniu i wychodzeniu z tych zbiorów dzieci. Wbijasz wystąpienieRecursiveIteratorIterator
w foreach, a on nurkuje w strukturze, więc nie musisz tego robić.Gdyby
RecursiveIteratorIterator
nie zostało użyte, musiałbyś napisać własne pętle rekurencyjne, aby wykorzystać zachowanie rekurencyjne, sprawdzając iterator „rekurencyjny”hasChildren()
i używającgetChildren()
.Więc to jest krótkie omówienie
RecursiveIteratorIterator
, czym się różni odIteratorIterator
? Cóż, w zasadzie zadajesz to samo pytanie, co Jaka jest różnica między kotkiem a drzewem? To, że oba pojawiają się w tej samej encyklopedii (lub podręczniku dla iteratorów), nie oznacza, że powinieneś się pomylić między nimi.IteratorIterator
Zadaniem tego
IteratorIterator
jest pobranie dowolnegoTraversable
obiektu i owinięcie go tak, aby spełniał wymaganiaIterator
interfejsu. Służy do tego możliwość zastosowania zachowania specyficznego dla iteratora na obiekcie niebędącym iteratorem.Aby dać praktyczny przykład,
DatePeriod
klasa jest,Traversable
ale nie jestIterator
. W związku z tym możemy zapętlić jego wartości za pomocą,foreach()
ale nie możemy zrobić innych rzeczy, które normalnie wykonalibyśmy za pomocą iteratora, takich jak filtrowanie.ZADANIE : Powtarzaj w poniedziałki, środy i piątki przez następne cztery tygodnie.
Tak, jest to trywialne
foreach
omijanieDatePeriod
i używanieif()
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 kodu nie zadziała, ponieważ
CallbackFilterIterator
oczekuje wystąpienia klasy implementującejIterator
interfejs, aDatePeriod
nie. Ponieważ jednak tak jestTraversable
, możemy łatwo spełnić to wymaganie, używającIteratorIterator
.$period = new IteratorIterator(new DatePeriod(…));
Jak widać, nie ma to nic wspólnego z iteracją po klasach iteratorów ani rekursją, i na tym polega różnica między
IteratorIterator
iRecursiveIteratorIterator
.Podsumowanie
RecursiveIteraratorIterator
służy do iteracji poRecursiveIterator
(„rekurencyjnym” iteratorze), wykorzystując dostępne zachowanie rekurencyjne.IteratorIterator
służy do stosowaniaIterator
zachowania doTraversable
obiektów , które nie są iteratorami .źródło
IteratorIterator
tylko standardowy typ przechodzenia w kolejności liniowej dlaTraversable
obiektów? Te, które mogłyby być użyte bez tego, takforeach
jak jest? A nawet dalej, czy nie jestRecursiveIterator
zawsze a,Traversable
a zatem nie tylko,IteratorIterator
ale takżeRecursiveIteratorIterator
zawsze „do stosowaniaIterator
zachowania do obiektów nie-iteracyjnych, przemieszczalnych” ? (Powiedziałbym teraz, żeforeach
stosuje typ iteracji za pośrednictwem obiektu iteratora na obiektach kontenera, które implementują interfejs typu iteratora, więc są to obiekty-kontenera-iteratora, zawszeTraversable
)IteratorIterator
to klasa, która polega na zawijaniuTraversable
obiektów w plikIterator
. Nic więcej . Wydaje się, że używasz tego terminu bardziej ogólnie.Recursive
inRecursiveIterator
sugeruje zachowanie, podczas gdy bardziej odpowiednia byłaby nazwa opisująca zdolności, npRecursibleIterator
.W przypadku użycia z
iterator_to_array()
,RecursiveIteratorIterator
będzie rekurencyjnie przeszukiwać tablicę, aby znaleźć wszystkie wartości. Oznacza to, że spłaszczy oryginalną tablicę.IteratorIterator
zachowa pierwotną strukturę hierarchiczną.Ten przykład jasno pokaże 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));
źródło
new IteratorIterator(new ArrayIterator($array))
to znaczynew ArrayIterator($array)
, że strona zewnętrznaIteratorIterator
nic nie robi. Co więcej, spłaszczanie wyniku nie ma z tym nic wspólnegoiterator_to_array
- po prostu przekształca iterator w tablicę. Spłaszczenie jest właściwością sposobu, w jakiRecursiveArrayIterator
przechodzi jego wewnętrzny iterator.RecursiveDirectoryIterator wyświetla całą nazwę ścieżki, a nie tylko nazwę pliku. Reszta wygląda dobrze. Dzieje się tak, ponieważ nazwy plików są generowane przez SplFileInfo. Zamiast tego powinny być wyświetlane jako nazwa basenowa. Żą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"; } }
wynik:
źródło