A practicar

A practicar cierra al haber cumplido su función

A practicar Desarrollo

Cerramos la sección A practicar…, sí, A practicar cierra con este último artículo donde solucionaremos el reto anterior. ¿Por qué cerramos esta sección? Sencillamente porque, a partir de ahora, los siguientes retos se estaban convirtiendo en programas mucho más amplios y cargados o, en su defecto, la práctica no mostraba correctamente lo que queríamos expresar y aprender. Si has llegado hasta aquí desde el principio, los nuevos retos, pese a que hubieran contenido novedades, no hubieran sido atractivos.

Por ello, A practicar cierra y seguiremos viendo todo lo que nos viene encima, que todavía es muchísimo, pero sin una aplicación tan “directa”. Empezar, por ejemplo, a ver ficheros de texto o la programación orientada a objetos con varios ejemplos y luego proponer un reto que, o era muy largo, o era un calco de los ejemplos vistos, creo que no hubiera aportado nada.

A practicar cierra, pero, antes de cerrar la sección, debemos dar solución al reto anterior, el cual fue de una dificultad más alta de los que acostumbramos.

Solución al reto anterior

El reto anterior era complejo, especialmente por el uso de la recursividad. En él se nos planteaban dos funciones, una que sumara todos los valores de un array que contenía más arrays en su interior, y otra que lo reorganizara en bloques. Vamos a recordar, en primer lugar, cuál era el array.

<?php
$array = array(
	6,
	5,
	2,
	array(
		5,
		2,
		8,
		7,
		5,
		array(
			6,
			3,
			7,
			8,
			1
		),
		3
	),
	8,
	6,
	array(
		7,
		1,
		array(
			8,
			3,
			2,
			array(
				5,
				2,
				3,
				9,
				1
			),
			7,
			6,
			5,
			4
		),
		2,
		8,
		1,
		8,
		7
	),
	1,
	2,
	6,
	9
);
?>

Como se ve, es un array de estructura sencilla aunque haya sido largo de plasmar. Lo único que tiene son números y más números y, en ocasiones, arrays de números dentro. Podríamos tener muchos más subniveles dentro del array.

La primera función que nos pedían era suma_recursiva, a la que le pasábamos el array y nos tenía que devolver la suma de todos sus valores, incluyendo los valores de los arrays interiores. Obviamente, teníamos que realizarlo utilizando recursividad. Veamos la solución y luego la comentamos con más detalle:

function suma_recursiva($array) {
	$suma = 0;
	foreach ($array as $value) {
		if (is_array($value)) {
			$suma += suma_recursiva($value);
		} else {
			$suma += $value;
		}
	}
	return $suma;
}

Las funciones recursivas no suelen ser excesivamente largas (siempre y cuando no tengamos que hacer muchas cosas “no recursivas” dentro), así que vemos que, a nivel de código, parece muy sencilla. Vamos a ver qué va haciendo en cada paso.

Nada más entrar en la función, en la línea 2, definimos nuestra variable $suma a 0. Esto lo va a hacer así siempre, en cada llamada a la función recursiva, pues vamos a sumar cada array de manera individual para luego devolver el valor.

Seguidamente, recorremos todo el array con el bucle FOREACH obteniendo cada uno de sus valores. A continuación, la condifición que le pasamos al IF es sencilla, pues debemos comprobar si el $value obtenido es un array o no. Esto se realiza con la función is_array que vimos en el artículo sobre funciones predefinidas en PHP pero también en validaciones de formularios. Es decir, debemos saber si el valor es un número o un array. Si es un número (pasaría por ELSE), entonces le sumamos el valor a la variable $suma. Pero si es un array, necesitamos sumar los valores dentro de su array. ¿Cómo?, con una llamada a la propia función suma_recursiva pero ahora con $value, que no deja de ser el array interno que nos hemos encontrado.

De esta forma, suma_recursiva irá encadenando llamadas recursivas cada vez que encuentre un array interno, sumará sus valores y nos devolverá la suma total, la cual sumaremos a la suma que ya teníamos en marcha para, al final, conseguir el valor total. Veamos una pequeña traza para ver mejor esta idea.

