¿Cómo RecursiveIteratorIterator
funciona?
El manual de PHP no tiene mucho documentado o explicado. ¿Cuál es la diferencia entre IteratorIterator
y RecursiveIteratorIterator
?
¿Cómo RecursiveIteratorIterator
funciona?
El manual de PHP no tiene mucho documentado o explicado. ¿Cuál es la diferencia entre IteratorIterator
y RecursiveIteratorIterator
?
RecursiveIteratorIterator
funciona, ¿ya ha entendido cómo IteratorIterator
funciona? Quiero decir que es básicamente lo mismo, solo que la interfaz que consumen los dos es diferente. ¿Está más interesado en algunos ejemplos o desea ver las diferencias de la implementación del código C subyacente?
IteratorIterator
mapas Iterator
y IteratorAggregate
en un Iterator
, donde REcusiveIteratorIterator
se usa para atravesar recusivamente aRecursiveIterator
Respuestas:
RecursiveIteratorIterator
es un cruce de árbol deIterator
implementación concreto . Permite a un programador atravesar un objeto contenedor que implementa la interfaz, consulteRecursiveIterator
Iterador en Wikipedia para conocer los principios generales, tipos, semántica y patrones de los iteradores.
A diferencia de lo IteratorIterator
que es un Iterator
objeto transversal de implementación concreto en orden lineal (y por defecto acepta cualquier tipo de Traversable
en su constructor), RecursiveIteratorIterator
permite recorrer todos los nodos en un árbol ordenado de objetos y su constructor toma unRecursiveIterator
.
En resumen: le RecursiveIteratorIterator
permite recorrer un árbol, le IteratorIterator
permite recorrer una lista. Pronto lo muestro con algunos ejemplos de código a continuación.
Técnicamente, esto funciona rompiendo la linealidad atravesando todos los hijos de un nodo (si los hay). Esto es posible porque, por definición, todos los hijos de un nodo son nuevamente a RecursiveIterator
. El nivel superior Iterator
luego apila internamente los diferentes RecursiveIterator
s por su profundidad y mantiene un puntero al sub actual activo Iterator
para el recorrido.
Esto permite visitar todos los nodos de un árbol.
Los principios subyacentes son los mismos que con IteratorIterator
: Una interfaz especifica el tipo de iteración y la clase de iterador base es la implementación de esta semántica. Compare con los ejemplos a continuación, para el bucle lineal con foreach
usted normalmente no piense mucho en los detalles de implementación a menos que necesite definir un nuevo Iterator
(por ejemplo, cuando algún tipo concreto en sí mismo no se implementaTraversable
).
Para el recorrido recursivo, a menos que no use una Traversal
iteración de recorrido recursiva predefinida , normalmente necesita crear una instancia de la RecursiveIteratorIterator
iteración existente o incluso escribir una iteración de recorrido recursiva que sea Traversable
suya para tener este tipo de iteración de recorrido foreach
.
Propina: probablemente no implementó uno ni el otro por su cuenta, por lo que esto podría ser algo que valga la pena hacer para su experiencia práctica de las diferencias que tienen. Encontrará una sugerencia de bricolaje al final de la respuesta.
Diferencias técnicas en resumen:
IteratorIterator
toma cualquiera Traversable
para el recorrido lineal,RecursiveIteratorIterator
necesita uno más específico RecursiveIterator
para recorrer un árbol.IteratorIterator
expone su Iterator
vía principal getInnerIerator()
,RecursiveIteratorIterator
proporciona el sub-activo actual Iterator
sólo a través de ese método.IteratorIterator
no tiene conocimiento de nada como padres o hijos,RecursiveIteratorIterator
sabe cómo atrapar y atravesar a los niños.IteratorIterator
no necesita una pila de iteradores, RecursiveIteratorIterator
tiene dicha pila y conoce el sub-iterador activo.IteratorIterator
tiene su orden debido a la linealidad y no hay opción, RecursiveIteratorIterator
tiene una opción para un mayor recorrido y necesita decidir por cada nodo (decidido a través del modo porRecursiveIteratorIterator
).RecursiveIteratorIterator
tiene más métodos que IteratorIterator
.Para resumir: RecursiveIterator
es un tipo concreto de iteración (bucle sobre un árbol) que funciona en sus propios iteradores, a saber RecursiveIterator
. Ese es el mismo principio subyacente que con IteratorIerator
, pero el tipo de iteración es diferente (orden lineal).
Idealmente, también puede crear su propio conjunto. Lo único necesario es que su iterador implemente lo Traversable
que es posible a través de Iterator
o IteratorAggregate
. Entonces puedes usarlo con foreach
. Por ejemplo, algún tipo de objeto de iteración recursiva transversal de árbol ternario junto con la interfaz de iteración correspondiente para los objetos de contenedor.
Repasemos con algunos ejemplos de la vida real que no son tan abstractos. Entre interfaces, iteradores concretos, objetos contenedores y semántica de iteración, esto tal vez no sea una mala idea.
Tome una lista de directorio como ejemplo. Considere que tiene el siguiente árbol de archivos y directorios en el disco:
Mientras que un iterador con orden lineal simplemente atraviesa la carpeta y los archivos de nivel superior (una lista de un solo directorio), el iterador recursivo también atraviesa las subcarpetas y enumera todas las carpetas y archivos (una lista de directorios con listas de sus subdirectorios):
Non-Recursive Recursive
============= =========
[tree] [tree]
├ dirA ├ dirA
└ fileA │ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
Puede compararlo fácilmente con el IteratorIterator
que no tiene recursividad para recorrer el árbol de directorios. Y elRecursiveIteratorIterator
que puede atravesar el árbol como muestra el listado recursivo.
En un primer momento un ejemplo muy básico, con una DirectoryIterator
que implementa Traversable
lo que permite foreach
a iterar sobre ella:
$path = 'tree';
$dir = new DirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
El resultado ejemplar para la estructura de directorios anterior es:
[tree]
├ .
├ ..
├ dirA
├ fileA
Como puede ver, esto aún no está usando IteratorIterator
o RecursiveIteratorIterator
. En su lugar, solo usa foreach
que opera en elTraversable
interfaz.
Como foreach
de forma predeterminada solo conoce el tipo de iteración denominado orden lineal, es posible que deseemos especificar el tipo de iteración explícitamente. A primera vista, puede parecer demasiado detallado, pero para fines de demostración (y para hacer la diferencia RecursiveIteratorIterator
más visible más adelante), especifiquemos el tipo lineal de iteración especificando explícitamente el IteratorIterator
tipo de iteración para la lista de directorios:
$files = new IteratorIterator($dir);
echo "[$path]\n";
foreach ($files as $file) {
echo " ├ $file\n";
}
Este ejemplo es casi idéntico al primero, la diferencia es que $files
ahora es un IteratorIterator
tipo de iteración para Traversable
$dir
:
$files = new IteratorIterator($dir);
Como de costumbre, el acto de iteración lo realiza foreach
:
foreach ($files as $file) {
La salida es exactamente la misma. Entonces, ¿qué es diferente? Diferente es el objeto utilizado dentro de foreach
. En el primer ejemplo es un DirectoryIterator
en el segundo ejemplo es IteratorIterator
. Esto muestra la flexibilidad que tienen los iteradores: puede reemplazarlos entre sí, el código interno foreach
simplemente continúa funcionando como se esperaba.
Comencemos a obtener la lista completa, incluidos los subdirectorios.
Como ahora hemos especificado el tipo de iteración, consideremos cambiarlo a otro tipo de iteración.
Sabemos que debemos atravesar todo el árbol ahora, no solo el primer nivel. Para tener ese trabajo con un simple foreach
necesitamos un tipo diferente de iterador: RecursiveIteratorIterator
. Y eso solo puede iterar sobre objetos contenedores que tienen la RecursiveIterator
interfaz .
La interfaz es un contrato. Cualquier clase que lo implemente se puede usar junto con RecursiveIteratorIterator
. Un ejemplo de tal clase es the RecursiveDirectoryIterator
, que es algo así como la variante recursiva deDirectoryIterator
.
Veamos un primer ejemplo de código antes de escribir cualquier otra oración con la palabra I:
$dir = new RecursiveDirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
Este tercer ejemplo es casi idéntico al primero, sin embargo, crea una salida diferente:
[tree]
├ tree\.
├ tree\..
├ tree\dirA
├ tree\fileA
De acuerdo, no tan diferente, el nombre del archivo ahora contiene el nombre de la ruta al frente, pero el resto también se ve similar.
Como muestra el ejemplo, incluso el objeto de directorio ya implementa la RecursiveIterator
interfaz, esto aún no es suficiente para foreach
recorrer todo el árbol de directorios. Aquí es donde RecursiveIteratorIterator
entra en acción. El ejemplo 4 muestra cómo:
$files = new RecursiveIteratorIterator($dir);
echo "[$path]\n";
foreach ($files as $file) {
echo " ├ $file\n";
}
Usar el en RecursiveIteratorIterator
lugar de solo el $dir
objeto anterior hará que se foreach
recorran todos los archivos y directorios de forma recursiva. Esto luego enumera todos los archivos, ya que ahora se ha especificado el tipo de iteración del objeto:
[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
Esto ya debería demostrar la diferencia entre recorrido plano y árbol. El RecursiveIteratorIterator
puede atravesar cualquier estructura en forma de árbol como una lista de elementos. Debido a que hay más información (como el nivel en el que tiene lugar actualmente la iteración), es posible acceder al objeto iterador mientras se itera sobre él y, por ejemplo, sangrar la salida:
echo "[$path]\n";
foreach ($files as $file) {
$indent = str_repeat(' ', $files->getDepth());
echo $indent, " ├ $file\n";
}
Y salida del Ejemplo 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
Seguro que esto no gana un concurso de belleza, pero muestra que con el iterador recursivo hay más información disponible que solo el orden lineal de clave y valor . Incluso foreach
solo se puede expresar este tipo de linealidad, acceder al propio iterador permite obtener más información.
De manera similar a la metainformación, también hay diferentes formas posibles de atravesar el árbol y, por lo tanto, ordenar la salida. Este es el modo deRecursiveIteratorIterator
y se puede configurar con el constructor.
El siguiente ejemplo le dirá a la RecursiveDirectoryIterator
que elimine las entradas de puntos ( .
y ..
) ya que no las necesitamos. Pero también se cambiará el modo de recursividad para tomar el elemento padre (el subdirectorio) primero ( SELF_FIRST
) antes que los hijos (los archivos y subdirectorios del subdirectorio):
$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 salida ahora muestra las entradas del subdirectorio correctamente enumeradas, si compara con la salida anterior, las que no estaban allí:
[tree]
├ tree\dirA
├ tree\dirA\dirB
├ tree\dirA\dirB\fileD
├ tree\dirA\fileB
├ tree\dirA\fileC
├ tree\fileA
Por lo tanto, el modo de recursividad controla qué y cuándo se devuelve una rama u hoja en el árbol, para el ejemplo del directorio:
LEAVES_ONLY
(predeterminado): solo enumera archivos, no directorios.SELF_FIRST
(arriba): lista el directorio y luego los archivos allí.CHILD_FIRST
(sin ejemplo): enumere los archivos en el subdirectorio primero y luego en el directorio.Salida del ejemplo 5 con los otros dos modos:
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
Cuando compara eso con el recorrido estándar, todas estas cosas no están disponibles. Por lo tanto, la iteración recursiva es un poco más compleja cuando necesitas entenderla, sin embargo, es fácil de usar porque se comporta como un iterador, lo pones en ay foreach
listo.
Creo que estos son ejemplos suficientes para una respuesta. Puede encontrar el código fuente completo, así como un ejemplo para mostrar árboles ascii de buen aspecto en esta esencia: https://gist.github.com/3599532
Hágalo usted mismo: haga el
RecursiveTreeIterator
trabajo línea por línea.
El ejemplo 5 demostró que hay metainformación disponible sobre el estado del iterador. Sin embargo, esto se demostró a propósito dentro de la foreach
iteración. En la vida real, esto pertenece naturalmente al interior delRecursiveIterator
.
Un mejor ejemplo es el RecursiveTreeIterator
, se encarga de sangrar, prefijar, etc. Vea el siguiente fragmento de código:
$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á destinado a la línea de trabajo de la línea, la salida es bastante sencillo con un pequeño problema:
[tree]
├ tree\dirA
│ ├ tree\dirA\dirB
│ │ └ tree\dirA\dirB\fileD
│ ├ tree\dirA\fileB
│ └ tree\dirA\fileC
└ tree\fileA
Cuando se usa en combinación con un RecursiveDirectoryIterator
, muestra el nombre completo de la ruta y no solo el nombre del archivo. El resto luce bien. Esto se debe a que los nombres de archivo son generados por SplFileInfo
. En su lugar, deben mostrarse como el nombre de base. La salida deseada es la siguiente:
/// Solved ///
[tree]
├ dirA
│ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
Cree una clase de decorador que se pueda usar con en RecursiveTreeIterator
lugar de RecursiveDirectoryIterator
. Debe proporcionar el nombre base de la actual en SplFileInfo
lugar del nombre de la ruta. El fragmento de código final podría verse así:
$lines = new RecursiveTreeIterator(
new DiyRecursiveDecorator($dir)
);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));
Estos fragmentos incluidos $unicodeTreePrefix
son parte de la esencia del Apéndice: Hágalo usted mismo: Haga el RecursiveTreeIterator
trabajo línea por línea. .
RecursiveIteratorIterator
porque esto es común con otros tipos, pero proporcioné información técnica sobre cómo funciona realmente. Creo que los ejemplos muestran bien las diferencias: el tipo de iteración es la principal diferencia entre los dos. No tengo idea de que si compras el tipo de iteración, lo acuñas de manera un poco diferente, pero en mi humilde opinión no es fácil con la semántica que tienen los tipos de iteración.
¿Cuál es la diferencia de
IteratorIterator
yRecursiveIteratorIterator
?
Para comprender la diferencia entre estos dos iteradores, primero se debe comprender un poco acerca de las convenciones de nomenclatura utilizadas y lo que entendemos por iteradores "recursivos".
PHP tiene iteradores no "recursivos", como ArrayIterator
y FilesystemIterator
. También hay iteradores "recursivos" como RecursiveArrayIterator
y RecursiveDirectoryIterator
. Los últimos tienen métodos que les permiten profundizar, los primeros no.
Cuando las instancias de estos iteradores se repiten por sí solas, incluso las recursivas, los valores solo provienen del nivel "superior" incluso si se repiten en una matriz anidada o un directorio con subdirectorios.
Los iteradores recursivos implementan un comportamiento recursivo (vía hasChildren()
, getChildren()
) pero no lo explotan .
Podría ser mejor pensar en los iteradores recursivos como iteradores "recurrentes", tienen la capacidad de ser iterados de manera recursiva pero simplemente iterar sobre una instancia de una de estas clases no hará eso. Para explotar el comportamiento recursivo, sigue leyendo.
Aquí es donde RecursiveIteratorIterator
entra en juego. Tiene el conocimiento de cómo llamar a los iteradores "recurrentes" de tal manera que profundice en la estructura en un bucle normal y plano. Pone en acción el comportamiento recursivo. Básicamente hace el trabajo de pasar por encima de cada uno de los valores en el iterador, buscando ver si hay "niños" en los que recurrir o no, y entrar y salir de esas colecciones de niños. Pegas una instancia de RecursiveIteratorIterator
en un foreach y se sumerge en la estructura para que tú no tengas que hacerlo.
Si RecursiveIteratorIterator
no se usó, tendría que escribir sus propios bucles recursivos para explotar el comportamiento recursivo, verificando el iterador "recurrente" hasChildren()
y usando getChildren()
.
Así que esa es una breve descripción general de RecursiveIteratorIterator
, ¿en qué se diferencia IteratorIterator
? Bueno, básicamente estás haciendo el mismo tipo de pregunta que ¿Cuál es la diferencia entre un gatito y un árbol? El hecho de que ambos aparezcan en la misma enciclopedia (o manual, para los iteradores) no significa que deba confundirse entre los dos.
El trabajo del IteratorIterator
es tomar cualquier Traversable
objeto y envolverlo de manera que satisfaga la Iterator
interfaz. Un uso de esto es poder aplicar el comportamiento específico del iterador en el objeto no iterador.
Para dar un ejemplo práctico, la DatePeriod
clase es Traversable
pero no un Iterator
. Como tal, podemos recorrer sus valores con foreach()
pero no podemos hacer otras cosas que normalmente haríamos con un iterador, como filtrar.
TAREA : Repita los lunes, miércoles y viernes de las próximas cuatro semanas.
Sí, esto es trivial al foreach
pasar sobre el DatePeriod
y usar un if()
dentro del ciclo; ¡pero ese no es el objetivo de este ejemplo!
$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) { … }
El fragmento de código anterior no funcionará porque CallbackFilterIterator
espera una instancia de una clase que implemente la Iterator
interfaz, que DatePeriod
no lo hace. Sin embargo, dado que es Traversable
, podemos satisfacer fácilmente ese requisito utilizando IteratorIterator
.
$period = new IteratorIterator(new DatePeriod(…));
Como puede ver, esto no tiene nada que ver con iterar sobre clases de iterador ni recursividad, y ahí radica la diferencia entre IteratorIterator
y RecursiveIteratorIterator
.
RecursiveIteraratorIterator
es para iterar sobre un RecursiveIterator
(iterador "recurrente"), explotando el comportamiento recursivo que está disponible.
IteratorIterator
es para aplicar Iterator
comportamiento a Traversable
objetos que no son iteradores .
IteratorIterator
solo el tipo estándar de recorrido de orden lineal para Traversable
objetos? ¿Aquellos que podrían usarse sin él tal cual foreach
? Y aún más, ¿no es RecursiveIterator
siempre un Traversable
y por lo tanto no solo IteratorIterator
sino también RecursiveIteratorIterator
siempre "para aplicar Iterator
comportamiento a objetos no iteradores, Traversables" ? (Ahora diría que foreach
aplica el tipo de iteración a través del objeto iterador en objetos contenedor que implementan una interfaz de tipo iterador, por lo que estos son objetos contenedor-iterador, siempre Traversable
)
IteratorIterator
es una clase que se trata de envolver Traversable
objetos en un Iterator
. Nada más . Parece que está aplicando el término de manera más general.
Recursive
en RecursiveIterator
implica comportamiento, mientras que un nombre más adecuado hubiera sido uno que describa la capacidad, como RecursibleIterator
.
Cuando se usa con iterator_to_array()
, RecursiveIteratorIterator
recorrerá recursivamente la matriz para encontrar todos los valores. Lo que significa que aplanará la matriz original.
IteratorIterator
mantendrá la estructura jerárquica original.
Este ejemplo le mostrará claramente la diferencia:
$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))
es equivalente a new ArrayIterator($array)
, es decir, lo externo IteratorIterator
no hace nada. Además, el aplanamiento de la salida no tiene nada que ver con iterator_to_array
eso, simplemente convierte el iterador en una matriz. El aplanamiento es una propiedad de la forma en que RecursiveArrayIterator
camina su iterador interno.
RecursiveDirectoryIterator muestra el nombre completo de la ruta y no solo el nombre del archivo. El resto luce bien. Esto se debe a que los nombres de archivo son generados por SplFileInfo. En su lugar, deben mostrarse como el nombre de base. La salida deseada es la siguiente:
$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";
}
}
salida:
tree
├ dirA
│ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA