Iterando en PHP

A veces no le damos más de una vuelta a una cosa aparentemente tan sencilla como recorrer todos los elementos de un array, pero la opción que elijamos puede llegar a tener un impacto considerable en el rendimiento de la aplicaciójn.

Tradicionalmente recorrer un array ha estado ligado a la estructura de control for, que en PHP es tan potente como en C.

A veces, también, nos perdemos en los océanos de Internet para buscar cosas y nos olvidamos de buscar en el manual, que en el caso de PHP es un buen manual, repleto de consejos. No hay muchos proyectos que tengan una documentación de tanta calidad como PHP. Su calidad unido a que dejan hacer comentarios y aportaciones en cada página, hace que sea la referencia más importante de este lenguaje.

Vamos a ver primero una forma de recorrer un array con un error de principiante:

$array = [1,2,3,4,5];
for($i = 0; $i < count($array); ++$i)
{
   //...lo que sea
}

Esto funciona pero la longitud del array va a ser calculada a cada iteración, que es innecesario porque la longitud del array -en principio- no va a cambiar. Una forma mejor es calcularla antes y almacenarla en una variable:

$array = [1,2,3,4,5];
$l = count($array);
for($i = 0; $i < $l; ++$i)
{
   //...lo que sea
}

Esto ya va mucho mejor, pero no es muy compacto. Aquí viene la gracia de las expresiones que van dentro de la especificación del bucle for, que permite escribir más de una expresión y separlas por comas:

$array = [1,2,3,4,5];
for($i = 0, $l = count($array); $i < $l; ++$i)
{
   //...lo que sea
}

Es una notación muy compacta. Sería más interesante todavía descartar automáticamente la variable $l al finalizar el bucle, que hubiera alguna forma de hacer que su ámbito fuera el bloque, pero PHP no lo hace así y el valor de $l sigue estando disponible después de la iteración.

Lo cual nos lleva al foreach, que para iteraciones simples como esta es la más rápida y elegante y que además no deja ninguna variable inútil en el ámbito actual (Inútil en principio, a lo mejor luego te interesa para alguna otra tarea). Si no necesitamos nada especial, la mejor opción es:

$array = [1,2,3,4,5];
foreach($array as $elemento)
{
   //...lo que sea
}

Y como no es lo mismo suponerlo que comprobarlo, ahora hacemos un pequeño experimento midiendo tiempos. Vamos a ejecutar cada una de las 4 opciones, por ejemplo, 20 veces. Almacenamos el tiempo empleado en cada opción y al final sacamos las medias.

$tiempos_1 = [];
$tiempos_2 = [];
$tiempos_3 = [];
$tiempos_4 = [];
$a = [1,2,3,4,5];
$iteraciones = 20;
foreach(range(0,$iteraciones) as $iteracion)
{
    $start = microtime();
    //... versión 1 del bucle
    $tiempos_1[] = microtime() - $start;

    $start = microtime();
    //... versión 2 del bucle
    $tiempos_2[] = microtime() - $start;

    $start = microtime();
    //... versión 3 del bucle
    $tiempos_3[] = microtime() - $start;

    $start = microtime();
    //... versión 4 del bucle
    $tiempos_4[] = microtime() - $start;
}

printf("Media opción 1: %E".PHP_EOL, array_sum($tiempos_1) / $iteraciones);
printf("Media opción 2: %E".PHP_EOL, array_sum($tiempos_2) / $iteraciones);
printf("Media opción 3: %E".PHP_EOL, array_sum($tiempos_3) / $iteraciones);
printf("Media opción 4: %E".PHP_EOL, array_sum($tiempos_4) / $iteraciones);

Podeis ejecutar varias veces todo esto y os dará medias distintas, pero en general las opciones 2,3,4 serán siempre bastante más rápidas y la opción 4 ligeramente más rápida que la 2 y la 3. La opción 4 es más rápida por muy poco y para verlo hay que imprimir los tiempos en notación científica con el modificador de formato %E. Ejemplo de salida:

Media opción 1: 1.540000E-5
Media opción 2: 9.200000E-6
Media opción 3: 8.950000E-6
Media opción 4: 8.700000E-6

¡Pero eso no es todo! PHP a veces es un poco suyo para ciertas cosas. Hemos dicho que después de un foreach no queda rastro de nada que haya sido auxiliar para la iteración, pero no es verdad. Haced la prueba imprimiendo el valor de $elemento después de finalizar la iteración y os llevaréis una sorpresa. Los peligros son variados con este comportamiento, así que se recomienda hacer un unset($elemento) después de finalizar la iteración y utilizar una variable con el mismo nombre antes de comenzar la iteración.

Responder

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *