Comment ça RecursiveIteratorIterator
marche?
Le manuel PHP n'a rien de bien documenté ou expliqué. Quelle est la différence entre IteratorIterator
et RecursiveIteratorIterator
?
Comment ça RecursiveIteratorIterator
marche?
Le manuel PHP n'a rien de bien documenté ou expliqué. Quelle est la différence entre IteratorIterator
et RecursiveIteratorIterator
?
RecursiveIteratorIterator
fonctionne, avez-vous déjà compris comment IteratorIterator
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
cartes Iterator
et IteratorAggregate
dans un Iterator
, 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 un Iterator
parcours d'objet concret implémentant dans un ordre linéaire (et par défaut acceptant n'importe quel type de Traversable
dans son constructeur), le RecursiveIteratorIterator
permet de boucler sur tous les nœuds dans 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 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 Iterator
empile ensuite en interne les différents RecursiveIterator
s par leur profondeur et garde un pointeur vers le sous-marin actif actuel Iterator
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 avec foreach
vous, 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 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 est Traversable
la 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:
IteratorIterator
prend n'importe quel Traversable
pour un parcours linéaire, RecursiveIteratorIterator
nécessite une RecursiveIterator
boucle plus spécifique sur un arbre.IteratorIterator
expose son Iterator
via principal getInnerIerator()
, RecursiveIteratorIterator
fournit le sous-système actif actuel Iterator
uniquement via cette méthode.IteratorIterator
ne sache absolument rien comme un parent ou des enfants, il RecursiveIteratorIterator
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 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 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 Traversable
ce qui est possible via Iterator
ou 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-Recursive Recursive
============= =========
[tree] [tree]
├ dirA ├ dirA
└ fileA │ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
Vous pouvez facilement comparer cela avec IteratorIterator
lequel ne fait aucune récursivité pour parcourir l'arborescence de répertoires. Et le RecursiveIteratorIterator
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émente Traversable
qui permet foreach
d' itérer dessus:
$path = 'tree';
$dir = new DirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
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 IteratorIterator
ou RecursiveIteratorIterator
. Au lieu de cela, il suffit d'utiliser foreach
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 avec RecursiveIteratorIterator
plus de visibilité plus tard), spécifions le type d'itération linéaire en spécifiant explicitement le IteratorIterator
type d'itération pour la liste des répertoires:
$files = new IteratorIterator($dir);
echo "[$path]\n";
foreach ($files as $file) {
echo " ├ $file\n";
}
Cet exemple est presque identique au premier, la différence est qu'il $files
s'agit désormais d'un IteratorIterator
type d'itération pour Traversable
$dir
:
$files = new IteratorIterator($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 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 foreach
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 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 = new RecursiveDirectoryIterator($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' RecursiveIterator
interface, ce n'est pas encore suffisant pour faire foreach
parcourir toute l'arborescence d'annuaire. C'est là que le RecursiveIteratorIterator
entre en action. L'exemple 4 montre comment:
$files = new RecursiveIteratorIterator($dir);
echo "[$path]\n";
foreach ($files as $file) {
echo " ├ $file\n";
}
Utiliser le à la RecursiveIteratorIterator
place de l' $dir
objet précédent fera foreach
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:
[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
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:
echo "[$path]\n";
foreach ($files as $file) {
$indent = str_repeat(' ', $files->getDepth());
echo $indent, " ├ $file\n";
}
Et sortie de l' 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 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 duRecursiveIteratorIterator
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):
$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 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à:
[tree]
├ tree\dirA
├ tree\dirA\dirB
├ tree\dirA\dirB\fileD
├ tree\dirA\fileB
├ tree\dirA\fileC
├ tree\fileA
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:
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 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
Faites-le vous-même: faites le
RecursiveTreeIterator
travail 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' foreach
ité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:
$dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$lines = new RecursiveTreeIterator($dir);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));
Le RecursiveTreeIterator
est destiné à fonctionner ligne par ligne, la sortie est assez simple avec un petit problème:
[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 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 par SplFileInfo
. Celles-ci devraient être affichées comme nom de base à la place. La sortie souhaitée est la suivante:
/// Solved ///
[tree]
├ dirA
│ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
Créez une classe de décorateur qui peut être utilisée avec RecursiveTreeIterator
au lieu de RecursiveDirectoryIterator
. Il doit fournir le nom de base du courant SplFileInfo
au lieu du chemin. Le fragment de code final pourrait alors ressembler à:
$lines = new RecursiveTreeIterator(
new DiyRecursiveDecorator($dir)
);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));
Ces fragments $unicodeTreePrefix
font partie de l'essentiel de l' annexe: Faites-le vous-même: faites le RecursiveTreeIterator
travail ligne par ligne. .
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.
Quelle est la différence entre
IteratorIterator
etRecursiveIteratorIterator
?
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».
PHP a des itérateurs non "récursifs", tels que ArrayIterator
et FilesystemIterator
. Il existe également des itérateurs "récursifs" tels que RecursiveArrayIterator
et 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.
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 de RecursiveIteratorIterator
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 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.
Le travail du IteratorIterator
est de prendre n'importe quel Traversable
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 est Traversable
mais 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 foreach
passant par- dessus 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 de code ci-dessus ne fonctionnera pas car le CallbackFilterIterator
attend une instance d'une classe qui implémente l' Iterator
interface, ce qui DatePeriod
ne fonctionne pas. Cependant, comme c'est le cas, Traversable
nous pouvons facilement satisfaire cette exigence en utilisant IteratorIterator
.
$period = new IteratorIterator(new DatePeriod(…));
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
et RecursiveIteratorIterator
.
RecursiveIteraratorIterator
est pour itérer sur un RecursiveIterator
(itérateur "récursible"), en exploitant le comportement récursif disponible.
IteratorIterator
est pour appliquer un Iterator
comportement à des Traversable
objets non itérateurs .
IteratorIterator
simplement le type standard de parcours d'ordre linéaire pour les Traversable
objets? Celles qui pourraient être utilisées sans elles, foreach
telles quelles ? Et encore plus, n'est-ce pas RecursiveIterator
toujours un Traversable
et donc pas seulement IteratorIterator
mais aussi RecursiveIteratorIterator
toujours "pour appliquer un Iterator
comportement à des objets non itérateurs, Traversables" ? (Je dirais maintenant foreach
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-objects Traversable
)
IteratorIterator
est une classe qui consiste à envelopper des Traversable
objets dans un fichier Iterator
. Rien de plus . Vous semblez appliquer le terme plus généralement.
Recursive
en erreur pendant longtemps car le in RecursiveIterator
implique un comportement, alors qu'un nom plus approprié aurait été celui qui décrit la capacité, comme RecursibleIterator
.
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:
$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));
new IteratorIterator(new ArrayIterator($array))
équivaut à new ArrayIterator($array)
, c'est-à-dire que l'extérieur IteratorIterator
ne 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 RecursiveArrayIterator
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:
$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";
}
}
production:
tree
├ dirA
│ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA