Foros del Web » Programando para Internet » PHP »

[SOLUCIONADO] Combinaciones de números aleatorios que no se repitan

Estas en el tema de Combinaciones de números aleatorios que no se repitan en el foro de PHP en Foros del Web. Buenas tardes gente, tengo un problema que en un principio lo subestime como algo trivial pero al cual no le encuentro una solución aceptable. Un ...
  #1 (permalink)  
Antiguo 27/02/2015, 13:43
Avatar de NSD
NSD
Colaborador
 
Fecha de Ingreso: mayo-2012
Ubicación: Somewhere
Mensajes: 1.332
Antigüedad: 11 años, 11 meses
Puntos: 320
Combinaciones de números aleatorios que no se repitan

Buenas tardes gente, tengo un problema que en un principio lo subestime como algo trivial pero al cual no le encuentro una solución aceptable.

Un cliente quiere hacer una especie de bingo electrónico, para lo cual, cada jugador tiene vinculados 15 números del 0 al 99 que debe generar el sistema de forma automática.

Las 3 reglas básicas son:

- No puede haber mas de 2 números repetidos en el mismo cartón.
- No puede haber cartones repetidos.
- El orden no importa.

Los puntos 1 y 3 no son difíciles, pero el punto 2 no se como resolverlo.
Pensé en tener 2 tablas en la base de datos:

-- Clientes --
Codigo Integer Autoincrement PK
Nombre CHAR
...

-- Cartones --
Codigo Integer Autoincrement PK
Cliente Integer FK(Clientes.Codigo)
Numero_1 Integer
Numero_2 Integer
Numero_3 Integer
Numero_4 Integer
Numero_5 Integer
Numero_6 Integer
Numero_7 Integer
Numero_8 Integer
Numero_9 Integer
Numero_10 Integer
Numero_11 Integer
Numero_12 Integer
Numero_13 Integer
Numero_14 Integer
Numero_15 Integer

Solución 1:
El algoritmo en php generaría 15 números al azar, los ordenaría y los guardaría en la tabla cartones.
Para saber si no esta esa combinación, esta acción estaría dentro de un while.
El problema de esto es claro: Cuando tenga mas de la mitad de las combinaciones insertadas, agregar un nuevo cartón al azar tardaría un montón y consumiría un montón de recursos y podría tardar mucho tiempo.

Solución 2:
Pre-Poblar la tabla cartones con todas las combinaciones posibles (Un montooooon) y luego seleccionar un cartón al azar de esa tabla que no este asignado para cada cliente nuevo.
Problema: La tabla Cartones es enorme, aun con pocos clientes.

¿Se les ocurre alguna otra solución posible?
__________________
Maratón de desafíos PHP Junio - Agosto 2015 en FDW | Reglamento - Desafios

Última edición por NSD; 27/02/2015 a las 13:43 Razón: Colores
  #2 (permalink)  
Antiguo 27/02/2015, 14:19
Avatar de informacionsys  
Fecha de Ingreso: mayo-2011
Ubicación: Bogota D.C
Mensajes: 793
Antigüedad: 12 años, 11 meses
Puntos: 76
Respuesta: Combinaciones de números aleatorios que no se repitan

tengo una duda; el mismo numero si puede estar en n cartones ? es valido que tengas un carton con los numeros 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 y otro casi igual pero en vez del 14 el 16.
  #3 (permalink)  
Antiguo 27/02/2015, 15:01
Avatar de NSD
NSD
Colaborador
 
Fecha de Ingreso: mayo-2012
Ubicación: Somewhere
Mensajes: 1.332
Antigüedad: 11 años, 11 meses
Puntos: 320
Respuesta: Combinaciones de números aleatorios que no se repitan

Si es valido.

No puede haber cartones iguales y no se puede repetir el numero dentro del cartón, pero si puede aparecer el mismo numero en muchos cartones.
__________________
Maratón de desafíos PHP Junio - Agosto 2015 en FDW | Reglamento - Desafios
  #4 (permalink)  