Valor general de $sumaInstrucciones que se realizan en cada momento
$suma = 0Los tres primeros elementos del array son 6, 5 y 2, por tanto al recorrerlos pasaremos por el ELSE y los iremos sumando a $suma hasta tener el valor 13
$suma = 13Aquí encontramos un array, por lo que volvemos a llamar recursivamente a la función pasándole este array. Si nos fijamos bien, este array será array(5,2,8,7,5,array(6,3,7,8,1),3), es decir, tiene también otro array en su interior
$suma = 13
$suma2 = 0
Al llamar recursivamente a la función con este nuevo array, tendremos una segunda suma (la hemos llamado $suma2 pero realmente no se guarda así en ningún sitio, sólo es para tenerlo claro nosotros). Aquí se recorrerá este array interno y se sumarán el 5, 2, 8, 7 y 5 para un total de 27 hasta encontrarnos con otro array más interno
$suma = 13
$suma2 = 27
$suma3 = 0
Recorremos este nuevo array interno con valores 6, 3, 7, 8 y 1, lo que nos dará una suma de 25. Como este array no tiene ningún otro array interno, la suma del mismo acabará y devolveremos el resultado de esta tercera suma
$suma = 13
$suma2 = 52
Al devolver el 25 anterior y sumarlo a lo que llevábamos en la segunda suma, un 27, tenemos un resultado de 52. Ahora seguiríamos recorriendo el array que habíamos dejado parado y al que le faltaba el valor 3. Por tanto, la suma de este array interno será de 55 y se lo devolveremos a la primera llamada general
$suma = 68Ya hemos sumado lo que había en $suma con lo que nos ha devuelto la suma de los arrays internos, por lo que seguimos. Ahora encontramos un 8 y un 6, lo que aumenta la suma hasta 82 antes de encontrarnos un nuevo array
$suma = 82
$suma2 = 0
Nuevo array interno, con un 7 y un 1 antes de encontrar otro array interno que provocará una nueva llamada recursiva
$suma = 82
$suma2 = 8
$suma3 = 0
Este array interno tiene un 8, un 3 y 2 que suman 13, pero nos encontramos, de nuevo, otro array dentro de él, por lo que paramos y llamamos de nuevo a la función recursiva
$suma = 82
$suma2 = 8
$suma3 = 13
$suma4 = 0
En este array, el más interno que encontraremos, tenemos 5, 2, 3, 9 y 1, que da un total de 20, valor que se le devolverá a la función anterior que llevaba una cuenta de 13, que ahora incrementará a 33
$suma = 82
$suma2 = 8
$suma3 = 33
Aún quedaban números aquí, un 7, un 6, un 5 y un 4, que dan 22 y debemos seguir sumando. Así pues, 33 más 22 serán 55 que será el valor devuelto
$suma = 82
$suma2 = 63
Habiendo completado dos arrays más internos, todavía tenemos 2, 8, 1, 8 y 7 por sumar a este array interno que lleva ya una cuenta de 63 y acabará, finalmente, sumando 89, los cuales se devolverán y sumarán a la suma general
$suma = 171Ya estamos llegando al final, pues sólo falta por sumar un 1, un 2, un 6 y un 9 y no quedan arrays internos
$suma = 189Finalmente, hemos pasado por todas las etapas y devuelto el resultado final

Podéis comprobar el resultado final si queréis, para ver que es correcto. La lógica, realmente, no es tan compleja. Hemos ido sumando bloques (arrays) y, cuando estaban sumados, los añadíamos a la suma que estaba esperando. En el fondo es como si hubiéramos tenido una suma así:

6+5+2+(5+2+8+7+5+(6+3+7+8+1)+3)+8+6+(7+1+(8+3+2+(5+2+3+9+1)+7+6+5+4)+2+8+1+8+7)+1+2+6+9

Y primero hubiéramos sumado lo del interior de los paréntesis.

Vamos, ahora, con la segunda función, reorganizar_recursivamente, que tendrá más complejidad a nivel de programación, pero no mucho más a nivel lógico.

function reorganizar_recursivamente($array, $nuevoArray = []) {
	$suma = 0;
	foreach ($array as $value) {
		if (is_array($value)) {
			$nuevoArray[] = $suma;
			$suma = 0;

			$nuevoArray = reorganizar_recursivamente($value, $nuevoArray);
		} else {
			$suma += $value;
		}
	}
	$nuevoArray[] = $suma;

	return $nuevoArray;
}

Por pasos. Tenemos ahora, como nos había dicho el reto, una función que recibe al mismo $array y que crea un $nuevoArray que, inicialmente, no tiene elementos. Ahora cada elemento hemos quedado que será la suma que llevemos acumulada hasta encontrar un nuevo array. Es más difícil de explicar que de ver.

