Comment RecursiveIteratorIterator
travail ?
Le manuel PHP ne contient pas beaucoup de documentation ou d'explications. Quelle est la différence entre IteratorIterator
y RecursiveIteratorIterator
?
Comment RecursiveIteratorIterator
travail ?
Le manuel PHP ne contient pas beaucoup de documentation ou d'explications. Quelle est la différence entre IteratorIterator
y RecursiveIteratorIterator
?
RecursiveIteratorIterator
est un béton Iterator
mise en œuvre de traversée d'un arbre . Il permet à un programmeur de parcourir un objet conteneur qui met en œuvre la méthode de la RecursiveIterator
l'interface, voir Iterator dans Wikipedia pour les principes généraux, les types, la sémantique et les modèles d'itérateurs.
À la différence de IteratorIterator
qui est un béton Iterator
implémentant la traversée d'objets dans un ordre linéaire (et acceptant par défaut n'importe quelle sorte de Traversable
dans son constructeur), le RecursiveIteratorIterator
permet de boucler sur tous les nœuds d'un arbre ordonné d'objets et son constructeur prend un RecursiveIterator
.
En bref : RecursiveIteratorIterator
vous permet de boucler sur un arbre, IteratorIterator
vous permet de boucler sur une liste. Je le montrerai bientôt avec quelques exemples de code ci-dessous.
Techniquement, cela fonctionne en rompant la linéarité en traversant tous les enfants d'un nœud (s'il y en a). Ceci est possible car, par définition, tous les enfants d'un noeud sont à nouveau un noeud RecursiveIterator
. Le niveau supérieur Iterator
puis empile en interne les différents RecursiveIterator
en fonction de leur profondeur et conserve un pointeur sur le sous-système actif actuel. Iterator
pour la traversée.
Cela permet de visiter tous les nœuds d'un arbre.
Les principes sous-jacents sont les mêmes que pour IteratorIterator
: Une interface spécifie le type d'itération et la classe d'itérateurs de base est l'implémentation de cette sémantique. Comparez avec les exemples ci-dessous, pour le bouclage linéaire avec foreach
vous ne pensez normalement pas beaucoup aux détails de l'implémentation, sauf si vous devez définir une nouvelle Iterator
(par exemple, lorsqu'un type concret n'implémente pas lui-même l'option Traversable
).
Pour la traversée récursive - à moins que vous n'utilisiez pas une méthode de traversée prédéfinie Traversal
qui a déjà une itération récursive de traversée - vous avez normalement besoin de pour instancier le RecursiveIteratorIterator
ou même écrire une itération récursive de traversée qui est une Traversable
votre propre pour avoir ce type d'itération transversale avec foreach
.
Conseil : Vous n'avez probablement pas implémenté l'un ou l'autre vous-même, donc cela pourrait être quelque chose à faire pour votre expérience pratique des différences qu'ils ont. Vous trouverez une suggestion de bricolage à la fin de la réponse.
Les différences techniques en bref :
IteratorIterator
prend n'importe quel Traversable
pour une traversée linéaire, RecursiveIteratorIterator
doit être plus spécifique RecursiveIterator
pour boucler sur un arbre.IteratorIterator
expose son principal Iterator
via getInnerIerator()
, RecursiveIteratorIterator
fournit le sous-ensemble actif actuel Iterator
uniquement par cette méthode.IteratorIterator
n'est absolument pas conscient de ce qu'est un parent ou un enfant, RecursiveIteratorIterator
sait aussi comment obtenir et traverser les 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 du fait de la linéarité et de l'absence de choix, RecursiveIteratorIterator
a un choix à faire pour la suite de la traversée et doit décider pour chaque nœud (décidé par l'intermédiaire de mode par RecursiveIteratorIterator
).RecursiveIteratorIterator
a plus de méthodes que IteratorIterator
.Pour résumer : RecursiveIterator
est 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 pour 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 Traversable
ce qui est possible via Iterator
o IteratorAggregate
. Vous pouvez ensuite l'utiliser avec foreach
. Par exemple, une sorte d'objet d'itération récursive à travers un arbre ternaire, ainsi que l'interface d'itération correspondante pour le ou les objets conteneurs.
Faisons le point avec quelques exemples concrets qui ne sont pas si abstraits. Entre les interfaces, les itérateurs concrets, les objets conteneurs et la sémantique des itérations, ce n'est peut-être pas une si mauvaise idée.
Prenons l'exemple d'une liste d'annuaires. Considérons que vous avez l'arbre de fichiers et de répertoires suivant sur le disque :
Alors qu'un itérateur avec un ordre linéaire ne fait que parcourir le dossier et les fichiers de premier niveau (une liste de répertoire unique), l'itérateur récursif parcourt également les sous-dossiers et liste tous les dossiers et fichiers (une liste de répertoire avec les listes de ses sous-répertoires) :
Non-Recursive Recursive
============= =========
[tree] [tree]
├ dirA ├ dirA
└ fileA │ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
Vous pouvez facilement comparer cela avec IteratorIterator
qui ne fait pas de récursion pour parcourir l'arbre des répertoires. Et le RecursiveIteratorIterator
qui peut se déplacer dans l'arbre comme le montre le listing récursif.
Tout d'abord, un exemple très basique avec un DirectoryIterator
qui met en œuvre Traversable
qui permet foreach
a itérer par-dessus :
$path = 'tree';
$dir = new DirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
La sortie exemplaire pour la structure de répertoire ci-dessus est alors :
[tree]
├ .
├ ..
├ dirA
├ fileA
Comme vous le voyez, ce n'est pas encore l'utilisation IteratorIterator
o RecursiveIteratorIterator
. Au lieu de cela, il suffit d'utiliser foreach
qui fonctionne sur le 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 verbeux, mais à des fins de démonstration (et pour faire la différence avec RecursiveIteratorIterator
plus visible plus tard), permet de spécifier le type d'itération linéaire en spécifiant explicitement l'élément IteratorIterator
type d'itération pour la liste de l'annuaire :
$files = new IteratorIterator($dir);
echo "[$path]\n";
foreach ($files as $file) {
echo " ├ $file\n";
}
Cet exemple est presque identique à la première, la différence étant que $files
est maintenant un IteratorIterator
type d'itération pour Traversable
$dir
:
$files = new IteratorIterator($dir);
Comme d'habitude, l'acte d'itération est effectué par la fonction foreach
:
foreach ($files as $file) {
Le résultat est exactement la même chose. Alors qu'est-ce qui est différent ? Différent est l'objet utilisé dans le foreach
. Dans le premier exemple, il s'agit d'un DirectoryIterator
dans 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 de foreach
continuent à fonctionner comme prévu.
Commençons par obtenir la liste complète, y compris les sous-répertoires.
Comme nous avons maintenant spécifié le type d'itération, envisageons de le changer pour un autre type d'itération.
Nous savons que nous devons maintenant traverser tout l'arbre, et pas seulement le premier niveau. Pour que cela fonctionne avec un simple foreach
nous avons besoin d'un autre type d'itérateur : RecursiveIteratorIterator
. Et que l'on ne peut itérer que sur les objets conteneurs qui ont l'attribut RecursiveIterator
interface .
L'interface est un contrat. Toute classe qui l'implémente peut être utilisée avec l'interface RecursiveIteratorIterator
. Un exemple d'une telle classe est la RecursiveDirectoryIterator
qui est en quelque sorte la variante récursive de DirectoryIterator
.
Voyons un premier exemple de code avant d'écrire toute autre phrase avec le mot "I" :
$dir = new RecursiveDirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
Ce troisième exemple est presque identique à la première, mais elle crée une sortie différente :
[tree]
├ tree\.
├ tree\..
├ tree\dirA
├ tree\fileA
D'accord, ce n'est pas si différent, le nom du fichier contient maintenant le nom du chemin d'accès devant, mais le reste est similaire.
Comme le montre l'exemple, même l'objet répertoire implémente déjà l'élément RecursiveIterator
mais ce n'est pas encore suffisant pour rendre foreach
traverse toute l'arborescence du répertoire. C'est là que le RecursiveIteratorIterator
entre en action. Exemple 4 montre comment :
$files = new RecursiveIteratorIterator($dir);
echo "[$path]\n";
foreach ($files as $file) {
echo " ├ $file\n";
}
Utilisation de la RecursiveIteratorIterator
au lieu de la précédente $dir
L'objet fera foreach
pour parcourir tous les fichiers et répertoires de manière récursive. Cela liste alors tous les fichiers, puisque le type d'itération des objets a été spécifié maintenant :
[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
Ceci devrait déjà démontrer la différence entre la traversée plate et la traversée d'arbre. Le site RecursiveIteratorIterator
est capable de parcourir n'importe quelle structure arborescente comme une 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 tout en l'itérant et, par exemple, d'indenter la sortie :
echo "[$path]\n";
foreach ($files as $file) {
$indent = str_repeat(' ', $files->getDepth());
echo $indent, " ├ $file\n";
}
Et la sortie de Exemple 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
Bien sûr, cela ne remporte pas de concours de beauté, mais cela montre qu'avec l'itérateur récursif, il y a plus d'informations disponibles que le simple ordre linéaire de l'itérateur. clé y valeur . Même foreach
ne peut exprimer que ce type de linéarité, l'accès à 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 d'ordonner les résultats. Il s'agit de la Mode de la RecursiveIteratorIterator
et elle peut être définie avec le constructeur.
L'exemple suivant indique que le RecursiveDirectoryIterator
pour supprimer les entrées de points ( .
y ..
) car nous n'en avons pas besoin. Mais aussi le mode de récursion sera changé pour prendre l'élément parent (le sous-répertoire) en premier ( SELF_FIRST
) avant les enfants (les fichiers et les sous-sous-répertoires dans le sous-répertoire) :
$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";
}
La sortie montre maintenant les entrées de sous-répertoire correctement listées, si vous comparez avec la sortie précédente celles qui n'étaient pas là :
[tree]
├ tree\dirA
├ tree\dirA\dirB
├ tree\dirA\dirB\fileD
├ tree\dirA\fileB
├ tree\dirA\fileC
├ tree\fileA
Le mode de récursion contrôle donc ce qui et quand une branche ou une feuille de l'arbre est retournée, pour l'exemple du répertoire :
LEAVES_ONLY
(par défaut) : Liste seulement les fichiers, pas les répertoires.SELF_FIRST
(ci-dessus) : Lister le répertoire et ensuite les fichiers qui s'y trouvent.CHILD_FIRST
(sans exemple) : Lister les fichiers du sous-répertoire d'abord, puis le répertoire.Sortie de Exemple 5 avec les deux autres modes :
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
Lorsque vous comparez cela avec la traversée standard, toutes ces choses ne sont pas disponibles. L'itération récursive est donc un peu plus complexe lorsqu'il s'agit de s'y retrouver, mais elle est facile à utiliser car elle se comporte comme un itérateur, vous la placez dans un fichier de type foreach
et c'est fait.
Je pense que ces exemples sont suffisants pour une réponse. Vous pouvez trouver le code source complet ainsi qu'un exemple d'affichage d'un bel arbre ascii dans ce gist : https://gist.github.com/3599532
Faites-le vous-même : Faites le
RecursiveTreeIterator
Travailler ligne par ligne.
Exemple 5 démontre que des méta-informations sur l'état de l'itérateur sont disponibles. Cependant, cela a été volontairement démontré sur le site foreach
itération. Dans la vie réelle, cela appartient naturellement à l'élément RecursiveIterator
.
Un meilleur exemple est le RecursiveTreeIterator
il s'occupe de l'indentation, de la préfixation et ainsi de suite. Voir le fragment de code suivant :
$dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$lines = new RecursiveTreeIterator($dir);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));
El RecursiveTreeIterator
est destiné à fonctionner ligne par ligne, le résultat est assez simple, à un petit problème près :
[tree]
├ tree\dirA
│ ├ tree\dirA\dirB
│ │ └ tree\dirA\dirB\fileD
│ ├ tree\dirA\fileB
│ └ tree\dirA\fileC
└ tree\fileA
Lorsqu'il est utilisé en combinaison avec un RecursiveDirectoryIterator
il affiche le chemin d'accès complet et pas seulement le nom du fichier. Le reste semble correct. Ceci est dû au fait que les noms de fichiers sont générés par SplFileInfo
. Ceux-ci doivent être affichés comme nom de base à la place. La sortie souhaitée est la suivante :
/// Solved ///
[tree]
├ dirA
│ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
Créer une classe de décorateur qui peut être utilisée avec RecursiveTreeIterator
au lieu de la RecursiveDirectoryIterator
. Il devrait fournir le nom de base de l'application courante SplFileInfo
au lieu du nom de chemin. Le fragment de code final pourrait alors ressembler à ceci :
$lines = new RecursiveTreeIterator(
new DiyRecursiveDecorator($dir)
);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));
Ces fragments comprennent $unicodeTreePrefix
font partie de l'essentiel dans Annexe : Faites-le vous-même : Faites le RecursiveTreeIterator
Travailler ligne par ligne. .
Quelle est la différence entre
IteratorIterator
yRecursiveIteratorIterator
?
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 nous entendons par itérateurs "récursifs".
PHP possède des itérateurs non "récursifs", tels que ArrayIterator
y FilesystemIterator
. Il existe également des itérateurs "récursifs" tels que le RecursiveArrayIterator
y RecursiveDirectoryIterator
. Les seconds disposent de méthodes permettant de les forer, les premiers non.
Lorsque des instances de ces itérateurs sont bouclées seules, même celles qui sont récursives, les valeurs ne proviennent que du niveau "supérieur", même si l'on boucle sur un tableau imbriqué ou un répertoire avec des sous-répertoires.
Les itérateurs récursifs mettre en œuvre comportement récursif (via hasChildren()
, getChildren()
) mais pas exploit il.
Il serait peut-être préférable de considérer les itérateurs récursifs comme des itérateurs "récursifs", ils ont l'attribut capacité pour être itéré récursivement, mais le simple fait d'itérer sur une instance de l'une de ces classes ne le fera pas. Pour exploiter le comportement récursif, poursuivez votre lecture.
C'est là que le RecursiveIteratorIterator
entre en jeu. Il sait comment appeler les itérateurs "récursifs" de manière à pénétrer dans la structure d'une boucle plate normale. Il met le comportement récursif en action. Il fait essentiellement le travail de passer en revue chacune des valeurs de l'itérateur, en regardant s'il y a des "enfants" dans lesquels on peut récourir ou non, et en entrant et sortant de ces collections d'enfants. Vous collez une instance de RecursiveIteratorIterator
dans un foreach, et il se plonge dans la structure pour que vous n'ayez pas à le faire.
Si le RecursiveIteratorIterator
n'était pas utilisé, vous auriez dû écrire vos propres boucles récursives pour exploiter le comportement récursif, en vérifiant les données de l'itérateur "récursif". hasChildren()
et en utilisant getChildren()
.
Donc c'est un bref aperçu de RecursiveIteratorIterator
En quoi est-il différent de IteratorIterator
? Eh bien, vous posez essentiellement 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 figurent dans la même encyclopédie (ou manuel, pour les itérateurs) qu'il faut confondre les deux.
Le travail de l IteratorIterator
est de prendre n'importe quel Traversable
et l'envelopper de manière à ce qu'il satisfasse à l'exigence de l'objet Iterator
l'interface. Cela permet d'appliquer un comportement spécifique à l'itérateur sur l'objet non itérateur.
Pour donner un exemple concret, le DatePeriod
La classe est Traversable
mais pas un Iterator
. En tant que tel, nous pouvons boucler sur ses valeurs avec foreach()
mais ne peut pas faire d'autres choses que nous ferions normalement avec un itérateur, comme le filtrage.
TASK : Boucle sur les lundis, mercredis et vendredis des quatre semaines suivantes.
Oui, c'est trivial par foreach
-sur les DatePeriod
et en utilisant un if()
dans la boucle ; mais ce n'est pas le but de cet exemple !
$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) { … }
L'extrait ci-dessus ne fonctionnera pas parce que l'option CallbackFilterIterator
s'attend à une instance d'une classe qui implémente la fonction Iterator
l'interface, qui DatePeriod
ne le fait pas. Cependant, étant donné que c'est Traversable
nous pouvons facilement satisfaire cette exigence en utilisant IteratorIterator
.
$period = new IteratorIterator(new DatePeriod(…));
Comme vous pouvez le voir, cela a nada n'a rien à voir avec l'itération sur des classes d'itérateurs ni avec la récursion, et c'est là que réside la différence entre IteratorIterator
y RecursiveIteratorIterator
.
RecursiveIteraratorIterator
permet d'itérer sur un RecursiveIterator
(itérateur "récursif"), en exploitant le comportement récursif disponible.
IteratorIterator
sert à appliquer Iterator
à un comportement non itérateur, Traversable
objets.
Lorsqu'il est utilisé avec iterator_to_array()
, RecursiveIteratorIterator
va parcourir récursivement le tableau pour trouver toutes les valeurs. Ce qui signifie qu'il aplatira le tableau d'origine.
IteratorIterator
conservera la structure hiérarchique d'origine.
Cet exemple vous montrera clairement la différence :
$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));
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.