Antiguo 27/02/2015, 15:27
Avatar de pateketrueke
Modernizr
 
Fecha de Ingreso: abril-2008
Ubicación: Mexihco-Tenochtitlan
Mensajes: 26.399
Antigüedad: 16 años
Puntos: 2534
Respuesta: Combinaciones de números aleatorios que no se repitan

El punto (2) yo lo veo extremadamente fácil, que claro, si lo quieres hacer con consultas a la base de datos entonces ya no es tan simple.

Es decir, ¿si antes de cargar todas las combinaciones en la base de datos revisas si hay duplicados no sería mejor?

Vamos, que si agregas de a uno por uno, y luego para validar repeticiones vas a la base de datos ahí ya tienes un problema de diseño: y hasta de sentido común.

Usando estructuras simples:
Código PHP:
Ver original
  1. $tabla = [
  2.   [1, 2, 3, 4],
  3.   [9, 6, 5, 8],
  4.   /* ... */
  5. ];

Ahora, ya teniendo una lista de listas, procedes a ordenar de manera natural cada sublista, de ahí puedes convertirlo a una cadena de texto.

Código PHP:
Ver original
  1. $lista = [
  2.   '1.2.3.4',
  3.   '5.6.8.9',
  4.   /* ... */
  5. ];

La solución se explica sola: como ya has reducido la lista de listas a una lista más simple entonces ahí aplicas array_unique() para descartar duplicados, etc.

En fin, hay muchas forma de hacer esto y la mejor forma es hacerlo en memoria.

No intentes resolver un problema estructural con base de datos.
__________________
Y U NO RTFM? щ(ºдºщ)

No atiendo por MP nada que no sea personal.
  #5 (permalink)  
Antiguo 27/02/2015, 16:49
Avatar de informacionsys  
Fecha de Ingreso: mayo-2011
Ubicación: Bogota D.C
Mensajes: 793
Antigüedad: 12 años, 11 meses
Puntos: 76
Respuesta: Combinaciones de números aleatorios que no se repitan

hola..

esta es una idea para que te guies...

Código PHP:
Ver original
  1. function getCartones()
  2. {
  3.     $fnNumbers = function()
  4.     {  
  5.         for($i2 = 1 ; $i2  <= 99 ; $i2++)
  6.         {
  7.             $n[] = $i2;
  8.         }
  9.         return $n;
  10.     };
  11.  
  12.     $numbers     = $fnNumbers();//arreglo con los numeros aleatorios posibles  
  13.     $totCartones = floor(count($numbers)/15);   //cantidad de cartones a crear
  14.  
  15.     function fnCarton($list,$numbers=array())
  16.     {  
  17.         if(!empty($numbers))
  18.         {
  19.             for($row = 1 ; $row <= 15 ; $row++)
  20.             {
  21.                 $nro  = array_rand($numbers,1);
  22.  
  23.                 if(in_array($nro,$numbers) && !empty($nro))
  24.                 {
  25.                     $list[] = $nro;
  26.                     $key    = array_search($nro,$numbers);
  27.                     unset($numbers[$key]);
  28.                 }
  29.  
  30.                 if(count($list) < 15 )
  31.                 {
  32.                    return fnCarton($list,$numbers);
  33.                 }
  34.                 else
  35.                 {
  36.                     return $list;
  37.                 }
  38.             };//fin for            
  39.         }
  40.     }; 
  41.  
  42.     for($ic = 0 ; $ic < $totCartones ; $ic++)
  43.     {
  44.         $list           = array();//arreglo con los 15 numeros posibles por carton
  45.         $dataCartones[] = fnCarton($list,$numbers);
  46.     }
  47.     return $dataCartones;  
  48. };
  49. $car =getCartones();
  50. var_dump($car);
  #6 (permalink)  
Antiguo 27/02/2015, 18:21
Avatar de NSD
NSD
Colaborador
 
Fecha de Ingreso: mayo-2012
Ubicación: Somewhere
Mensajes: 1.332
Antigüedad: 11 años, 11 meses
Puntos: 320
Respuesta: Combinaciones de números aleatorios que no se repitan

Gracias por sus respuestas, pero creo que no me exprese bien, tratare de explicarme un poco mejor.

El usuario da de alta cada tanto nuevos clientes, en el momento del alta, el sistema le asigna a ese cliente un cartón generado aleatoriamente que no debe estar asignado a ningún otro cliente previo.

En la base de datos, se guardan los clientes y los cartones que tiene asociado cada uno.

El problema es como generar un cartón QUE NO ESTE ASIGNADO A NINGÚN OTRO CLIENTE de los que ya están registrados.

Levantar a memoria todos los cartones registrados para luego armar uno nuevo, puede voltearme el server ya que van a ser muchísimos.

Se me ocurrió otra posibilidad que es muy viable y genérica, pero que no logro hacerla funcionar, la idea es esta:

Dado un conjunto de elementos cualquiera, se pueden obtener una lista de todas sus combinaciones de elementos de forma ordenada sin problemas, ahora, si pudiera obtener directamente una combinación de esa lista, sin calcular las demás, podría guardar el numero de carton del cliente como un entero comun, y luego al vuelo obtener la combinacion de numeros correspondiente.

El problema que tengo es que no logro que la funcion ande, ¿Me podrían dar una mano?
Código PHP:
Ver original
  1. <?php
  2.     function factorial($numero)
  3.     {
  4.         $fact = 1;
  5.         while($numero >= 1 && ($fact *= $numero--));
  6.         return $fact;
  7.     }
  8.    
  9.     function combinatoria($m, $n)
  10.     {
  11.         return (factorial($m) / (factorial($n) * factorial($m - $n)));
  12.     }
  13.    
  14.     // Esta funcion obtiene la combinacion de una posicion.
  15.     // Items es un array con los elementos
  16.     // $n el numero de elementos por carton
  17.     // $pos es la posicion que me interesa
  18.     function cartones($items, $n, $pos)
  19.     {
  20.         // Aca se guardan los items del carton
  21.         $combinacion = [];        
  22.         // Cuantos items hay
  23.         $m = count($items);
  24.                    
  25.         // Auxiliares
  26.         $base = 0;
  27.         $idx = -1;
  28.         // Mientras me falte algun elemento en la combinacion
  29.         while($n)
  30.         {
  31.             //echo "<hr>";
  32.             // Numero de combinaciones posibles
  33.             $posibles = combinatoria($m, $n);
  34.             // Para cada variacion de 1 elemento posible
  35.             for($nro=0; $nro<$m-$n+1; $nro++)
  36.             {
  37.                 // Desde donde arranca ese elemento
  38.                 $desde = $base + $posibles - combinatoria($m - $nro, $n);
  39.                 // Hasta donde termina ese elemento
  40.                 $hasta = $base + $posibles - combinatoria($m - $nro - 1, $n) - 1;
  41.  
  42.                 $idx++;
  43.                 //echo "Rango: $desde - $hasta | $pos<br>";
  44.                 // Si la posicion que busco esta dentro de ese rango
  45.                 // NOTA: La segunda parte, luego del or, esta puesta como parche, pero no le encuentro logica
  46.                 if(($pos >= $desde && $pos <= $hasta) || ($hasta < $desde && $pos == $desde))
  47.                 {  
  48.                     // Guardar el origen del grupo como punto de partida
  49.                     $base = $desde;
  50.                     //echo "Ok!<br>";
  51.                     // Agregar al carton el item correspondiente.
  52.                     $combinacion[] = $items[$idx];
  53.                     break;
  54.                 }          
  55.             }            
  56.             $m--;
  57.             $n--;          
  58.         }
  59.                
  60.         return $combinacion;
  61.     }
  62.     //var_dump(cartones(range(0, 9), 4, 34));
  63.    
  64.     // Tests
  65.     // Genero todas las combinaciones posibles de 9 elementos tomados de a 4 y los comparo con los de la funcion para ver cuales fallan
  66.     echo "<hr>";
  67.     $id = 0;
  68.     $fails = 0;
  69.     for($a=0; $a<=6; $a++)
  70.     {            
  71.         for($e=$a+1; $e<=9; $e++)
  72.         {
  73.             for($i=$e+1; $i<=9; $i++)
  74.             {
  75.                 for($o=$i+1; $o<=9; $o++)
  76.                 {
  77.                     $carton = cartones(range(0, 9), 4, $id);
  78.                     if($carton[0] != $a || $carton[1] != $e || $carton[2] != $i || $carton[3] != $o)
  79.                     {                        
  80.                         echo "$id -> [$a, $e, $i, $o] -> [$carton[0], $carton[1], $carton[2], $carton[3]]<br>";
  81.                         $fails++;
  82.                     }
  83.                     else
  84.                     {
  85.                         //echo "$id -> Ok!<br>";                        
  86.                     }
  87.                     $id++;
  88.                 }
  89.             }
  90.         }
  91.     }
  92.    
  93.     echo "Terminado! $fails Fallas";


