83 votes

Comment fonctionne RecursiveIteratorIterator en PHP ?

Comment RecursiveIteratorIterator travail ?

Le manuel PHP ne contient pas beaucoup de documentation ou d'explications. Quelle est la différence entre IteratorIterator y RecursiveIteratorIterator ?

235voto

hakre Points 102271

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 :

  • Alors que 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.
  • Alors que 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 :

Directory Tree

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. .

30voto

salathe Points 26305

Quelle est la différence entre IteratorIterator y 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 nous entendons par itérateurs "récursifs".

Itérateurs récursifs et non 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.

RecursiveIteratorIterator

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.

IteratorIterator

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 .

Résumé

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.

0voto

iMat Points 10229

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.com

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.

Powered by:

X