Si vous demandez comment RecursiveIteratorIteratorfonctionne, avez-vous déjà compris comment IteratorIteratorfonctionne? Je veux dire que c'est fondamentalement le même, seule l'interface qui est consommée par les deux est différente. Et êtes-vous plus intéressé par quelques exemples ou voulez-vous voir le diff de l'implémentation du code C sous-jacent?
hakre
@Gordon je ne savais pas comment une seule boucle foreach peut traverser tous les éléments de la structure arborescente
varuog
@hakra j'essaie maintenant d'étudier toutes les interfaces intégrées ainsi que l'interface spl et l'implémentation de l'itérateur.J'étais intéressé de savoir comment cela fonctionne en arrière-plan avec la boucle forach avec quelques exemples.
varuog
@hakre Ils sont tous les deux très différents. IteratorIteratorcartes Iteratoret IteratorAggregatedans un Iterator, où REcusiveIteratorIteratorest utilisé pour traverser récusivement aRecursiveIterator
Adam
Réponses:
249
RecursiveIteratorIteratorest une traversée d'arbre d'Iterator implémentation concrète . Il permet à un programmeur de parcourir un objet conteneur qui implémente l' interface, voir Iterator dans Wikipedia pour les principes généraux, les types, la sémantique et les modèles d'itérateurs.RecursiveIterator
À la différence de IteratorIteratorlaquelle se trouve un Iteratorparcours d'objet concret implémentant dans un ordre linéaire (et par défaut acceptant n'importe quel type de Traversabledans son constructeur), le RecursiveIteratorIteratorpermet de boucler sur tous les nœuds dans un arbre ordonné d'objets et son constructeur prend un RecursiveIterator.
En bref: RecursiveIteratorIteratorvous permet de boucler sur un arbre, IteratorIteratorvous permet de boucler sur une liste. Je montre cela avec quelques exemples de code ci-dessous bientôt.
Techniquement, cela fonctionne en brisant la linéarité en parcourant tous les enfants d'un nœud (le cas échéant). Cela est possible car, par définition, tous les enfants d'un nœud sont à nouveau un RecursiveIterator. Le niveau supérieur Iteratorempile ensuite en interne les différents RecursiveIterators par leur profondeur et garde un pointeur vers le sous-marin actif actuel Iteratorpour le parcours.
Cela permet de visiter tous les nœuds d'un arbre.
Les principes sous-jacents sont les mêmes qu'avec IteratorIterator: Une interface spécifie le type d'itération et la classe d'itérateur de base est l'implémentation de cette sémantique. Comparez avec les exemples ci-dessous, pour une boucle linéaire avec foreachvous, normalement, ne pensez pas beaucoup aux détails de l'implémentation à moins que vous n'ayez besoin d'en définir un nouveau Iterator(par exemple, lorsqu'un type concret lui-même ne l'implémente pas Traversable).
Pour une traversée récursive - à moins que vous n'utilisiez une Traversalitération de parcours récursive déjà définie - vous devez normalement instancier l' RecursiveIteratorIteratoritération existante ou même écrire une itération de parcours récursive qui est Traversablela vôtre pour avoir ce type d'itération de traversée avec foreach.
Astuce: vous n'avez probablement pas implémenté l'un ni l'autre le vôtre, donc cela pourrait valoir la peine de le faire pour votre expérience pratique des différences qu'ils ont. Vous trouvez une suggestion de bricolage à la fin de la réponse.
Différences techniques en bref:
Alors que IteratorIteratorprend n'importe quel Traversablepour un parcours linéaire, RecursiveIteratorIteratornécessite une RecursiveIteratorboucle plus spécifique sur un arbre.
Où IteratorIteratorexpose son Iteratorvia principal getInnerIerator(), RecursiveIteratorIteratorfournit le sous-système actif actuel Iteratoruniquement via cette méthode.
Bien que IteratorIteratorne sache absolument rien comme un parent ou des enfants, il RecursiveIteratorIteratorsait également comment obtenir et traverser des enfants.
IteratorIteratorn'a pas besoin d'une pile d'itérateurs, RecursiveIteratorIteratorpossède une telle pile et connaît le sous-itérateur actif.
Où IteratorIteratora son ordre en raison de la linéarité et de l'absence de choix, RecursiveIteratorIteratora le choix pour une traversée ultérieure et doit décider pour chaque nœud (décidé via le mode parRecursiveIteratorIterator ).
RecursiveIteratorIteratora plus de méthodes que IteratorIterator.
Pour résumer: RecursiveIteratorest un type concret d'itération (boucle sur un arbre) qui fonctionne sur ses propres itérateurs, à savoir RecursiveIterator. C'est le même principe sous-jacent que avec IteratorIerator, mais le type d'itération est différent (ordre linéaire).
Idéalement, vous pouvez également créer votre propre ensemble. La seule chose nécessaire est que votre itérateur implémente Traversablece qui est possible via Iteratorou IteratorAggregate. Ensuite, vous pouvez l'utiliser avec foreach. Par exemple, une sorte d'objet d'itération récursive de traversée d'arbre ternaire avec l'interface d'itération correspondante pour le ou les objets conteneurs.
Passons en revue avec quelques exemples réels qui ne sont pas si abstraits. Entre interfaces, itérateurs concrets, objets conteneurs et sémantique d'itération, ce n'est peut-être pas une si mauvaise idée.
Prenons une liste de répertoires comme exemple. Considérez que vous avez le fichier et l'arborescence de répertoires suivants sur le disque:
Alors qu'un itérateur avec un ordre linéaire traverse simplement le dossier et les fichiers de niveau supérieur (une seule liste de répertoires), l'itérateur récursif traverse également les sous-dossiers et répertorie tous les dossiers et fichiers (une liste de répertoires avec les listes de ses sous-répertoires):
Non-RecursiveRecursive======================[tree][tree]├ dirA ├ dirA
└ fileA │├ dirB
││└ fileD
│├ fileB
│└ fileC
└ fileA
Vous pouvez facilement comparer cela avec IteratorIteratorlequel ne fait aucune récursivité pour parcourir l'arborescence de répertoires. Et le RecursiveIteratorIteratorqui peut traverser dans l'arbre comme le montre la liste récursive.
L'exemple de sortie pour la structure de répertoires ci-dessus est alors:
[tree]├.├..├ dirA
├ fileA
Comme vous le voyez, cela n'utilise pas encore IteratorIteratorou RecursiveIteratorIterator. Au lieu de cela, il suffit d'utiliser foreachqui fonctionne sur l' Traversableinterface.
Comme foreachpar défaut ne connaît que le type d'itération nommé ordre linéaire, nous pourrions vouloir spécifier le type d'itération explicitement. À première vue, cela peut sembler trop détaillé, mais à des fins de démonstration (et pour faire la différence avec RecursiveIteratorIteratorplus de visibilité plus tard), spécifions le type d'itération linéaire en spécifiant explicitement le IteratorIteratortype d'itération pour la liste des répertoires:
$files =newIteratorIterator($dir);
echo "[$path]\n";foreach($files as $file){
echo " ├ $file\n";}
Cet exemple est presque identique au premier, la différence est qu'il $filess'agit désormais d'un IteratorIteratortype d'itération pour Traversable$dir:
$files =newIteratorIterator($dir);
Comme d'habitude, l'acte d'itération est effectué par foreach:
foreach($files as $file){
La sortie est exactement la même. Alors qu'est-ce qui est différent? L'objet utilisé dans le foreach. Dans le premier exemple, c'est un DirectoryIteratordans le deuxième exemple, c'est le IteratorIterator. Cela montre la flexibilité des itérateurs: vous pouvez les remplacer les uns par les autres, le code à l'intérieur foreachcontinue de fonctionner comme prévu.
Permet de commencer à obtenir la liste complète, y compris les sous-répertoires.
Comme nous avons maintenant spécifié le type d'itération, considérons de le changer en un autre type d'itération.
Nous savons que nous devons parcourir tout l'arbre maintenant, pas seulement le premier niveau. Avoir ce travail avec un simple foreachnous avons besoin d' un autre type de iterator: RecursiveIteratorIterator. Et que l'on ne peut itérer que sur les objets conteneurs qui ont l' RecursiveIteratorinterface .
L'interface est un contrat. Toute classe l'implémentant peut être utilisée avec le RecursiveIteratorIterator. Un exemple d'une telle classe est le RecursiveDirectoryIterator, qui est quelque chose comme la variante récursive de DirectoryIterator.
Voyons un premier exemple de code avant d'écrire une autre phrase avec le mot I:
$dir =newRecursiveDirectoryIterator($path);
echo "[$path]\n";foreach($dir as $file){
echo " ├ $file\n";}
Ce troisième exemple est presque identique au premier, mais il crée une sortie différente:
[tree]├ tree\.
├ tree\..├ tree\dirA
├ tree\fileA
D'accord, pas si différent, le nom de fichier contient maintenant le chemin d'accès devant, mais le reste se ressemble également.
Comme le montre l'exemple, même l'objet annuaire imprègne déjà l' RecursiveIteratorinterface, ce n'est pas encore suffisant pour faire foreachparcourir toute l'arborescence d'annuaire. C'est là que le RecursiveIteratorIteratorentre en action. L'exemple 4 montre comment:
$files =newRecursiveIteratorIterator($dir);
echo "[$path]\n";foreach($files as $file){
echo " ├ $file\n";}
Utiliser le à la RecursiveIteratorIteratorplace de l' $dirobjet précédent fera foreachparcourir tous les fichiers et répertoires de manière récursive. Cela répertorie ensuite tous les fichiers, car le type d'itération d'objet a été spécifié maintenant:
Cela devrait déjà démontrer la différence entre la traversée à plat et l'arborescence. Le RecursiveIteratorIteratorest capable de parcourir n'importe quelle structure arborescente sous forme de liste d'éléments. Comme il y a plus d'informations (comme le niveau auquel l'itération a lieu actuellement), il est possible d'accéder à l'objet itérateur en l'itérant et par exemple indenter la sortie:
Bien sûr, cela ne gagne pas un concours de beauté, mais cela montre qu'avec l'itérateur récursif, il y a plus d'informations disponibles que juste l'ordre linéaire de la clé et de la valeur . Même foreachne peut qu'exprimer ce genre de linéarité, accéder à l'itérateur lui-même permet d'obtenir plus d'informations.
Comme pour les méta-informations, il existe également différentes manières de parcourir l'arbre et donc de classer la sortie. C'est le mode duRecursiveIteratorIterator et il peut être défini avec le constructeur.
L'exemple suivant indiquera au RecursiveDirectoryIteratorde supprimer les entrées de points ( .et ..) car nous n'en avons pas besoin. Mais aussi le mode de récursivité sera changé pour prendre l'élément parent (le sous-répertoire) en premier ( SELF_FIRST) avant les enfants (les fichiers et sous-sous-répertoires dans le sous-répertoire):
La sortie affiche maintenant les entrées de sous-répertoire correctement répertoriées, si vous comparez avec la sortie précédente, celles-ci n'étaient pas là:
Lorsque vous comparez cela avec le parcours standard, toutes ces choses ne sont pas disponibles. L'itération récursive est donc un peu plus complexe lorsque vous devez l'enrouler la tête, mais elle est facile à utiliser car elle se comporte comme un itérateur, vous la mettez dans un foreachet c'est fait.
Je pense que ce sont suffisamment d'exemples pour une réponse. Vous pouvez trouver le code source complet ainsi qu'un exemple pour afficher de jolis ascii-arbres dans cet article: https://gist.github.com/3599532
Faites-le vous-même: faites le RecursiveTreeIteratortravail ligne par ligne.
L'exemple 5 a démontré qu'il existe des méta-informations sur l'état de l'itérateur. Cependant, cela a été délibérément démontré dans l' foreachitération. Dans la vraie vie, cela appartient naturellement au RecursiveIterator.
Un meilleur exemple est le RecursiveTreeIterator, il s'occupe de l'indentation, du préfixe et ainsi de suite. Voir le fragment de code suivant:
Lorsqu'il est utilisé en combinaison avec a, RecursiveDirectoryIteratoril affiche le chemin d'accès complet et pas seulement le nom du fichier. Le reste semble bon. C'est parce que les noms de fichiers sont générés par SplFileInfo. Celles-ci devraient être affichées comme nom de base à la place. La sortie souhaitée est la suivante:
Créez une classe de décorateur qui peut être utilisée avec RecursiveTreeIteratorau lieu de RecursiveDirectoryIterator. Il doit fournir le nom de base du courant SplFileInfoau lieu du chemin. Le fragment de code final pourrait alors ressembler à:
Ces fragments $unicodeTreePrefixfont partie de l'essentiel de l' annexe: Faites-le vous-même: faites le RecursiveTreeIteratortravail ligne par ligne. .
Il ne répond pas aux questions posées, contient des erreurs factuelles et manque des points essentiels lorsque vous passez à la personnalisation de l'itération. Dans l'ensemble, cela ressemble à une mauvaise tentative d'obtenir la prime sur un sujet dont vous ne savez pas grand-chose ou, si vous le savez, ne peut pas se transformer en une réponse aux questions posées.
salathe le
2
Eh bien, ce qui ne répond pas à ma question "pourquoi", il suffit de faire plus de mots sans en dire beaucoup. Peut-être que vous commencez avec une erreur qui compte? Montrez-le, ne le gardez pas secret.
hakre
La toute première phrase est incorrecte: "RecursiveIteratorIterator est un IteratorIterator qui prend en charge…", ce n'est pas vrai.
salathe le
1
@salathe: Merci de vos commentaires. J'ai édité la réponse pour y répondre. La première phrase était en effet erronée et incomplète. J'ai encore omis les détails d'implémentation concrets de RecursiveIteratorIteratorparce que c'est en commun avec d'autres types, mais j'ai donné quelques informations techniques sur la façon dont cela fonctionne réellement. Je pense que les exemples montrent bien les différences: le type d'itération est la principale différence entre les deux. Aucune idée si vous achetez le type d'itération que vous créez un peu différemment, mais à mon humble avis, pas facile avec les types d'itération sémantiques.
hakre le
1
La première partie est quelque peu améliorée, mais une fois que vous commencez à passer aux exemples, elle vire toujours à des inexactitudes factuelles. Si vous coupiez la réponse à la règle horizontale, ce serait beaucoup mieux.
salathe le
31
Quelle est la différence entre IteratorIteratoret RecursiveIteratorIterator?
Pour comprendre la différence entre ces deux itérateurs, il faut d'abord comprendre un peu les conventions de dénomination utilisées et ce que l'on entend par itérateurs «récursifs».
Itérateurs récursifs et non récursifs
PHP a des itérateurs non "récursifs", tels que ArrayIteratoret FilesystemIterator. Il existe également des itérateurs "récursifs" tels que RecursiveArrayIteratoret RecursiveDirectoryIterator. Ces derniers ont des méthodes permettant de les approfondir, les premiers non.
Lorsque les instances de ces itérateurs sont bouclées d'elles-mêmes, même récursives, les valeurs ne proviennent que du niveau "supérieur" même si elles bouclent sur un tableau ou un répertoire imbriqué avec des sous-répertoires.
Les itérateurs récursifs implémentent un comportement récursif (via hasChildren(), getChildren()) mais ne l' exploitent pas.
Il vaudrait peut-être mieux considérer les itérateurs récursifs comme des itérateurs «récursifs», ils ont la capacité d'être itérés de manière récursive mais simplement itérer sur une instance de l'une de ces classes ne le fera pas. Pour exploiter le comportement récursif, continuez à lire.
RecursiveIteratorIterator
C'est là que le jeu RecursiveIteratorIteratorentre en jeu. Il sait comment appeler les itérateurs "récursibles" de manière à explorer la structure en une boucle normale et plate. Il met en action le comportement récursif. Il fait essentiellement le travail de franchir chacune des valeurs de l'itérateur, de chercher à voir s'il y a des «enfants» dans lesquels rentrer ou non, et d'entrer et de sortir de ces collections d'enfants. Vous collez une instance de RecursiveIteratorIteratordans un foreach, et elle plonge dans la structure pour que vous n'ayez pas à le faire.
Si le RecursiveIteratorIteratorn'était pas utilisé, vous auriez à écrire vos propres boucles récursives pour exploiter le comportement récursif, en comparant les itérateurs "récursibles" hasChildren()et en utilisant getChildren().
Voilà donc un bref aperçu de RecursiveIteratorIterator, en quoi est-ce différent IteratorIterator? Eh bien, vous posez fondamentalement le même genre de question que Quelle est la différence entre un chaton et un arbre? Ce n'est pas parce que les deux apparaissent dans la même encyclopédie (ou manuel, pour les itérateurs) que vous devriez être confus entre les deux.
ItérateurIterator
Le travail du IteratorIteratorest de prendre n'importe quel Traversableobjet et de l'envelopper de telle sorte qu'il satisfasse l' Iteratorinterface. Une utilisation pour cela est de pouvoir ensuite appliquer un comportement spécifique à l'itérateur sur l'objet non-itérateur.
Pour donner un exemple pratique, la DatePeriodclasse est Traversablemais pas un Iterator. En tant que tel, nous pouvons boucler ses valeurs avec foreach()mais ne pouvons pas faire d'autres choses que nous ferions habituellement avec un itérateur, comme le filtrage.
TÂCHE : Boucle les lundis, mercredis et vendredis des quatre prochaines semaines.
Oui, c'est trivial en foreachpassant par- dessus DatePeriodet en utilisant un if()dans la boucle; mais ce n'est pas le but de cet exemple!
$period =newDatePeriod(newDateTime,newDateInterval('P1D'),28);
$dates =newCallbackFilterIterator($period,function($date){return in_array($date->format('l'), array('Monday','Wednesday','Friday'));});foreach($dates as $date){…}
L'extrait de code ci-dessus ne fonctionnera pas car le CallbackFilterIteratorattend une instance d'une classe qui implémente l' Iteratorinterface, ce qui DatePeriodne fonctionne pas. Cependant, comme c'est le cas, Traversablenous pouvons facilement satisfaire cette exigence en utilisant IteratorIterator.
$period =newIteratorIterator(newDatePeriod(…));
Comme vous pouvez le voir, cela n'a rien à voir avec l'itération sur les classes d'itérateur ni la récursivité, et c'est là que réside la différence entre IteratorIteratoret RecursiveIteratorIterator.
Résumé
RecursiveIteraratorIteratorest pour itérer sur un RecursiveIterator(itérateur "récursible"), en exploitant le comportement récursif disponible.
IteratorIteratorest pour appliquer un Iteratorcomportement à des Traversableobjets non itérateurs .
N'est-ce pas IteratorIteratorsimplement le type standard de parcours d'ordre linéaire pour les Traversableobjets? Celles qui pourraient être utilisées sans elles, foreachtelles quelles ? Et encore plus, n'est-ce pas RecursiveIteratortoujours un Traversableet donc pas seulement IteratorIteratormais aussi RecursiveIteratorIteratortoujours "pour appliquer un Iteratorcomportement à des objets non itérateurs, Traversables" ? (Je dirais maintenant foreachapplique le type d'itération via l'objet iterator sur les objets conteneur qui implémentent une interface de type itérateur, donc ce sont toujours des objets iterator-container-objects Traversable)
hakre
Comme l'indique ma réponse, IteratorIteratorest une classe qui consiste à envelopper des Traversableobjets dans un fichier Iterator. Rien de plus . Vous semblez appliquer le terme plus généralement.
salathe le
Réponse apparemment informative. Une question, RecursiveIteratorIterator n'emballerait-il pas également les objets afin qu'ils aient également accès au comportement Iterator? La seule différence entre les deux serait que le RecursiveIteratorIterator peut explorer vers le bas, alors que le IteratorIterator ne le peut pas?
Mike Purcell
@salathe, savez-vous pourquoi l'itérateur récursif (RecursiveDirectoryIterator) n'implémente pas le comportement hasChildren (), getChildren ()?
anru
7
+1 pour dire "récursible". Le nom m'a induit Recursiveen erreur pendant longtemps car le in RecursiveIteratorimplique un comportement, alors qu'un nom plus approprié aurait été celui qui décrit la capacité, comme RecursibleIterator.
chèvre
0
Lorsqu'il est utilisé avec iterator_to_array(), RecursiveIteratorIteratorparcourra récursivement le tableau pour trouver toutes les valeurs. Cela signifie qu'il aplatira le tableau d'origine.
IteratorIterator conservera la structure hiérarchique d'origine.
Cet exemple vous montrera clairement la différence:
C'est totalement trompeur. new IteratorIterator(new ArrayIterator($array))équivaut à new ArrayIterator($array), c'est-à-dire que l'extérieur IteratorIteratorne fait rien. De plus, l'aplatissement de la sortie n'a rien à voir avec iterator_to_array- il convertit simplement l'itérateur en tableau. L'aplatissement est une propriété du chemin RecursiveArrayIteratorparcouru par son itérateur interne.
Questions Quolonel
0
RecursiveDirectoryIterator il affiche le chemin d'accès complet et pas seulement le nom du fichier. Le reste a l'air bien. C'est parce que les noms de fichiers sont générés par SplFileInfo. Celles-ci devraient être affichées comme nom de base à la place. La sortie souhaitée est la suivante:
RecursiveIteratorIterator
fonctionne, avez-vous déjà compris commentIteratorIterator
fonctionne? Je veux dire que c'est fondamentalement le même, seule l'interface qui est consommée par les deux est différente. Et êtes-vous plus intéressé par quelques exemples ou voulez-vous voir le diff de l'implémentation du code C sous-jacent?IteratorIterator
cartesIterator
etIteratorAggregate
dans unIterator
, oùREcusiveIteratorIterator
est utilisé pour traverser récusivement aRecursiveIterator
Réponses:
RecursiveIteratorIterator
est une traversée d'arbre d'Iterator
implémentation concrète . Il permet à un programmeur de parcourir un objet conteneur qui implémente l' interface, voir Iterator dans Wikipedia pour les principes généraux, les types, la sémantique et les modèles d'itérateurs.RecursiveIterator
À la différence de
IteratorIterator
laquelle se trouve unIterator
parcours d'objet concret implémentant dans un ordre linéaire (et par défaut acceptant n'importe quel type deTraversable
dans son constructeur), leRecursiveIteratorIterator
permet de boucler sur tous les nœuds dans un arbre ordonné d'objets et son constructeur prend unRecursiveIterator
.En bref:
RecursiveIteratorIterator
vous permet de boucler sur un arbre,IteratorIterator
vous permet de boucler sur une liste. Je montre cela avec quelques exemples de code ci-dessous bientôt.Techniquement, cela fonctionne en brisant la linéarité en parcourant tous les enfants d'un nœud (le cas échéant). Cela est possible car, par définition, tous les enfants d'un nœud sont à nouveau un
RecursiveIterator
. Le niveau supérieurIterator
empile ensuite en interne les différentsRecursiveIterator
s par leur profondeur et garde un pointeur vers le sous-marin actif actuelIterator
pour le parcours.Cela permet de visiter tous les nœuds d'un arbre.
Les principes sous-jacents sont les mêmes qu'avec
IteratorIterator
: Une interface spécifie le type d'itération et la classe d'itérateur de base est l'implémentation de cette sémantique. Comparez avec les exemples ci-dessous, pour une boucle linéaire avecforeach
vous, normalement, ne pensez pas beaucoup aux détails de l'implémentation à moins que vous n'ayez besoin d'en définir un nouveauIterator
(par exemple, lorsqu'un type concret lui-même ne l'implémente pasTraversable
).Pour une traversée récursive - à moins que vous n'utilisiez une
Traversal
itération de parcours récursive déjà définie - vous devez normalement instancier l'RecursiveIteratorIterator
itération existante ou même écrire une itération de parcours récursive qui estTraversable
la vôtre pour avoir ce type d'itération de traversée avecforeach
.Différences techniques en bref:
IteratorIterator
prend n'importe quelTraversable
pour un parcours linéaire,RecursiveIteratorIterator
nécessite uneRecursiveIterator
boucle plus spécifique sur un arbre.IteratorIterator
expose sonIterator
via principalgetInnerIerator()
,RecursiveIteratorIterator
fournit le sous-système actif actuelIterator
uniquement via cette méthode.IteratorIterator
ne sache absolument rien comme un parent ou des enfants, ilRecursiveIteratorIterator
sait également comment obtenir et traverser des enfants.IteratorIterator
n'a pas besoin d'une pile d'itérateurs,RecursiveIteratorIterator
possède une telle pile et connaît le sous-itérateur actif.IteratorIterator
a son ordre en raison de la linéarité et de l'absence de choix,RecursiveIteratorIterator
a le choix pour une traversée ultérieure et doit décider pour chaque nœud (décidé via le mode parRecursiveIteratorIterator
).RecursiveIteratorIterator
a plus de méthodes queIteratorIterator
.Pour résumer:
RecursiveIterator
est un type concret d'itération (boucle sur un arbre) qui fonctionne sur ses propres itérateurs, à savoirRecursiveIterator
. C'est le même principe sous-jacent que avecIteratorIerator
, mais le type d'itération est différent (ordre linéaire).Idéalement, vous pouvez également créer votre propre ensemble. La seule chose nécessaire est que votre itérateur implémente
Traversable
ce qui est possible viaIterator
ouIteratorAggregate
. Ensuite, vous pouvez l'utiliser avecforeach
. Par exemple, une sorte d'objet d'itération récursive de traversée d'arbre ternaire avec l'interface d'itération correspondante pour le ou les objets conteneurs.Passons en revue avec quelques exemples réels qui ne sont pas si abstraits. Entre interfaces, itérateurs concrets, objets conteneurs et sémantique d'itération, ce n'est peut-être pas une si mauvaise idée.
Prenons une liste de répertoires comme exemple. Considérez que vous avez le fichier et l'arborescence de répertoires suivants sur le disque:
Alors qu'un itérateur avec un ordre linéaire traverse simplement le dossier et les fichiers de niveau supérieur (une seule liste de répertoires), l'itérateur récursif traverse également les sous-dossiers et répertorie tous les dossiers et fichiers (une liste de répertoires avec les listes de ses sous-répertoires):
Vous pouvez facilement comparer cela avec
IteratorIterator
lequel ne fait aucune récursivité pour parcourir l'arborescence de répertoires. Et leRecursiveIteratorIterator
qui peut traverser dans l'arbre comme le montre la liste récursive.Au début, un exemple très basique avec un
DirectoryIterator
qui implémenteTraversable
qui permetforeach
d' itérer dessus:L'exemple de sortie pour la structure de répertoires ci-dessus est alors:
Comme vous le voyez, cela n'utilise pas encore
IteratorIterator
ouRecursiveIteratorIterator
. Au lieu de cela, il suffit d'utiliserforeach
qui fonctionne sur l'Traversable
interface.Comme
foreach
par défaut ne connaît que le type d'itération nommé ordre linéaire, nous pourrions vouloir spécifier le type d'itération explicitement. À première vue, cela peut sembler trop détaillé, mais à des fins de démonstration (et pour faire la différence avecRecursiveIteratorIterator
plus de visibilité plus tard), spécifions le type d'itération linéaire en spécifiant explicitement leIteratorIterator
type d'itération pour la liste des répertoires:Cet exemple est presque identique au premier, la différence est qu'il
$files
s'agit désormais d'unIteratorIterator
type d'itération pourTraversable
$dir
:Comme d'habitude, l'acte d'itération est effectué par
foreach
:La sortie est exactement la même. Alors qu'est-ce qui est différent? L'objet utilisé dans le
foreach
. Dans le premier exemple, c'est unDirectoryIterator
dans le deuxième exemple, c'est leIteratorIterator
. Cela montre la flexibilité des itérateurs: vous pouvez les remplacer les uns par les autres, le code à l'intérieurforeach
continue de fonctionner comme prévu.Permet de commencer à obtenir la liste complète, y compris les sous-répertoires.
Comme nous avons maintenant spécifié le type d'itération, considérons de le changer en un autre type d'itération.
Nous savons que nous devons parcourir tout l'arbre maintenant, pas seulement le premier niveau. Avoir ce travail avec un simple
foreach
nous avons besoin d' un autre type de iterator:RecursiveIteratorIterator
. Et que l'on ne peut itérer que sur les objets conteneurs qui ont l'RecursiveIterator
interface .L'interface est un contrat. Toute classe l'implémentant peut être utilisée avec le
RecursiveIteratorIterator
. Un exemple d'une telle classe est leRecursiveDirectoryIterator
, qui est quelque chose comme la variante récursive deDirectoryIterator
.Voyons un premier exemple de code avant d'écrire une autre phrase avec le mot I:
Ce troisième exemple est presque identique au premier, mais il crée une sortie différente:
D'accord, pas si différent, le nom de fichier contient maintenant le chemin d'accès devant, mais le reste se ressemble également.
Comme le montre l'exemple, même l'objet annuaire imprègne déjà l'
RecursiveIterator
interface, ce n'est pas encore suffisant pour faireforeach
parcourir toute l'arborescence d'annuaire. C'est là que leRecursiveIteratorIterator
entre en action. L'exemple 4 montre comment:Utiliser le à la
RecursiveIteratorIterator
place de l'$dir
objet précédent feraforeach
parcourir tous les fichiers et répertoires de manière récursive. Cela répertorie ensuite tous les fichiers, car le type d'itération d'objet a été spécifié maintenant:Cela devrait déjà démontrer la différence entre la traversée à plat et l'arborescence. Le
RecursiveIteratorIterator
est capable de parcourir n'importe quelle structure arborescente sous forme de liste d'éléments. Comme il y a plus d'informations (comme le niveau auquel l'itération a lieu actuellement), il est possible d'accéder à l'objet itérateur en l'itérant et par exemple indenter la sortie:Et sortie de l' exemple 5 :
Bien sûr, cela ne gagne pas un concours de beauté, mais cela montre qu'avec l'itérateur récursif, il y a plus d'informations disponibles que juste l'ordre linéaire de la clé et de la valeur . Même
foreach
ne peut qu'exprimer ce genre de linéarité, accéder à l'itérateur lui-même permet d'obtenir plus d'informations.Comme pour les méta-informations, il existe également différentes manières de parcourir l'arbre et donc de classer la sortie. C'est le mode du
RecursiveIteratorIterator
et il peut être défini avec le constructeur.L'exemple suivant indiquera au
RecursiveDirectoryIterator
de supprimer les entrées de points (.
et..
) car nous n'en avons pas besoin. Mais aussi le mode de récursivité sera changé pour prendre l'élément parent (le sous-répertoire) en premier (SELF_FIRST
) avant les enfants (les fichiers et sous-sous-répertoires dans le sous-répertoire):La sortie affiche maintenant les entrées de sous-répertoire correctement répertoriées, si vous comparez avec la sortie précédente, celles-ci n'étaient pas là:
Le mode récursivité contrôle donc quoi et quand une brach ou une feuille dans l'arborescence est retournée, pour l'exemple de répertoire:
LEAVES_ONLY
(par défaut): répertorie uniquement les fichiers, pas de répertoires.SELF_FIRST
(ci-dessus): répertorie le répertoire et les fichiers qu'il contient.CHILD_FIRST
(sans exemple): Listez d'abord les fichiers dans le sous-répertoire, puis le répertoire.Sortie de l' exemple 5 avec les deux autres modes:
Lorsque vous comparez cela avec le parcours standard, toutes ces choses ne sont pas disponibles. L'itération récursive est donc un peu plus complexe lorsque vous devez l'enrouler la tête, mais elle est facile à utiliser car elle se comporte comme un itérateur, vous la mettez dans un
foreach
et c'est fait.Je pense que ce sont suffisamment d'exemples pour une réponse. Vous pouvez trouver le code source complet ainsi qu'un exemple pour afficher de jolis ascii-arbres dans cet article: https://gist.github.com/3599532
L'exemple 5 a démontré qu'il existe des méta-informations sur l'état de l'itérateur. Cependant, cela a été délibérément démontré dans l'
foreach
itération. Dans la vraie vie, cela appartient naturellement auRecursiveIterator
.Un meilleur exemple est le
RecursiveTreeIterator
, il s'occupe de l'indentation, du préfixe et ainsi de suite. Voir le fragment de code suivant:Le
RecursiveTreeIterator
est destiné à fonctionner ligne par ligne, la sortie est assez simple avec un petit problème:Lorsqu'il est utilisé en combinaison avec a,
RecursiveDirectoryIterator
il affiche le chemin d'accès complet et pas seulement le nom du fichier. Le reste semble bon. C'est parce que les noms de fichiers sont générés parSplFileInfo
. Celles-ci devraient être affichées comme nom de base à la place. La sortie souhaitée est la suivante:Créez une classe de décorateur qui peut être utilisée avec
RecursiveTreeIterator
au lieu deRecursiveDirectoryIterator
. Il doit fournir le nom de base du courantSplFileInfo
au lieu du chemin. Le fragment de code final pourrait alors ressembler à:Ces fragments
$unicodeTreePrefix
font partie de l'essentiel de l' annexe: Faites-le vous-même: faites leRecursiveTreeIterator
travail ligne par ligne. .la source
RecursiveIteratorIterator
parce que c'est en commun avec d'autres types, mais j'ai donné quelques informations techniques sur la façon dont cela fonctionne réellement. Je pense que les exemples montrent bien les différences: le type d'itération est la principale différence entre les deux. Aucune idée si vous achetez le type d'itération que vous créez un peu différemment, mais à mon humble avis, pas facile avec les types d'itération sémantiques.Pour comprendre la différence entre ces deux itérateurs, il faut d'abord comprendre un peu les conventions de dénomination utilisées et ce que l'on entend par itérateurs «récursifs».
Itérateurs récursifs et non récursifs
PHP a des itérateurs non "récursifs", tels que
ArrayIterator
etFilesystemIterator
. Il existe également des itérateurs "récursifs" tels queRecursiveArrayIterator
etRecursiveDirectoryIterator
. Ces derniers ont des méthodes permettant de les approfondir, les premiers non.Lorsque les instances de ces itérateurs sont bouclées d'elles-mêmes, même récursives, les valeurs ne proviennent que du niveau "supérieur" même si elles bouclent sur un tableau ou un répertoire imbriqué avec des sous-répertoires.
Les itérateurs récursifs implémentent un comportement récursif (via
hasChildren()
,getChildren()
) mais ne l' exploitent pas.Il vaudrait peut-être mieux considérer les itérateurs récursifs comme des itérateurs «récursifs», ils ont la capacité d'être itérés de manière récursive mais simplement itérer sur une instance de l'une de ces classes ne le fera pas. Pour exploiter le comportement récursif, continuez à lire.
RecursiveIteratorIterator
C'est là que le jeu
RecursiveIteratorIterator
entre en jeu. Il sait comment appeler les itérateurs "récursibles" de manière à explorer la structure en une boucle normale et plate. Il met en action le comportement récursif. Il fait essentiellement le travail de franchir chacune des valeurs de l'itérateur, de chercher à voir s'il y a des «enfants» dans lesquels rentrer ou non, et d'entrer et de sortir de ces collections d'enfants. Vous collez une instance deRecursiveIteratorIterator
dans un foreach, et elle plonge dans la structure pour que vous n'ayez pas à le faire.Si le
RecursiveIteratorIterator
n'était pas utilisé, vous auriez à écrire vos propres boucles récursives pour exploiter le comportement récursif, en comparant les itérateurs "récursibles"hasChildren()
et en utilisantgetChildren()
.Voilà donc un bref aperçu de
RecursiveIteratorIterator
, en quoi est-ce différentIteratorIterator
? Eh bien, vous posez fondamentalement le même genre de question que Quelle est la différence entre un chaton et un arbre? Ce n'est pas parce que les deux apparaissent dans la même encyclopédie (ou manuel, pour les itérateurs) que vous devriez être confus entre les deux.ItérateurIterator
Le travail du
IteratorIterator
est de prendre n'importe quelTraversable
objet et de l'envelopper de telle sorte qu'il satisfasse l'Iterator
interface. Une utilisation pour cela est de pouvoir ensuite appliquer un comportement spécifique à l'itérateur sur l'objet non-itérateur.Pour donner un exemple pratique, la
DatePeriod
classe estTraversable
mais pas unIterator
. En tant que tel, nous pouvons boucler ses valeurs avecforeach()
mais ne pouvons pas faire d'autres choses que nous ferions habituellement avec un itérateur, comme le filtrage.TÂCHE : Boucle les lundis, mercredis et vendredis des quatre prochaines semaines.
Oui, c'est trivial en
foreach
passant par- dessusDatePeriod
et en utilisant unif()
dans la boucle; mais ce n'est pas le but de cet exemple!L'extrait de code ci-dessus ne fonctionnera pas car le
CallbackFilterIterator
attend une instance d'une classe qui implémente l'Iterator
interface, ce quiDatePeriod
ne fonctionne pas. Cependant, comme c'est le cas,Traversable
nous pouvons facilement satisfaire cette exigence en utilisantIteratorIterator
.Comme vous pouvez le voir, cela n'a rien à voir avec l'itération sur les classes d'itérateur ni la récursivité, et c'est là que réside la différence entre
IteratorIterator
etRecursiveIteratorIterator
.Résumé
RecursiveIteraratorIterator
est pour itérer sur unRecursiveIterator
(itérateur "récursible"), en exploitant le comportement récursif disponible.IteratorIterator
est pour appliquer unIterator
comportement à desTraversable
objets non itérateurs .la source
IteratorIterator
simplement le type standard de parcours d'ordre linéaire pour lesTraversable
objets? Celles qui pourraient être utilisées sans elles,foreach
telles quelles ? Et encore plus, n'est-ce pasRecursiveIterator
toujours unTraversable
et donc pas seulementIteratorIterator
mais aussiRecursiveIteratorIterator
toujours "pour appliquer unIterator
comportement à des objets non itérateurs, Traversables" ? (Je dirais maintenantforeach
applique le type d'itération via l'objet iterator sur les objets conteneur qui implémentent une interface de type itérateur, donc ce sont toujours des objets iterator-container-objectsTraversable
)IteratorIterator
est une classe qui consiste à envelopper desTraversable
objets dans un fichierIterator
. Rien de plus . Vous semblez appliquer le terme plus généralement.Recursive
en erreur pendant longtemps car le inRecursiveIterator
implique un comportement, alors qu'un nom plus approprié aurait été celui qui décrit la capacité, commeRecursibleIterator
.Lorsqu'il est utilisé avec
iterator_to_array()
,RecursiveIteratorIterator
parcourra récursivement le tableau pour trouver toutes les valeurs. Cela signifie qu'il aplatira le tableau d'origine.IteratorIterator
conservera la structure hiérarchique d'origine.Cet exemple vous montrera clairement la différence:
la source
new IteratorIterator(new ArrayIterator($array))
équivaut ànew ArrayIterator($array)
, c'est-à-dire que l'extérieurIteratorIterator
ne fait rien. De plus, l'aplatissement de la sortie n'a rien à voir aveciterator_to_array
- il convertit simplement l'itérateur en tableau. L'aplatissement est une propriété du cheminRecursiveArrayIterator
parcouru par son itérateur interne.RecursiveDirectoryIterator il affiche le chemin d'accès complet et pas seulement le nom du fichier. Le reste a l'air bien. C'est parce que les noms de fichiers sont générés par SplFileInfo. Celles-ci devraient être affichées comme nom de base à la place. La sortie souhaitée est la suivante:
production:
la source