Para que todo ande los test no deberia fallar ninguno, pero actualmente fallan 140 de 210.
No encuentro como hacerlo andar
__________________
Maratón de desafíos PHP Junio - Agosto 2015 en FDW | Reglamento - Desafios
  #7 (permalink)  
Antiguo 28/02/2015, 11:25
Avatar de hhs
hhs
Colaborador
 
Fecha de Ingreso: junio-2013
Ubicación: México
Mensajes: 2.995
Antigüedad: 10 años, 9 meses
Puntos: 379
Respuesta: Combinaciones de números aleatorios que no se repitan

NSD no he visto a detalle el algoritmo pero estas usando las reglas del bingo tradicional ?
__________________
Saludos
About me
Laraveles
A class should have only one reason to change.
  #8 (permalink)  
Antiguo 28/02/2015, 11:56
 
Fecha de Ingreso: febrero-2015
Mensajes: 61
Antigüedad: 9 años, 1 mes
Puntos: 15
Respuesta: Combinaciones de números aleatorios que no se repitan

tal vez...

agregar 2 campos a "cartones"

1 boolean yausado
1 string numerosdelcarton

:: ni siquiera necesitarias tener un campo por cada numero... haciendo un EXPLODE("," , numerosdelcarton ) tendrias un array con todos los nums

el boolean te permite filtrar los ya usados, para obtener un nuevo carton

numerosdelcarton: guardas los numeros ORDENADOS separados con coma (o lo que sea) como un string.... esto te permite buscar el carton mas facil ( en vez de estar creando arrays/ conjuntos e iterando por cada uno de los 15 numeros )


------------
<?php
Código PHP:
Ver original
  1. function dameListaNum($cuantos=15, $min=0, $max=99){
  2.     if ($cuantos > ($max-$min) ) {die( "error, parametros incorrectos"); }
  3.     // si $cuantos > numero de permutaciones que se pueden hacer... die ("error, $cuantos > a lo permitido")
  4.    
  5.    
  6.     $x=0;
  7.     $valores = array();
  8.     while ($x<$cuantos) {
  9.       $num_aleatorio = rand($min,$max);
  10.       if (!in_array($num_aleatorio,$valores)) {
  11.         array_push($valores,$num_aleatorio);
  12.         $x++;
  13.       }
  14.     }
  15.  
  16.     // ordena
  17.     asort($valores);
  18.     return implode(",", $valores);
  19. }
  20.  
  21. // genero 1000 cartones de prueba
  22. $cartones = array();
  23.  
  24. for($i=1;$i<=1000;$i++){
  25.     $uncarton = dameListaNum();
  26.     if (!in_array($uncarton, $cartones)){
  27.         array_push($cartones,$uncarton);
  28.     }
  29. }
  30.  
  31.  
  32. // prueba pa ver si hay duplicados
  33. echo count($cartones);
  34. echo "\n";
  35. // elimino duplicados
  36. $resultado = array_unique($cartones);
  37. echo count($resultado);
  38. // deberia dar 1000 / 1000