Esta función, en primer lugar, define el valor 0 para $suma. Posteriormente hace lo mismo que la anterior, es decir, recorre el array y comprueba si el elemento es un array o no lo es. Si el elemento no es un array, actúa igual, es decir, incrementa el valor de $suma con el valor que ha encontrado. Pero si es un array ahora cambian las cosas.

Lo primero que hace, si es un array, es crear un nuevo elemento en el array $nuevoArray con lo que se lleva de suma hasta ese momento. Posteriormente, coloca de nuevo $suma al valor 0, porque lo que hemos sumado ya se ha incorporado al nuevo array y no lo necesitamos más. A continuación, vuelve a llamar a reorganizar_recursivamente con el elemento que ha encontrado (que es un array interno) y el array que estamos creando, pues lo necesita para seguir construyéndolo.

Nos queda un pequeño cambio y es que, una vez ha terminado de recorrer el array, la función añade lo que lleva sumado a $nuevoArray para tener un nuevo elemento y devuelve el $nuevoArray que se está construyendo sobre la marcha. Es complejo, sí, pero veamos la traza para verlo un poco más sencillamente.

$nuevoArrayInstrucciones que se realizan en cada momento
[]Los primeros elementos que se recorren son el 6, el 5 y el 2, lo que sumados dan 13. Al llegar al siguiente elemento, que es un array, añadimos este 13 a $nuevoArray y se coloca $suma a 0 para volver a empezar la cuenta luego
[13]Empezamos ahora a recorrer el primer array interno que nos encontramos, que tiene 5, 2, 8, 7 y 5 antes de encontrarse con otro array aún más interno. Esto suma 27, por lo que antes de entrar en el array más interno, volvemos a añadirlo a $nuevoArray
[13, 27]Este nuevo array interno tiene 6, 3, 7, 8 y 1 como valores, que suman 25. Como no hay más arrays internos, ya lo hemos recorrido todo y añadimos a $nuevoArray ese 25
[13, 27, 25]Ahora seguimos recorriendo los valores que nos quedaban en el array interno anterior. Pero sólo queda un 3, así que su suma será, lógicamente, 3, y añadimos el valor a $nuevoArray
[13, 27, 25, 3]Volvemos al array principal y encontramos ahora un 8 y un 6. Juntos suman 14 y, como encontramos otro array interno, añadimos ese 14
[13, 27, 25, 3, 14]El array interno tiene 7 y 1 antes de encontrarse a otro array más interno, así que añadimos esta vez 8
[13, 27, 25, 3, 14, 8]Nuevas iteraciones en el array interno nos dan 8, 3 y 2, añadiendo un 13 a $nuevoArray
[13, 27, 25, 3, 14, 8, 13]El array más interno de todos tiene 5, 2, 3, 9 y 1, que da 20 y será el valor que añadimos
[13, 27, 25, 3, 14, 8, 13, 20]Volviendo al array anterior, nos queda el 7, 6, 5 y 4 para un valor total de 22 que añadimos, otra vez, a $nuevoArray
[13, 27, 25, 3, 14, 8, 13, 20, 22]Acabamos con el array interno que habíamos encontrado con sus últimos valores, 2, 8, 1, 8 y 7, un total de 26 que van directos a $nuevoArray
[13, 27, 25, 3, 14, 8, 13, 20, 22, 26]Llegamos al final y nos queda el 1, el 2, el 6 y el 9, que dan 18 y será el último elemento que añadimos
[13, 27, 25, 3, 14, 8, 13, 20, 22, 26, 18]$nuevoArray está ahora completo, por los bloques que nos hemos ido encontrando

Como curiosidad, si sumáis esos números de $nuevoArray veréis que suman 189, como era de esperar. Anteriormente los habíamos sumado todos, pero ahora los hemos agrupado por bloques.

Aunque no tenga utilidad muy real lo que hemos hecho, nos ha servido para ver cómo utilizar funciones recursivas. Una vez hemos llegado a concebir la función recusiva, nos va a parecer hasta sencilla y lógica, pues suelen ser muy limpias en general. Pero la dificultad es llegar a ellas, pues tenemos que tener claros todos los pasos a realizar.

Despedida de la sección

Como hemos comentado al principio, A practicar cierra debido a la mayor complejidad de código que vamos viendo y a la dificultad que conlleva ofrecer un reto sencillo y accesible. Pero esto no significa que nos quedaremos sin ejemplos, pues los artículos vendrán cargados de situaciones donde podremos practicar los nuevos conocimientos. A practicar cierra pero la programación continúa.

Deja una respuesta