Recorrer array multidimensional recursivamente

A veces tenemos que tartar con arrays multidimensionales que son bastante complicados en su estructura, por lo que tenemos que empezar a escribir foreach’s anidados como locos, con la consecuencia inmediata de bajar la eficiencia de nuestros scripts y de disminuir la legibilidad de nuestro código.

Vamos a ver como mejorar estos inconvenientes usando una función recursiva.
Supongamos que tenemos el siguiente array:

$frases[] = [
            'yahoo'=> [
                'title'=> [
                   'count'=> 1, 
                   'text' => "Yahoo!!"
                ], 
                '__total__'=>20
               ]
             ];
$frases[] = ['google'=> ['title'=> ['count'=>21, 'text'=>"google!!"], '__total__'=>230]];	 
$frases[] = ['msn'=> ['title'=> ['count'=>25, 'text'=>"msn!!"] , '__total__'=>123, 'mas'=> ['elem1','elem2']]];
$frases[] = ['elem3'];  

/* asi se ve la estructura del array 
Array
(
    [0] => Array
        (
            [yahoo] => Array
                (
                    [title] => Array
                        (
                            [count] => 1
                            [text] => Yahoo!!
                        )

                    [__total__] => 20
                )

        )

    [1] => Array
        (
            [google] => Array
                (
                    [title] => Array
                        (
                            [count] => 21
                            [text] => google!!
                        )

                    [__total__] => 230
                )

        )

    [2] => Array
        (
            [msn] => Array
                (
                    [title] => Array
                        (
                            [count] => 25
                            [text] => msn!!
                        )

                    [__total__] => 123
                    [mas] => Array
                        (
                            [0] => elem1
                            [1] => elem2
                        )

                )

        )

    [3] => Array
        (
            [0] => elem3
        )

)
*/ 

Como podemos ver es bastante complicado en su estructura, por lo que utilizar foreach anidados para recuperar los datos sería un dolor de cabeza; además, para diferentes estructuras deberíamos reescribir el código para recorrerla.
Veríamos algo como lo siguiente:

foreach($frases as $frase_key => $frase_info){ 
    foreach($frase_info as $tag_key => $tag_info){ 
        foreach($tag_info as $data_key => $data_info){
        //codigo
        //....
        }
    }
}

La clave del asunto es ver este tipo de arrays como si fueran grafos (ver teoría de grafos) y de esta manera crear una función recursiva que use algún algoritmo conocido para recuperar los valores.

Veamos la función:

function recorro($matriz){
    foreach($matriz as $key=>$value){
          if (is_array($value)){
              //si es un array sigo recorriendo
              echo 'key:'. $key;
              echo '';
              recorro($value);
          }else{  
             //si es un elemento lo muestro
             echo $key.': '.$value ;
             echo '';
          }		
   }
}

Esta función recorre el array completamente sin importar lo intrincado de su estructura, además, es muy eficiente y la lectura del código es muy simple, también es una solución genérica y reusable.

Juan Benitez

Fundador de Tecnopedia.net. Licenciado en Informática, desarrollador Web, Android y PHP. Apasionado de las tecnologías y el fútbol. Reparto mis días programando, creando sitios, apps o escribiendo en @Tecnopedianet... y sí, además tengo esposa y una hija ;)

View all posts by Juan Benitez →