?>

Última edición por MMan; 28/02/2015 a las 13:17 Razón: agregado codigo de prueba
  #9 (permalink)  
Antiguo 01/03/2015, 14:20
Avatar de NSD
NSD
Colaborador
 
Fecha de Ingreso: mayo-2012
Ubicación: Somewhere
Mensajes: 1.332
Antigüedad: 11 años, 11 meses
Puntos: 320
Respuesta: Combinaciones de números aleatorios que no se repitan

Al fin logre hacerlo andar tras mucha horas sin dormir y varias tasas de cafe!!

Gracias por la ayuda, tratare de explicar como lo resolví para que si alguien mas tiene el mismo problema le sirva de ayuda.

Tanto los algoritmos de @informacionsys como el de @MMan y el sugerido por @pateketrueke y también la solución 1 propuesta en el primer mensaje tienen el mismo gran problema:
Para generar un cartón nuevo, necesitan conocer a los que ya están generados previamente, y, como previamente puede haber hasta 253338471349989000 combinaciones, claro esta que no hay server que soporte levantar todas esas combinaciones a memoria en simultaneo.

La solución que diseñe es la siguiente:

1) Supongamos (hipotéticamente, ya dije que no es posible) que pudiéramos crear un array que contenga las 253338471349989000 combinaciones posibles, a cada una, le correspondería un indice numérico correlativo que la identificaría dentro del array.

2) Supongamos también, que a esa lista de combinaciones la pudiera "desordenar" o "ordenar aleatoriamente" en base a una semilla (seed) de forma tal, que cada vez que la genere, este siempre en el mismo orden.

3) Cada cliente tiene un ID autoincrementado único en la base de datos, que se podría corresponder con los indices de la lista de combinaciones ordenada aleatoriamente.

4) Afirmemos que se puede obtener la combinación de una posición de la lista, sin generar a todas las demás combinaciones, es decir, obtener una combinación sin conocer a la lista completa.

5) El resultado, es que cada alta de un nuevo cliente, lleva asociada una combinación de números aleatoria (que seria el cartón del bingo que le corresponde) única, sin mas requisitos que tener un campo autoincremental como clave primaria.

6) Para desordenar la lista, el algoritmo de fisher-yates (comúnmente usado para la reproducción de listas de canciones aleatoriamente y sin repetición) con semilla, es la solución genial.

