Comment fonctionne RecursiveIteratorIterator en PHP?

Comment fonctionne RecursiveIteratorIterator?

Le manuel PHP n'a rien de bien documenté ou expliqué. Quelle est la différence entre IteratorIterator et RecursiveIteratorIterator?

77
demandé sur fuxia 2012-08-22 20:07:57

4 réponses

RecursiveIteratorIterator est un béton Iterator la mise en œuvre de l'arbre transversal. Il permet à un programmeur de traverser un objet conteneur qui implémente l'interface RecursiveIterator, voir Iterator dans Wikipedia pour les principes généraux, les types, la sémantique et les modèles d'itérateurs.

Dans la différence de IteratorIterator qui est un béton Iterator implémentant la traversée d'objet dans un ordre linéaire (et acceptant par défaut tout type de Traversable dans son constructeur), le RecursiveIteratorIterator permet de boucler tous les nœuds dans un arbre ordonné {[138] } d'objets et son constructeur prend un RecursiveIterator.

En bref: RecursiveIteratorIterator permet de vous faire une boucle sur un arbre, IteratorIterator permet de vous faire une boucle sur une liste. Je montre cela avec quelques exemples de code ci-dessous bientôt.

Techniquement, cela fonctionne en sortant de la linéarité en traversant tous les enfants d'un nœud (le cas échéant). Ceci est possible car par définition tous les enfants d'un nœud sont à nouveau un RecursiveIterator. Le toplevel {[20] } empile ensuite en interne les différents RecursiveIterator s par leur profondeur et garde un pointeur sur le sous-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 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 ces sémantiques. Comparez avec les exemples ci-dessous, pour une boucle linéaire avec foreach, vous ne pensez normalement pas au l'implémentation détaille beaucoup moins que vous n'ayez besoin de définir un nouveau Iterator (par exemple quand un type concret lui-même n'implémente pas Traversable).

Pour la traversée récursive-à moins que vous n'utilisiez un Traversal prédéfini qui a déjà une itération de traversée récursive-vous avez normalement besoin de pour instancier l'itération RecursiveIteratorIterator existante ou même écrire une itération de traversée récursive qui est la vôtre Traversable 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, 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.

Différences Techniques en bref:

  • Tout IteratorIterator prend tout Traversable linéaire transversal, RecursiveIteratorIterator besoins plus spécifiques, RecursiveIterator faire une boucle sur un arbre.
  • IteratorIterator expose son principal Iterator par getInnerIerator(), RecursiveIteratorIterator fournit le sous-Iterator actif courant uniquement via cette méthode.
  • alors que IteratorIterator n'est totalement pas au courant de quelque chose comme parent ou enfants, RecursiveIteratorIterator sait comment obtenir et traverser les enfants aussi.
  • IteratorIterator n'a pas besoin d'une pile de itérateurs, RecursiveIteratorIterator a une pile et sait l'actif sous-itérateur.
  • IteratorIterator a son ordre en raison de la linéarité et pas de choix, RecursiveIteratorIterator a un choix pour une traversée ultérieure et doit décider par chaque nœud (décidé via le mode par RecursiveIteratorIterator).
  • RecursiveIteratorIterator a plus de méthodes IteratorIterator.

Pour résumer: RecursiveIterator est un type concret d'itération (en boucle sur un arbre) qui fonctionne sur ses propres itérateurs, à savoir RecursiveIterator. C'est le même principe qu'avec les IteratorIerator, mais le type d'itération est différent (ordre linéaire).

Idéalement, vous pouvez aussi 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 utilisez-le avec foreach. Par exemple, une sorte d'objet d'itération récursive à travers l'arbre ternaire avec l'interface d'itération correspondante pour l'objet conteneur(s).


Passons en revue avec quelques exemples réels qui ne sont pas si abstraits. Entre les interfaces, les itérateurs concrets, les objets conteneur et la sémantique d'itération, ce n'est peut-être pas une si mauvaise idée.

Prenez une liste de répertoires comme exemple. Considérez que vous avez l'arborescence de fichiers et de répertoires suivante sur le disque:

Arborescence Des Répertoires

Alors qu'un itérateur avec un ordre linéaire traverse simplement le dossier et les fichiers du niveau supérieur( une liste de répertoires unique), l'itérateur récursif traverse également les sous-dossiers et liste tous les dossiers et fichiers (une liste de répertoires avec des 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écursivité pour parcourir l'arborescence des répertoires. Et le {[19] } qui peut traverser dans l'arbre comme le Récursive liste affiche.

Abord un exemple très simple avec un DirectoryIterator qui met en œuvre Traversable ce qui permet foreach de itérer sur elle:

$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épertoire 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'interface Traversable.

Comme foreach par défaut ne connaît que le type de l'itération nommée ordre linéaire, on peut 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), spécifions le type d'itération linéaire en spécifiant explicitement le type d'itération IteratorIterator pour la liste de 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 que $files est maintenant un type IteratorIterator de d'itération pour Traversable $dir:

$files = new IteratorIterator($dir);

Comme d'habitude l'acte d'itération est effectué par le foreach:

foreach ($files as $file) {

La sortie est exactement le même. Donc ce qui est différent? Différent est l'objet utilisé dans le foreach. Dans le premier exemple, il 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 de foreach continue de fonctionner comme prévu.

Commençons à obtenir le 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 traverser l'arbre entier maintenant, pas seulement le premier niveau. Pour le fonctionnement 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 des objets conteneur qui ont le RecursiveIterator interface .

L'interface est un contrat. Tout la classe l'implémentant peut être utilisée avec le RecursiveIteratorIterator. Un exemple d'une telle classe est l'RecursiveDirectoryIterator, qui est quelque chose comme la variante récursive de DirectoryIterator.

Permet de voir un premier exemple de code avant d'écrire une phrase avec le mot:

$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

Ok, pas si différent, le nom de fichier contient maintenant le chemin d'accès dans avant, mais le reste semble similaire aussi bien.

Comme le montre l'exemple, même l'objet directory imèle déjà l'interface RecursiveIterator, ce n'est pas encore suffisant pour que foreach traverse toute l'arborescence des répertoires. 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";
}

En utilisant l'objet RecursiveIteratorIterator au lieu de l'objet $dir précédent, foreach parcourra tous les fichiers et répertoires de manière récursive. Cela répertorie alors tous les fichiers, comme le 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 la traversée en arbre. Le RecursiveIteratorIterator est capable de traverser n'importe quelle structure arborescente en tant que liste d'éléments. Comme il y a plus d'informations (comme le niveau que l'itération prend actuellement), il est possible d'accéder à l'objet itérateur tout en itérant dessus 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 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 l'ordre linéaire de key et value. 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.

Similaire à la méta-information, il existe également différentes façons de traverser l'arbre et donc d'ordonner la sortie. C'est l'en Mode de la RecursiveIteratorIterator et il peut être réglé 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écursion sera modifié 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 ceux-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 un brach ou une feuille dans l'arbre est retourné, pour l'exemple de répertoire:

  • LEAVES_ONLY (par défaut): liste uniquement les fichiers, Pas de répertoires.
  • SELF_FIRST (ci-dessus): liste le répertoire, puis les fichiers qui s'y trouvent.
  • CHILD_FIRST (sans exemple): Liste d'abord les fichiers dans le sous-répertoire, 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 lorsque vous avez besoin d'envelopper votre tête autour d'elle, mais elle est facile à utiliser car elle se comporte comme un itérateur, vous le mettez dans un foreach et fait.

Je pense que ce sont des exemples suffisants pour une réponse. Vous pouvez trouver le code source complet ainsi qu'un exemple pour afficher de beaux arbres ascii dans cet essentiel: https://gist.github.com/3599532

Faites-Le Vous-Même: Faites Le RecursiveTreeIterator ligne de travail par ligne.

L'exemple 5 a démontré qu'il existe des méta-informations sur l'état de l'itérateur disponible. Cependant, cela a été délibérément démontré dans l'itération foreach. Dans la vraie vie, cela appartient naturellement à l'intérieur du RecursiveIterator.

Meilleur exemple en est la RecursiveTreeIterator, il prend soin de mise en retrait, 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é à travailler 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 un RecursiveDirectoryIterator, Il affiche le chemin entier et pas seulement le nom de fichier. Le reste semble bon. C'est parce que les noms de fichiers sont générés par SplFileInfo. Ceux-ci devraient être affichés comme le nom de base à la place. La sortie souhaitée est la suivant:

/// Solved ///

[tree]
 ├ dirA
 │ ├ dirB
 │ │ └ fileD
 │ ├ fileB
 │ └ fileC
 └ fileA

Créer un décorateur classe qui peut être utilisée avec RecursiveTreeIterator au lieu de RecursiveDirectoryIterator. Il devrait fournir le nom de base du courant SplFileInfo au lieu du chemin d'accès. 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, y compris $unicodeTreePrefix, font partie de l'essentiel de l'annexe : Faites-Le Vous-Même: Faites Le RecursiveTreeIterator ligne par ligne de travail..

218
répondu hakre 2012-09-02 22:24:46

Quelle est la différence de IteratorIterator et RecursiveIteratorIterator?

Pour comprendre la différence entre ces deux itérateurs, il faut d'abord comprendre un peu les conventions de nommage utilisées et ce que nous entendons par itérateurs "récursifs".

Itérateurs récursifs et non 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 leur permettant d'être percé , les anciens n'en ont pas.

Lorsque les instances de ces itérateurs sont bouclées seules, même les récursives, les valeurs ne proviennent que du niveau "supérieur" même si elles sont en boucle sur un tableau ou un répertoire imbriqué avec des sous-répertoires.

Les itérateurs récursifs implémentent le comportement récursif (via hasChildren(), getChildren()) mais ne l'exploitez pas.

Il pourrait être préférable de penser aux itérateurs récursifs comme des itérateurs "récursibles", ils ont le la capacité à être itérée récursivement 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 {[3] } entre en jeu. Il a la connaissance de la façon d'appeler les itérateurs "récursibles" de manière à percer dans la structure dans une boucle normale, plate. Il met le comportement récursif en action. Il fait essentiellement le travail de pas à pas sur chacune des valeurs de l'itérateur, cherchez à voir s'il y a des "enfants" à récurer ou non, et entrez dans et hors de ces collections d'enfants. Vous collez une instance de RecursiveIteratorIterator dans un foreach, et elle plonge dans la structure de sorte que vous n'avez pas à le faire.

Si le RecursiveIteratorIterator n'était pas utilisé, vous devrez écrire vos propres boucles récursives pour exploiter le comportement récursif, en vérifiant par rapport à l'itérateur" récursible " 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?{[53] } juste parce que les deux apparaissent dans la même Encyclopédie (ou manuel, pour les itérateurs) ne signifie pas que vous devriez être confus entre les deux.

IteratorIterator

Le travail de IteratorIterator est de prendre n'importe quel objet Traversable, et de l'envelopper de telle sorte qu'il satisfasse l'interface Iterator. 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 classe DatePeriod est Traversable mais pas Iterator. En tant que tel, nous pouvons faire une boucle sur ses valeurs avec foreach() mais ne pouvons pas faire d'autres choses que nous le ferions habituellement avec un itérateur, comme le filtrage.

Tâche : boucle sur les lundis, mercredis et vendredis des quatre prochaines semaines.

Oui, c'est trivial en foreach - ing sur le 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 car CallbackFilterIterator attend une instance d'une classe qui implémente l'interface Iterator, ce qui n'est pas le cas de DatePeriod. Cependant, comme il s'agit de 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érateurs ni la récursivité, et c'est là que réside la différence entre IteratorIterator et RecursiveIteratorIterator.

Résumé

RecursiveIteraratorIterator est pour itérer sur un RecursiveIterator (itérateur"récursible"), en exploitant le comportement récursif disponible.

IteratorIterator est pour appliquer Iterator comportement à non-itérateur, Traversable objets.

30
répondu salathe 2012-09-02 14:39:32

RecursiveDirectoryIterator il affiche le chemin entier et pas seulement le nom de fichier. Le reste semble bon. C'est parce que les noms de fichiers sont générés par SplFileInfo. Ceux-ci devraient être affichés comme le 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";
    }
}

Sortie:

tree
 ├ dirA
 │ ├ dirB
 │ │ └ fileD
 │ ├ fileB
 │ └ fileC
 └ fileA
0
répondu javad shariaty 2017-10-11 09:14:12

Lorsqu'il est utilisé avec iterator_to_array(), RecursiveIteratorIterator parcourra récursivement le tableau pour trouver toutes les valeurs. Ce qui signifie qu'il va aplatir le tableau d'origine.

IteratorIterator gardera 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));
-1
répondu Tchoupi 2012-08-30 20:56:22