Por circunstancias del trabajo me ví envuelto en una cruzada para resolver un proceso. Tenía que devolver todas las combinaciones entre elementos de un array bidimensional combinando cada elemento de una dimension con los elementos de las otras dimensiones, pero no los de la suya propia.
La verdad es que el problema parece muy simple y el reto picó a Javi que me prometió una fórmula matemática que lo resolviera, pero a mi me interesaba que el PHP lo resolviera, y contra antes mejor, que el timming se me tiraba encima.
A nivel de concepto es muy fácil, y lo hemos hecho muchas veces en nuestra vida real, repartiendo monedas o lo que sea, pero ahora le toca el turno al PHP. Voy a poner un array de ejemplo para que nos entendamos:
PHP:
-
-
$pre_comb[] =
array('A',
'B',
'C',
'D');
-
$pre_comb[] =
array('1',
'2',
'3');
-
$pre_comb[] =
array('X',
'Y',
'Z');
Primero de todo debemos tener en cuenta que el número de de elementos en cada segunda dimensión no es fijo, igual que el número de arrays de la primera dimensión tampoco lo es.
El resultado que necesitamos es un array en el que cada elemento sea un array que contenga una combinación, de un elemento de cada segunda dimensión dada. Por ejemplo:
Mi teoría fué usar unos bucles anidados para recorrer el array inicial y atacar las segundas dimensiones por parejas, es decir, cogiendo un elemento de la 1ª segunda dimensión y toda la 2ª segunda dimensión, y realizar las combinaciones pertinentes, para luego coger ése resultado y la 3ª segunda dimensión y aplicar la combinación. De esta forma conseguiría ir combinando todas las dimensiones con todas.
Pero vamos por pasos. Tenemos el array $pre_comb lleno, así que lo primero que necesito saber es si el array dado tiene sólo un elemento o tiene varios, porque si sólo tiene una combinación, el resultado está claro, no?
Actualizado: Si sólo había un elemento no funcionaba bién, pues devolvía un array con las posibilidades mal posicionadas. Se ha corregido de forma que se devuelven las posibilidades de ese primer elemento como debería.
PHP:
-
$combinaciones =
array();
-
-
{
-
$combinaciones =
array();
-
foreach($pre_comb[0] as $tipo_hab)
-
{
-
$combinaciones[] =
array($tipo_hab);
-
}
-
}
-
else
-
{
-
...
-
}
Luego empieza el juego en sí. Primero escribimos los dos bucles anidados que van a recorrer el array elemento por elemento (supongo que no es necesario decir "dentro del else", no?
):
PHP:
-
for($i=0;$i
-
{
-
for($j=0;$j
-
{
-
...
-
}
-
}
Ahora estamos posicionados a nivel de elemento de cada segunda dimensión. Como la táctica que vamos a usar es coger el elemento actual con la segunda dimensión siguiente, debemos asegurarnos que la siguiente segunda dimensión existe. Si no existe no haremos nada, pues ya habremos acabado:
PHP:
-
for($i=0;$i
-
{
-
for($j=0;$j
-
{
-
if(($i+1)
-
{
-
...
-
}
-
}
-
}
Ahora estamos seguros que hay una dimensión siguiente. Lo que pasa es que tenemos que actuar de forma diferente si es la primera segunda dimensión o son las restantes. Porqué? La primera vez que intentamos realizarlo tendremos que usar el array de entrada $pre_comb y las restantes usaremos el array de salida, pues iremos combinando los resultados que vamos obteniendo con la siguiente dimensión.
PHP:
-
for($i=0;$i
-
{
-
for($j=0;$j
-
{
-
if(($i+1)
-
{
-
if($i==0)
-
{
-
...
-
}
-
else
-
{
-
...
-
}
-
}
-
}
-
}
Entonces, si es la primera iteración llamaremos a la función combinar (ver más adelante) que nos recoge el elemento actual y la dimensión siguiente y nos devuelve un array con las combinaciones hechas. Y con el resultado obtenido, lo "mergeamos" al array final de combinaciones.
PHP:
-
for($i=0;$i
-
{
-
for($j=0;$j
-
{
-
if(($i+1)
-
{
-
if($i==0)
-
{
-
$devuelto = self::combinar($pre_comb[$i][$j], $pre_comb[$i+1]);
-
$combinaciones =
array_merge($combinaciones,
$devuelto);
-
}
-
else
-
{
-
...
-
}
-
}
-
}
-
}
En el caso de que no estemos en la primera segunda dimensión tenemos que hacer un proceso un poco diferente. Pero primero tratemos de entenderlo. imaginemos que estamos en la segunda segunda dimensión. En el array resultado tenemos las combinaciones entre la primera y la segunda segunda dimensión:
Así que sale más a cuenta coger el array resultante como elemento a combinar junto con la segunda dimensión siguiente. Gracias a ello, vamos acumulando combinaciones hasta tener el array final con todas las combinaciones que necesitamos.
Éso, a nivel de código, nos obliga a realizar un tercer bucle anidado que recorra el array $combinaciones y nos vaya devolviendo los arrays de cada combinación. Estos arrays se van a combinar con la siguiente dimensión y al final "mergearemos" el resultado con el array de combinaciones.
Lo único que debemos tener en cuenta es que vamos a reconstruir el array de combinaciones finales cada vez que hagamos una pasada per el mismo, pues el array devuelto no lo podemos meter directamente. Por éso se construye un array auxiliar.
PHP:
-
for($i=0;$i
-
{
-
for($j=0;$j
-
{
-
if(($i+1)
-
{
-
if($i==0)
-
{
-
$devuelto = combinar($pre_comb[$i][$j], $pre_comb[$i+1]);
-
$combinaciones =
array_merge($combinaciones,
$devuelto);
-
}
-
else
-
{
-
$aux_combinaciones =
array();
-
foreach($combinaciones as $index => $combinacion)
-
{
-
$devuelto = combinar($combinacion, $pre_comb[$i+1]);
-
unset($combinaciones[$index]);
-
$aux_combinaciones =
array_merge($aux_combinaciones,
$devuelto);
-
}
-
$combinaciones = $aux_combinaciones;
-
unset($aux_combinaciones);
-
break;
-
}
-
}
-
}
-
}
Vale, pero ahora necesitamos la función combinar(). Es una función muy simple, pero como se realiza un trabajo muy parecido en los dos sitios que se llama, he preferido dejarlo en una función externa. Sinceramente, antes hacía más trabajo pero ahora podría integrarse con el proceso anterior...
PHP:
-
private function combinar($item, $data)
-
{
-
-
for($i=0;$i
-
{
-
-
{
-
-
}
-
else
-
{
-
$retorno[] =
array($item,
$data[$i]);
-
}
-
}
-
return $retorno;
-
}
Sencillamente, recoge un elemento y un array de elementos, y genera un array resultante con las combinaciones del elemento con los del array. Si el primer parámetro es un array en vez de un elemento sólo, hace un merge del elemento que tocaría.
Y así lo dejo solucionado.
Salud!
Tag:
Escribe un Comentario