7) El código de la solución comentado:
Código PHP:
Ver original
  1. <?php
  2.     class Factorial
  3.     {
  4.         /*/
  5.          * Factoriales previamente calculados, como siempre se usan los mismos
  6.          * y ademas son pocos, no hace falta recalcularlos todo el tiempo.
  7.         /*/
  8.         private static $cache = [];
  9.        
  10.         /*/
  11.          * Calcular el factorial de un numero si no esta cacheado.
  12.         /*/
  13.         public static function calc($nro) {
  14.            
  15.             if(!array_key_exists($nro, self::$cache))
  16.             {
  17.                 self::$cache[$nro] = 1;
  18.                 $numero = $nro;
  19.                 while($numero >= 1 && (self::$cache[$nro] *= $numero--));                
  20.             }
  21.            
  22.             return self::$cache[$nro];
  23.         }
  24.     }
  25.  
  26.     /*/
  27.      * @param $items array Los elementos que pueden componer un carton.
  28.      * @param $cantidad int La cantidad de elementos por carton.
  29.      * @param $posicion_buscada int La posicion o numero de carton buscada.
  30.     /*/
  31.     function carton($items, $cantidad, $posicion_buscada)
  32.     {        
  33.         // Una semilla constante, permite desordenar siempre de la misma manera.
  34.         mt_srand(123456789);
  35.        
  36.         // Recorrer los todos elementos.
  37.         for($posicion=count($items)-1; $posicion; $posicion--)
  38.         {
  39.             // Realizar los intercambios.
  40.             $posicion_nueva = mt_rand(0, $posicion);
  41.             $backup = $items[$posicion];
  42.             $items[$posicion] = $items[$posicion_nueva];
  43.             $items[$posicion_nueva] = $backup;
  44.         }
  45.         unset($posicion, $posicion_nueva, $backup);
  46.        
  47.         // Variables de uso general.
  48.         $carton = [];
  49.         $items_count = count($items);
  50.         $posicion_actual = 0;
  51.        
  52.         // Para cada elemento que tengo que agregar al carton.
  53.         for($elemento=0; $elemento<$cantidad; $elemento++)
  54.         {
  55.             // Variables para uso de cada elemento en particular.
  56.             $items_count_combinacion = $items_count;
  57.             $items_count_avaible = $items_count - $cantidad + $elemento;            
  58.             $cantidad_elementos = $cantidad - $elemento;            
  59.             $posicion_tope = $posicion_actual;
  60.            
  61.             // Recorrer cada item disponible.
  62.             for($item_numero = 0; $item_numero<=$items_count_avaible; $item_numero++)
  63.             {
  64.                 $mc = $items_count_combinacion - $item_numero - 1;
  65.                 $nc = $cantidad_elementos - 1;  
  66.                 // Hasta que posicion le corresponde este elemento.
  67.                 $posicion_tope += (Factorial::calc($mc) / (Factorial::calc($nc) * Factorial::calc($mc - $nc)));
  68.                 $item = array_shift($items);
  69.                 $items_count--;
  70.                
  71.                 // Si la posicion buscada es menor al tope que le corresponde al elemento.
  72.                 if($posicion_buscada <= $posicion_tope)
  73.                 {
  74.                     // Asignar el elemento al carton.
  75.                     $carton[] = $item;
  76.                     break;
  77.                 }
  78.                 else // Seguir buscando desde el tope en adelante.
  79.                     $posicion_actual = $posicion_tope;
  80.             }
  81.         }
  82.        
  83.         // El carton vuelve ordenado.
  84.         sort($carton);
  85.        
  86.         return $carton;
  87.     }
  88.    
  89.     // Test: Generar los primeros 100 cartones.
  90.     $cartones = [];
  91.     $items = range(0, 99);    
  92.     for($idx=1; $idx<=100; $idx++)
  93.         $cartones[] = implode(", ", carton($items, 15, $idx));
  94.     var_dump($cartones);
__________________
Maratón de desafíos PHP Junio - Agosto 2015 en FDW | Reglamento - Desafios
  #10 (permalink)  
Antiguo 01/03/2015, 14:48
Avatar de NSD
NSD
Colaborador
 
Fecha de Ingreso: mayo-2012
Ubicación: Somewhere
Mensajes: 1.332
Antigüedad: 11 años, 11 meses
Puntos: 320
Respuesta: Combinaciones de números aleatorios que no se repitan

Una versión mejorada de ese algoritmo se puede lograr cambiando esta linea:
Código PHP:
Ver original
  1. mt_srand(123456789);
por esta:
Código PHP:
Ver original
  1. mt_srand($posicion_buscada);
y eliminando el sort del final.

Sera necesario ademas, guardar la combinación generada en la base de datos y el proceso de alta de clientes debera constar de los siguientes pasos:

1) Se da de alta un cliente con ID autoincremental.
2) Se calcula el cartón de ese cliente y se verifica que no este asignado a otro cliente previo.
3) Si no esta asignado, se guardan los datos del cartón asignándoselo al cliente.
4) Si ya esta guardado, se borra de la base de datos el cliente y se da nuevamente de alta (para que tenga un nuevo id) y se vuelve al paso 2.
5) En las lecturas se toman los datos del cartón directamente desde la base de datos.

