PHP: Combinaciones entre elementos de un array bidimensional
27 de Marzo de 2007 en Programación, PHP
Lecturas: 14,188

PHPPor 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:
  1. $pre_comb = array();
  2. $pre_comb[] = array('A', 'B', 'C', 'D');
  3. $pre_comb[] = array('1', '2', '3');
  4. $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:

PHP:
  1. $combinaciones = array(array('A','1','X'), array('A','1','Y'), ... );

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:
  1. $combinaciones = array();
  2. if(count($pre_comb)==1)
  3. {
  4. $combinaciones = array();
  5. foreach($pre_comb[0] as $tipo_hab)
  6. {
  7. $combinaciones[] = array($tipo_hab);
  8. }
  9. }
  10. else
  11. {
  12. ...
  13. }

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:
  1. for($i=0;$i
  2. {
  3. for($j=0;$j
  4. {
  5. ...
  6. }
  7. }

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:
  1. for($i=0;$i
  2. {
  3. for($j=0;$j
  4. {
  5. if(($i+1)
  6. {
  7. ...
  8. }
  9. }
  10. }

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:
  1. for($i=0;$i
  2. {
  3. for($j=0;$j
  4. {
  5. if(($i+1)
  6. {
  7. if($i==0)
  8. {
  9. ...
  10. }
  11. else
  12. {
  13. ...
  14. }
  15. }
  16. }
  17. }

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:
  1. for($i=0;$i
  2. {
  3. for($j=0;$j
  4. {
  5. if(($i+1)
  6. {
  7. if($i==0)
  8. {
  9. $devuelto = self::combinar($pre_comb[$i][$j], $pre_comb[$i+1]);
  10. $combinaciones = array_merge($combinaciones,$devuelto);
  11. }
  12. else
  13. {
  14. ...
  15. }
  16. }
  17. }
  18. }

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:

PHP:
  1. $combinaciones = array(array('A','1'), array('A','2'), array('A','3'),
  2. array('B','1'), array('B','2'), array('B','3'),
  3. array('C','1'), array('C','2'), array('C','3'),
  4. array('D','1'), array('D','2'), array('D','3')
  5. );

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:
  1. for($i=0;$i
  2. {
  3. for($j=0;$j
  4. {
  5. if(($i+1)
  6. {
  7. if($i==0)
  8. {
  9. $devuelto = combinar($pre_comb[$i][$j], $pre_comb[$i+1]);
  10. $combinaciones = array_merge($combinaciones,$devuelto);
  11. }
  12. else
  13. {
  14. $aux_combinaciones = array();
  15. foreach($combinaciones as $index => $combinacion)
  16. {
  17. $devuelto = combinar($combinacion, $pre_comb[$i+1]);
  18. unset($combinaciones[$index]);
  19. $aux_combinaciones = array_merge($aux_combinaciones,$devuelto);
  20. }
  21. $combinaciones = $aux_combinaciones;
  22. unset($aux_combinaciones);
  23. break;
  24. }
  25. }
  26. }
  27. }

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:
  1. private function combinar($item, $data)
  2. {
  3. $retorno = array();
  4. for($i=0;$i
  5. {
  6. if(is_array($item))
  7. {
  8. $retorno[] = array_merge($item, array($data[$i]));
  9. }
  10. else
  11. {
  12. $retorno[] = array($item, $data[$i]);
  13. }
  14. }
  15. return $retorno;
  16. }

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:
 Enviar a Fresqui

Leer los Comentarios

[ # 895 ] Comment desde Anuack Luna [04 de Mayo de 2007, 05:06]

PHP- DISCULPA POR LAS MAYUSCULAS, EL TECLADO ESTA DAÑADO.

HOLA FOREROS

TENGO UN INCONVENIENTE QUE ME ESTA DANDO VUELTAS SIN SOLUCIÓN

RESULTA QUE TENGO 2 TABLAS.

EN UNA TABLA REGISTRA LOS MENSAJES ENTRE USUARIOS

LA OTRA TABLA REGISTRA LOS USUARIOS BLOQUEADOS.

EL FILTRO QUE ME GUSTARIA QUE ME AYUDARAN ES IGUAL A UN FILTRO DE CORREO SPAM.

QUE EL VALLA Y VERIFIQUE SI EL USUARIO ESTA BLOQUEADO, SI ES ASI NO LO
MUESTRE.

PERO SI NO ESTA BLOQUEADO MUESTRE EL REGISTRO.

ALGUNA SUGERENCIA

LES DEJO LA DATABASE.

LES AGRADEZCO LA AYUDA.

CREATE TABLE `mensaje_bloqueado` (
`id` int(11) NOT NULL auto_increment,
`fecha` date default NULL,
`id_usuario` int(11) default NULL,
`id_bloqueado` int(11) default NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

—————————————————————

CREATE TABLE `mensajes_usuarios` (
`id` int(11) NOT NULL auto_increment,
`fecha` date default NULL,
`remite` varchar(255) default NULL,
`id_remite` varchar(250) default NULL,
`para` varchar(255) default NULL,
`id_para` varchar(250) default NULL,
`mensaje_corto` varchar(255) default NULL,
`mensaje` text,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

[ # 896 ] Comment desde Xavi [04 de Mayo de 2007, 07:49]

Buenas Anuack.

Primero, es divertido ver como los programadores nos montamos las tablas cada uno a su manera :)

Bueno, al tajo. Tocho a la vista ;)
Imagino que tienes una tabla usuario con los datos del propio usuario, y la enlacas a la tabla mensaje_bloqueado por id_usuario. Si es así te falta una foreign key sobre el campo id_usuario que apunte a la tabla usuario. Segundo, te faltaría un campo que ligara con el id de la tabla mensaje_usuario, y luego simplemente una query que controle id_bloqueado, que por lo que veo tiene pinta de enlazar con otra tabla bloqueado. De todas formas, lo encuentro un sistema complicado. Por un lado, tendrás que marcar mensaje a mensaje los que quieras bloquear. Podrías tener un campo bloqueado (booleano) en la tabla usuario, y si está a true no muestras ningún mensaje que pertenezca a ese usuario. Y si igualmente quieres bloquear mensaje por mensaje, en vez del id_bloqueado en la tabla mensaje bloqueado lo pondría en mensajes_usuarios y como booleano, y lo controlas también en la misma query. Porque… de verdad necesitas saber la fecha de cuándo lo bloqueaste? Bueno, vamos a usar tu tabla intermedia :S

Entonces la query sería algo como (obviamente, la query va de tu lado ;) ):

select *
from mensajes_usuarios m,
mensajes_bloqueados mb,
usuarios u
where
m.id = mb.id_mensaje and
mb.id_usuario = u.id and
u.bloqueado = false and
m.bloqueado = false

Ésto te dará todos los campos de todos los mensajes siempre que el usuario no esté bloqueado ni el mensaje bloqueado.

Como ves, es simplemente la selección de datos por SQL y en el código PHP no tienes que meter lógica, simplemente vomitar los datos de forma bonita ;)

Si necesitas más ayuda avisas.

Y búscate un teclado con minúsculas! ;)

[ # 32420 ] Comment desde derkeNuke [16 de Agosto de 2008, 04:55]

En el post no se ve el código! El problema es por los > y los <, es un problema grave para el post.

Saludos.

Escribe un Comentario





Estadísticas