28 thoughts on “Recorrer array multidimensional recursivamente

  1. Hola Gonzalo, el operador «=>» sirve para diferenciar la clave y el valor de un elemento de una array. Por ejemplo, al crear un array puedo hacerlo indicando cuales serán sus claves y valores:

    $coleccion = array(clave1 => valor1, clave2 => valor2);
    

    Además, lo puedes usar en la sentencia «foreach» con el mismo propósito:

    foreach($coleccion as $clave => $valor){
           //para cada elemento del array tendremos en la variable $clave su clave
           //y enla variable $valor el valor del elemento
    }
    

    Ejemplo completo:

    $frutas = array('rojo' => 'manzana', 'amarillo' => 'banana', 'violeta' => 'uva');
    foreach($frutas as $clave => $valor){
           echo "La fruta de color ".$clave." es la ".$valor."";
    }
    

    Salida:

    La fruta de color rojo es la manzana
    La fruta de color amarillo es la banana
    La fruta de color violeta es la uva
    
  2. Muchas gracias y más por esos buenos ejemplos…Hacía bastante tenía la duda y Google no sirve para buscar símbolos.

  3. Que grande amigo, sos un groxo…!!! me solucionaste un problemon no me podia ir sin comentar….!!!! saludos

  4. BUEN DIA CHICOS , NECESITO REALIZAR UN EJERCIO, DE LIQUIDACION DE SUELDOS.
    TENGO EL SIGUIENTE CODIGO

    Liquidacion de sueldos

    Emplados
    Horas
    Sueldos

    <?php

    // creamos los array correspondientes para empleados

    $empleados = array (
    array('Mendez','150','1500'),
    array('Gomez','190','1900'),
    array('Ramirez','250','2500'),
    array('Martinez','300','3000'),
    );

    foreach ($empleados as $empleado) {
    foreach ($empleado as $campo)
    echo "$campo»»;

    echo »;
    }

    ?>

    LA PREGUNTA ES ALGUIEN ME PUEDE AYUDAR, POR QUE NO ME QUEDA BIEN, LA IDEA ES HACER UNA TABLA QUE TENGA NOMBRE-HORAS-SUELDOS Y ABAJO DE TODO LOS TOTALES , CUALQUIER AYUDA SIRVE MUCHO ,., GRACIASSSSSSSSSSSSS

  5. Bueno veamos si esto te sirve:

    
    $empleados = array (
    	array('Mendez','150','1500'),
    	array('Gomez','190','1900'),
    	array('Ramirez','250','2500'),
    	array('Martinez','300','3000'),
    );
    
    echo "";
    foreach ($empleados as $empleado) {
    	echo "";
    	foreach ($empleado as $key=>$campo){
    		
    		echo "";
    		
    		switch($key){
    		   case 1:
    			// sumo las horas
    			$horas_totales += $campo;
    			break;
    		   case 2:
    			// sumo los sueldos
    			$sueldos_totales += $campo;
    			break;
    		}
    	}
    	echo "";
    }
    echo "";
    echo "
    ".$campo."
    Totales".$horas_totales." ".$sueldos_totales."
    ";
  6. Hola, he leido sus comentario y espero puedan ayudarme, tengo un problema lo que quiero hacer es no insertar un registro en una fecha y horas ya ocupados. en mi base de datos guardo todo en una tabla… el siquiente código solo es el de la comparación que tengo, me compara las horas pero si tengo mas de un registro con la misma fecha ya no funciona, solo hace la comparación con el primero. Como podria realizar la comparación para que me compare todos los registros que tienen la misma fecha.

    <?php

    include (\"conexion.php\");

    //Datos del salon
    $salon_php=$_POST[\"salon_php\"];
    $nombre_evento=$_POST[\"nombre_evento\"];
    $apepat_resp=$_POST[\"apepat_resp\"];
    $apemat_resp=$_POST[\"apemat_resp\"];
    $nombre_resp=$_POST[\"nombre_resp\"];
    $fecha=$_POST[\"fecha\"];
    $hora_inicio=$_POST[\"hora_inicio\"];
    $hora_fin=$_POST[\"hora_fin\"];

    $query=\"SELECT * FROM tbl_evento where fecha = \’$fecha\’\";
    $result=mysql_query($query,$conexion) or die(\"Error: \".mysql_error());
    $Rs=mysql_fetch_array($result);
    $Rc=mysql_num_rows($result);

    echo $Rc;
    for($i=0;$i<$Rc;$i++)
    {
    if(($Rs[\"id_salon\"]!= $salonFin) && (($hora_inicio < $Rs[\"hora_inicio\"] && $hora_fin <= $Rs[\"hora_inicio\"]) || ($hora_inicio >= $Rs[\"hora_fin\"] && $hora_fin > $Rs[\"hora_fin\"])))
    {
    echo \"horario libre\";

    }
    else
    {
    echo \"horario ocupado\";

    }

    }

    ?>

  7. Buen Dia Juan..

    Gracias por este codigo, esta muy bueno. Te tengo una pregunta para ver si me puedes dar una mano.

    usando tu codigo
    function recorro($matriz)
    {

    foreach($matriz as $key=>$value){

    if (is_array($value)){
    //si es un array sigo recorriendo
    echo ‘key:’. $key;
    echo »;
    recorro($value);
    }else{
    //si es un elemento lo muestro
    echo $key.’: ‘.$value ;
    echo »;
    }

    }

    }

    en esta parte de codigo yo tengo un condicional que me funciona bien lo que necesito es que si la condicion del condicional se cumple tengo que sustituir el valor de la KEY con el nuevo valor y que este nuevo valor se quede grabado en el array.

    como lo puedo hacer???

  8. Hola Sebastián, no logro comprender exactamente qué precisas, tal vez si envías un ejemplo pueda ayudarme.
    saludos, Juan.

  9. Hola Juan, gracias por contestar.

    te explico. tengo un array PHP que se llama form, este array es multidimencional y por eso me va muy bien tu codigo porque el array es dinamico es decir, segun la llamada a la base de dato va a tener informacion distinta.

    Nosotros tenemos la exigencia que ciertos valores en este array dedan tener la traduccion a segun del usuario que se conecta con la aplicacion. por esto hice la funcion extracttag, que en realidad lo que hace es recibir el valor de la key que lee tu foreach y lo busca en un archivo xml donde tengo la traducción del idioma del usuario.

    tu funcion me funciona perfectamente, lo que hice fue agregar un IF donde compruebo que el valor de la Key en el punto donde esta el foreach se encuentre en el archivo xml del lenguage del usuario si se encuentra entonce deberia cambiarme el valor en el array que el forearh me esta leyendo

    aqui abajo el codigo

    <?php
    $userlang = //codigo script que busca en la base de datos la informacion del idioma del usuario…..

    $form[0] = $base; //array php
    $form[1] = $array1; //array php
    $form[2] = $array2; //array php

    function extracttag($namefile, $name)
    {
    $file1 = $namefile.\".xml\";
    $file2 = $namefile.\".xml\";
    if (file_exists($file1))
    {
    $newtag = simplexml_load_file($file1);
    if ($newtag) {
    foreach ($newtag->tag as $a) {
    if ($a->tag_l == $name) {
    return $a->translations;
    }
    }
    return $a;
    } else return \"\";
    }
    elseif (file_exists($file2))
    {
    $newtag = simplexml_load_file($file2);
    if ($newtag) {
    foreach ($newtag->tag as $a) {
    if ($a->tag_l == $name) {
    return $a->translations;
    }
    }
    return $a;
    } else return \"\";
    } else
    {
    echo \"Error_open_xml\";
    }
    }

    function recorro($matriz)
    {
    foreach($matriz as $key=>$value)
    {
    if (is_array($value))
    {
    //si es un array sigo recorriendo
    recorro($value);
    }else
    {
    //si es un elemento lo muestro
    $tagvalue = extracttag($userlang, $value);
    if ($tagvalue != \"\"){$value = $tagvalue; $matriz[$key] = $tagvalue; }
    }
    }return($matriz);
    }

    recorro($form);

    echo \'<pre>\’; // Esto para que sea mas legible
    var_dump($form);
    echo \'</pre>\’;

  10. Sebastián, ahora si esta claro, una forma de hacerlo es cambiar la función recorro() para que reciba el parámetro por referencia, de esta manera vamos modificando la matriz dentro de la función y evitamos el return que ocasiona que se «corte» el recorrido del array.
    Prueba con esta versión de la función:

    function recorro(&$matriz) // he agregado el pasaje por referencia
    {
    	foreach($matriz as $key=>$value)
    	{
    		if (is_array($value))
    		{
    			//si es un array sigo recorriendo
    			recorro($value);
    			$matriz[$key]=$value; //he agregado esta linea
    		}else
    		{
    			//si es un elemento lo muestro
    			$tagvalue = extracttag($userlang, $value);
    			if($tagvalue!=""){
    				$matriz[$key]=$tagvalue;
    			}		
    
    		}
    	}
    }
    

    Espero que te sirva.
    saludos, Juan.

  11. Te mereces una estatua en la principal plaza de tu ciudad. Yo había probado usar referencia pero en el foreach, no se me ocurrió hacerlo en la función.

    Una cosita mas, el valor que me pone cuando se cumple la función if($tagvalue!=»»){ $matriz[$key]=$tagvalue;} es el siguiente:

    [«tooltip»]=> object(SimpleXMLElement)#8 (1)
    {[0]=> string(26) «test,test,test,test,test, » }

    yo necesito que el valor sea asi:
    [«tooltip»]=> string(26) «test,test,test,test,test, »

    No se porque se trae el objeto XML completo.

    De nuevo Gracias,,

  12. Bueno, en ese caso parece ser la forma en que accedes a los elementos del XML, deberías leer la documentación de SimpleXml para hacerlo correctamente.
    De todas formas, si quieres enviame el XML que usas y te doy una mano.

    saludos, Juan.

  13. De nuevo Juan Muchas gracias por tu aporte.

    aqui te dejo mi codigo

    con esta funcion php traigo el contenido de la etiqueta translations de mi archivo XML

    function extracttag($namefile, $name)
    {
    $file1 = $namefile.».xml»;
    if (file_exists($file1))
    {
    $newtag = simplexml_load_file($file1);

    if ($newtag)
    {
    foreach ($newtag->tag as $a)
    {
    if ($a->tag_l == $name)
    {
    return $a->translations;
    }
    }
    return $a;
    } else return «»;
    }
    else
    {
    echo «Error_open_xml»;
    }
    }

    y este es el contenido del archivo xml (puse solo unas cuantas tag para no hacer largo el comentario)

    Geolocation Lattest,test,test,test,test,
    Geolocation LongGeolocation LongGeolocation LongGeolocation LongGeolocation Long
    Site Name*Site Name*Site Name*Site Name*Site Name*Site Name*

  14. ¿Cómo pdría utilizar la función recursiva recorro($matriz) para ir insertando los registros en la BD?
    EL Problema es que recibo de un webservice un array asoativo multidimensional. Ejemplo:
    Array
    ([0] => Array ([IDMarca =>82 …….)
    [1] => Array ([IDMarca =>82 …….)
    )….
    En la función recorro si el elemento es array vuelvo a recorrerlo. Si voy guardando en un array los valores, voy guardando los valores de todas los arrays, y no podría diferenciar los registros que quiero insertar en la BD.
    No sé si me he explicado bien.
    Gracias.

    Un saludo

  15. Hola, es claro que si tu array tiene 1 solo nivel no vale la pena usar la función recursiva ya que con un foreach simple se soluciona, digo esto porque el ejemplo que pones pareciera ser solo 1 nivel…
    Para más de 1 nivel habría que ver un ejemplo de tus datos más completo; para decidir de que forma cargamos los datos para un futuro INSERT.
    saludos, Juan.

  16. Buenos días!
    No mi array es multidimensional variable.
    A ver pongo un ejemplo más claro.
    Realizo una llamada a un webservice y me devuelve un objeto StdClass. Utilizo una función objetToArray($array) que me convierte el objeto a un array multidimensional.
    El array multidimensional puede ser por ejemplo:
    Array
    (
    [0] => Array
    (
    [IDModelYear] => 1
    [ModelYear] => 1990
    [Versiones] => Array
    (
    [0] => Array
    (
    [IDVersion] => 463
    [Descripcion] => hola
    [Foto] => foto.jpg
    )
    [1] => Array
    (
    [IDVersion] => 464
    [Descripcion] => hola2
    [Foto] => foto
    )


    [1] => Array
    (
    [IDModelYear] => 1
    [ModelYear] => 2000
    [Versiones] => Array
    (
    [0] => Array
    (
    [IDVersion] => 279
    [Descripcion] => ejemplo
    [Foto] => foto.jpg
    )
    [1] => Array
    (
    [IDVersion] => 280
    [Descripcion] => ejemplox
    [Foto] => fotoy.jpg
    )

    Pues bien necesito ir recorriendo este array multidimensional asociativo e insertando registros en la Base de Datos.
    Los campos de la tabla serían:
    IDModelYear ModelYear IDVersion Descripcion Foto
    1 1990 463 hola foto.jpg
    1 1990 464 hola2 foto
    1 2000 279 ejemplo foto.jpg
    1 2000 280 ejemplox fotoy.jpg

    Básicamente, esto sería lo que quiero implementar. No sé como podría utilizar/modificar la función recursiva recorro para poder implementar lo que te comenta.

    Muchísimas gracias por adelantado.
    Perdone las molestias.

  17. Buenas! De verdad necesito una ayuda super inmensa, ya no se que hacer y estoy desesperado. Estoy realizando un sistema de inscripción, pero no logro que me cargue la sección que selecciono para «X» materia, me guarda una en blanco so simplemente me repite «N» veces la seccion de la materia con todas las materias seleccionadas.

    Aquí pongo el código del formulario donde el estudiante escoge la(s) materia(s):

    SELECCIONA LA(S) MATERIAS A CURSAR EN EL PERÍODO CONFIGURADO

    Código

    Materia

    Créditos

    Seccion

    <input name="tag[]" type="checkbox" value="» onclick=»Block(this,’Aukera’)» id=»txek»/>

    ‘0’»);
    while ($row1=mysql_fetch_array($result2))
    {

    echo «».$row1[‘sec’].»»;

    }
    mysql_free_result($result2)
    ?>

    RECUERDA QUE PARA EL PERÍODO INTENSIVO SÓLO SE PERMITE UN MÁXIMO DE 12 U.C.

    Y por este lado recojo la información y la inserto en mi tabla

    <?php
    mysql_connect("localhost","******","*******") or die("No se pudo conectar a la base de datos");

    //SELECCIONAMOS LA BASE DE DATOS CON LA CUAL VAMOS A TRABAJAR CAMBIEN EL VALOR POR LA SUYA
    mysql_select_db("******");{

    foreach($_POST['tags'] as $valor1)
    foreach($_POST['tag'] as $valor)
    {
    $qry = mysql_query("INSERT INTO inscripcion (lapso, sec, materia, exp) VALUES ('2014-I','".$valor1."', '".$valor."', '".$_SESSION['exp']."')") or die("Query: $qry Error: «.mysql_error());
    }
    }?>

    si podrian ayudarme, necesito que el sistema inscriba para el día miercoles.
    Ayudaaaa!!

  18. Hola.

    Intento crear un array recursivo del plan general contable. De forma que todas las cuentas de capital (1) sean a su vez arrays en forma de árbol del plan.

    alguna idea?

  19. Disculpa quisiera tu ayuda como puedo hacer cuando inserto un dato por input a php compararlo con un valor dentro de este array

    array('carro' => 'puerta','caucho','parachoque', 'avion'=>'aleron','cola','alas', 'casa'=>'cocina','comedor','ventana', 'bicicleta'=>'pedales','cadena','frenos', 'supermercado'=>'anaqueles','carritos','caja', 'equipo'=>'volumen','cd','cornetas'); 

    y mostrar a que pertenece por ejemplo si escribo puerta me siga que esta en carro

     

    muchas gracias de antemano

Comments are closed.