Ventajas de esta versión mejorada:
- Mayor aleatoriedad, cartones contiguos son mas diferentes entre si.
- Menor consumo y mas velocidad, solo se calcula el carton al momento del alta.

- Contras de la versión mejorada:
- Requiere mas espacio en la base de datos.
- Posibilidad infima pero existente de que queden huecos en los id de los clientes.

Es posible, aunque extremadamente improbable, que la combinación de una semilla (que es la posición buscada) y la combinación a obtener de esa posición, coincidan con otra posición y otra combinación, por tal motivo, se debe preveer este caso con el paso 4.
Es algo muy poco probable pero que quizás alguna vez ocurra.

La versión del mensaje anterior no tiene fallas posibles y es 100% confiable, esta versión mejorada en cambio, tiene un caso particular que hay que considerar, pero las ventajas de usarla lo compensan con creces.
__________________
Maratón de desafíos PHP Junio - Agosto 2015 en FDW | Reglamento - Desafios
  #11 (permalink)  
Antiguo 22/02/2016, 08:29
Avatar de NSD
NSD
Colaborador
 
Fecha de Ingreso: mayo-2012
Ubicación: Somewhere
Mensajes: 1.332
Antigüedad: 11 años, 11 meses
Puntos: 320
Respuesta: Combinaciones de números aleatorios que no se repitan

Revivo el tema porque me llego un mensaje privado con la siguiente consulta:
Cita:
¿Cual es el objetivo de la posición tope? De acuerdo a si la posición tope es menor o no a la posición del cartón dentro del array, vos buscas un nuevo numero para componer el cartón.
La parte del código a la que me refiero es la siguiente:
Código PHP:
Ver original
  1. $mc = $items_count_combinacion - $item_numero - 1;
  2. $nc = $cantidad_elementos - 1;  
  3. // Hasta que posición le corresponde este elemento.
  4. $posicion_tope += (Factorial::calc($mc) / (Factorial::calc($nc) * Factorial::calc($mc - $nc)));
  5.  
  6. if($posicion_buscada <= $posicion_tope)
  7. {
  8. ...
El algoritmo funciona basado en combinaciones, consideremos un caso base para entenderlo mejor:
Supongamos que tienes los numero comprendidos entre el 1 y el 5 y quieres generar cartones con 3 numeros, solo podrias generar 10 cartones, puesto que la formula de combinacion es:
((m!) / (n! * (m-n)!)) = x
por lo tanto:
((5!) / (3! * (5-3)!)) = 10
Ahora bien, si quisieras generar todas las combinaciones posibles de forma "natural" tomarias el primer elemento y lo combinarias con todos los posibles:
[01]: 1, 2, 3
[02]: 1, 2, 4
[03]: 1, 2, 5
[04]: 1, 3, 4
[05]: 1, 3, 5
[06]: 1, 4, 5

Luego harias lo mismo con el segundo:
[07]: 2, 3, 4
[08]: 2, 3, 5
[09]: 2, 4, 5

Y con el tercero:
[10]: 3, 4, 5
Con el cuarto y quinto ya no hay ninguna combinacion posible para hacer sin repetir las anteriores.

Si en la funcion "carton" comentas esta parte del codigo:

Código PHP:
Ver original
  1. // Recorrer los todos elementos.
  2. //for($posicion=count($items)-1; $posicion; $posicion--)
  3. //{
  4. //    // Realizar los intercambios.
  5. //    $posicion_nueva = mt_rand(0, $posicion);
  6. //    $backup = $items[$posicion];
  7. //    $items[$posicion] = $items[$posicion_nueva];
  8. //    $items[$posicion_nueva] = $backup;
  9. //}
  10. //unset($posicion, $posicion_nueva, $backup);

Y la llamas asi:
Código PHP:
Ver original
  1. $items = range(1, 5);
  2. for($nro=1; $nro<=10; $nro++) {
  3.   echo "[$nro]: ".implode(", ", carton($items, 3, $nro))."<br>";
  4. }

Obtendras justamente ese mismo listado.

Observa que en cada posicion de numero dentro del carton (primera, segunda y tercera) en el orden listado son siempre crecientes hacia abajo y hacia la derecha, eso es muy importante.

Ahora bien, supongamos que quieres la combinacion 8:

Código PHP:
Ver original
  1. echo "[8]: ".implode(", ", carton($items, 3, 8))."<br>";

Que es:

[08]: 2, 3, 5

Para saber cuales son los numeros que la componen y teniendo en cuenta el punto anterior, debemos encontrar para cada numero posible (1, 2, 3, 4, 5) cual es la posicion tope en la que aparece, puedes agregar un echo antes en este lugar para observar como funciona:

Código PHP:
Ver original
  1. // Hasta que posicion le corresponde este elemento.
  2. $posicion_tope += (Factorial::calc($mc) / (Factorial::calc($nc) * Factorial::calc($mc - $nc)));
  3. $item = array_shift($items);
  4. $items_count--;
  5.  
  6. echo "Item: $item; Tope: $posicion_tope; Buscada: $posicion_buscada;<br>";
  7.  
  8. // Si la posicion buscada es menor al tope que le corresponde al elemento.
  9. if($posicion_buscada <= $posicion_tope)
  10. {

Para entender mejor que es lo que esta haciendo voy a comentar cada linea de la salida:
Item: 1; Tope: 6; Buscada: 1; // Hasta la combinacion 6 tienen un 1 como primer elemento.
Item: 2; Tope: 3; Buscada: 1; // Hasta la combinacion 3 tienen un 2 como segundo elemento.
Item: 3; Tope: 1; Buscada: 1; // Hasta la combinacion 1 tienen un 3 como tercer elemento.
[1]: 1, 2, 3 // La combinacion 1 se forma con los items donde Buscada <= Tope.
Item: 1; Tope: 6; Buscada: 2; // Hasta la combinacion 6 tienen un 1 como primer elemento.
Item: 2; Tope: 3; Buscada: 2; // Hasta la combinacion 3 tienen un 2 como segundo elemento.
Item: 3; Tope: 1; Buscada: 2; // Hasta la combinacion 1 tienen un 3 como tercer elemento.
Item: 4; Tope: 2; Buscada: 2; // Hasta la combinacion 2 tienen un 4 como tercer elemento.
[2]: 1, 2, 4 // La combinacion 2 se forma con los items donde Buscada <= Tope.
Item: 1; Tope: 6; Buscada: 3; // Hasta la combinacion 6 tienen un 1 como primer elemento.
Item: 2; Tope: 3; Buscada: 3; // Hasta la combinacion 3 tienen un 2 como segundo elemento.
Item: 3; Tope: 1; Buscada: 3; // Hasta la combinacion 1 tienen un 3 como tercer elemento.
Item: 4; Tope: 2; Buscada: 3; // Hasta la combinacion 2 tienen un 4 como tercer elemento.
Item: 5; Tope: 3; Buscada: 3; // Hasta la combinacion 3 tienen un 5 como tercer elemento.
[3]: 1, 2, 5 // La combinacion 3 se forma con los items donde Buscada <= Tope.
// ...

Y asi sigue de la misma manera con los demas.
__________________
Maratón de desafíos PHP Junio - Agosto 2015 en FDW | Reglamento - Desafios

Etiquetas: combinaciones, tabla
Atención: Estás leyendo un tema que no tiene actividad desde hace más de 6 MESES, te recomendamos abrir un Nuevo tema en lugar de responder al actual.
Respuesta




La zona horaria es GMT -6. Ahora son las 05:50.