Foros del Web » Programando para Internet » PHP »

Número aleatorio pero evitando uno en particular

Estas en el tema de Número aleatorio pero evitando uno en particular en el foro de PHP en Foros del Web. Hola vecinos!!! Sigo con mis ejercicios para aprender php y me encuentro con otra duda. Estoy realizando algo parecido a una apuesta de lotería para ...
  #1 (permalink)  
Antiguo 23/11/2013, 16:49
Avatar de rodrypaladin
Moderador
 
Fecha de Ingreso: abril-2010
Ubicación: Madrid
Mensajes: 2.127
Antigüedad: 14 años, 7 meses
Puntos: 468
Número aleatorio pero evitando uno en particular

Hola vecinos!!!

Sigo con mis ejercicios para aprender php y me encuentro con otra duda.

Estoy realizando algo parecido a una apuesta de lotería para ir practicando.

Los números aleatorios los genero muy bien con:
Código PHP:
$n1 rand(1,49); 
Ahora el 2º número sería también un número aleatorio entre el 1 y el 49 pero no tendrá que ser igual que el primer número, ya que este número ya ha salido del bombo. Y así hasta los 5 números totales

Lo he construido de la siguiente forma:
Código PHP:
$n1 rand(1,49);
$n2 rand(1,49);
if ( 
$n2=$n1) { $n2 rand(1,49); } 
Pero la cuestión es que no me convence, ya que al ser ALEATORIO puede darse el caso de que salga 10, 20 o 100 veces el mismo número y vuelva a generar otro.

Lo que ando buscando es una forma de decir que me genere un número aleatorio entre el 1 y el 49 pero evitando X número. De esa manera solo estaría realizando 1 operación en vez de 10, 20 o 100 veces si se diera el caso aleatorio como he dicho antes.

Espero que me podáis ayudar. Un saludo y gracias.
__________________
No te olvides de dar +1 a quien te echa un cable ;)
  #2 (permalink)  
Antiguo 23/11/2013, 17:00
Avatar de Triby
Mod on free time
 
Fecha de Ingreso: agosto-2008
Ubicación: $MX->Gto['León'];
Mensajes: 10.106
Antigüedad: 16 años, 3 meses
Puntos: 2237
Respuesta: Número aleatorio pero evitando uno en particular

No creo que haya un rango "con excepciones" y yo lo haría así:

Código PHP:
Ver original
  1. $numeros = array();
  2.  
  3. // Un ciclo para generar 5 números
  4. for($i = 0; $i < 5; $i++) {
  5.       // Un bucle para verificar que el número no se repita
  6.       while(true) {
  7.            $n = rand(1, 49);
  8.            if(!in_array($n, $numeros)) {
  9.                   // El número no ha sido seleccionado, lo agregamos
  10.                   $numeros[] = $n;
  11.                   // Salimos del while
  12.                   break;
  13.            }
  14.       }
  15. }
  16.  
  17. // Para ver el contenido del array
  18. var_dump($numeros);

Lógicamente, si necesitas mostrarlos, deberás recurrir a un ciclo for, foreach, o bien, directamente con $numero[indice]
__________________
- León, Guanajuato
- GV-Foto
  #3 (permalink)  
Antiguo 23/11/2013, 17:04
Avatar de rodrypaladin
Moderador
 
Fecha de Ingreso: abril-2010
Ubicación: Madrid
Mensajes: 2.127
Antigüedad: 14 años, 7 meses
Puntos: 468
Respuesta: Número aleatorio pero evitando uno en particular

Si, tendría que salir tu jugada, osea los números que tienes. y luego con la mismo saldría la ganadora.
__________________
No te olvides de dar +1 a quien te echa un cable ;)
  #4 (permalink)  
Antiguo 23/11/2013, 17:05
Avatar de dashtrash
Colaborador
 
Fecha de Ingreso: abril-2007
Ubicación: Ni en Sevilla,ni en Sanlúcar..qué más da..
Mensajes: 927
Antigüedad: 17 años, 7 meses
Puntos: 270
Respuesta: Número aleatorio pero evitando uno en particular

Código PHP:
Ver original
  1. $a=range(1,49);
  2. shuffle($a);
  3. echo implode(",",$a);
  #5 (permalink)  
Antiguo 23/11/2013, 17:07
Avatar de rodrypaladin
Moderador
 
Fecha de Ingreso: abril-2010
Ubicación: Madrid
Mensajes: 2.127
Antigüedad: 14 años, 7 meses
Puntos: 468
Respuesta: Número aleatorio pero evitando uno en particular

Cita:
Iniciado por dashtrash Ver Mensaje
Código PHP:
Ver original
  1. $a=range(1,49);
  2. shuffle($a);
  3. echo implode(",",$a);
Podrías decirme que hace cada linea por favor ?? Soy neuvo en php y nunca había visto esas lineas!!
__________________
No te olvides de dar +1 a quien te echa un cable ;)
  #6 (permalink)  
Antiguo 23/11/2013, 17:12
Avatar de dashtrash
Colaborador
 
Fecha de Ingreso: abril-2007
Ubicación: Ni en Sevilla,ni en Sanlúcar..qué más da..
Mensajes: 927
Antigüedad: 17 años, 7 meses
Puntos: 270
Respuesta: Número aleatorio pero evitando uno en particular

La primera linea crea un array con números del 1 al 49.
La segunda, los mezcla aleatoriamente.
La tercera, los muestra.
  #7 (permalink)  
Antiguo 23/11/2013, 17:24
Avatar de dashtrash
Colaborador
 
Fecha de Ingreso: abril-2007
Ubicación: Ni en Sevilla,ni en Sanlúcar..qué más da..
Mensajes: 927
Antigüedad: 17 años, 7 meses
Puntos: 270
Respuesta: Número aleatorio pero evitando uno en particular

Versión "extendida" para mostrar el funcionamiento:

Código PHP:
Ver original
  1. /* Creacion de un array con los numeros a mostrar */
  2. $numeros=array();
  3. for($k=1;$k<=49;$k++)
  4.    $numeros[]=$k;
  5.  
  6. // Se comienza el bucle. usando como contador una variable que indica cuántos elementos
  7. // del array quedan por mezclar.
  8. for($n=49;$n>0;$n--)
  9. {
  10.      // Se obtiene un índice al azar del array, entre 0 y el número de elementos que quedan por mezclar.
  11.      $r=rand(0,$n);
  12.     // Se obtiene ese elemento del array, y se elimina del mismo.
  13.      $p=array_splice($numeros,$r,1);
  14.     // Lo añadimos al final del array.
  15.     // Ahora este elemento está en la posición n, por lo que no volverá a ser mezclado.
  16.      $numeros[]=$p[0];
  17. }
  18. // Se muestra la salida
  19. echo implode(",",$numeros);
  #8 (permalink)  
Antiguo 23/11/2013, 17:55
Avatar de rodrypaladin
Moderador
 
Fecha de Ingreso: abril-2010
Ubicación: Madrid
Mensajes: 2.127
Antigüedad: 14 años, 7 meses
Puntos: 468
Respuesta: Número aleatorio pero evitando uno en particular

Creo que esa estructura sobrepasa de momento mis posibilidades jeje aunque no es complicado solo llevo 3días con php.

Volviendo a la forma que lo estaba intentando hacer..

La que expuse al abrir el hilo funcionaba pero cuando añado otro if

Código PHP:
$n1 rand(1,49);
$n2 rand(1,49);
if ( 
$n2=$n1) { $n2 rand(1,49); }
$n3 rand(1,49);
if (
$n3=$n2)||($n3=$n1) { $n3 rand(1,49); } 
Hasta el 1º if si que funciona, pero cuando voy al segundo no funciona ni con || ni con OR ni con XOR ¿ porque?
__________________
No te olvides de dar +1 a quien te echa un cable ;)
  #9 (permalink)  
Antiguo 23/11/2013, 18:15
Avatar de dashtrash
Colaborador
 
Fecha de Ingreso: abril-2007
Ubicación: Ni en Sevilla,ni en Sanlúcar..qué más da..
Mensajes: 927
Antigüedad: 17 años, 7 meses
Puntos: 270
Respuesta: Número aleatorio pero evitando uno en particular

Cita:
Iniciado por rodrypaladin Ver Mensaje
Creo que esa estructura sobrepasa de momento mis posibilidades jeje aunque no es complicado solo llevo 3días con php.
No tiene nada que ver con PHP..Intentar no encontrar repetidos a base de reintentar rand() no es la forma correcta, independientemente del lenguaje.
Si lo quieres ver así, el bombo es un sitio donde están todos los números (array con todos los números).
Cuando sale un número del bombo, equivale a que salga del array.
El siguiente número sale de un bombo que contiene n-1 bolas (un array con 1 elemento menos).

Lo que intentas hacer es un bombo donde, cada vez que sale una bola, la vuelves a meter en el bombo.Si vuelve a salir la misma, no pasa nada, la metes otra vez en el bombo, y vuelves a girarlo...hasta que salga una nueva.

No es una cuestión del lenguaje de programación, sino de modelar bien el proceso.

Y, también, de darse cuenta que da exactamente lo mismo obtener toda la secuencia aleatoria de números al principio, que ir sacándola uno a uno.

Aparte de eso, en tu código, estás haciendo asignaciones en el if, en vez de comparaciones.
  #10 (permalink)  
Antiguo 23/11/2013, 20:17
Avatar de Alexis88
Philosopher
 
Fecha de Ingreso: noviembre-2011
Ubicación: Tacna, Perú
Mensajes: 5.552
Antigüedad: 13 años
Puntos: 977
Respuesta: Número aleatorio pero evitando uno en particular

Como bien te han dicho, no hay forma de que coloques una excepción, al menos no dentro de una función primitiva de PHP, sin embargo, puedes emular ese accionar. Un pequeño ejemplo:

Código PHP:
Ver original
  1. <?php
  2. function loteria ($numero) {
  3.     $aleatorio = mt_rand(1, 49);
  4.     if (!in_array($aleatorio, $numero)) $numero[] = $aleatorio;
  5.     return count($numero) < 5 ? loteria($numero) : $numero;
  6. }
  7. echo "Numero de loteria: ";
  8. foreach (loteria(array()) as $valor) echo $valor . "\t";
  9. ?>

Utilizo un array el cual albergará a los 5 números aleatorios, para imprimir sus valores, puedo usar cualquiera de los ciclos existentes, en este caso, usaré el constructor Foreach. A éste, le debo de asignar un array, el cual se alimentará con la función loteria que es la que generará los 5 números aleatorios.

En dicha función, recibimos el array en la variable $numero, enseguida, generamos un número aleatorio entre el 1 y el 49 con la función mt_rand, que actúa como la función rand que usas, con la diferencia que ésta genera el número aleatorio 4 veces más rápido que la otra. Luego, con la función in_array, verificamos si el número aleatorio generado, se encuentra en el array, de no ser así, lo asignamos al array. El signo ! indica negación. Finalmente, si la cantidad de elementos en el array es menor a 5, volvemos a invocar a la función loteria, enviándole el array con los valores que contenga hasta ese momento, caso contrario, es decir, cuando ya tengamos los 5 números, retornamos el array a la primera llamada que se hizo dentro del constructor Foreach.

Ya teniendo el array completo, solamente procedemos a imprimir su contenido que son los 5 números aleatorios. La "\t", sirve como tabulador, para que entre los números, haya un espacio y no salgan todos juntos.

Saludos
  #11 (permalink)  
Antiguo 23/11/2013, 20:59
Avatar de rodrypaladin
Moderador
 
Fecha de Ingreso: abril-2010
Ubicación: Madrid
Mensajes: 2.127
Antigüedad: 14 años, 7 meses
Puntos: 468
Respuesta: Número aleatorio pero evitando uno en particular

No consigo entender del todo el código pero si que funciona a la perfección, tendré que mirarme el tema de las funciones que es lo que menos entiendo de ese código!! Mil gracias
__________________
No te olvides de dar +1 a quien te echa un cable ;)
  #12 (permalink)  
Antiguo 24/11/2013, 04:50
Avatar de dashtrash
Colaborador
 
Fecha de Ingreso: abril-2007
Ubicación: Ni en Sevilla,ni en Sanlúcar..qué más da..
Mensajes: 927
Antigüedad: 17 años, 7 meses
Puntos: 270
Respuesta: Número aleatorio pero evitando uno en particular

Cita:
Iniciado por Alexis88 Ver Mensaje
Como bien te han dicho, no hay forma de que coloques una excepción, al menos no dentro de una función primitiva de PHP, sin embargo, puedes emular ese accionar. Un pequeño ejemplo:

Código PHP:
Ver original
  1. <?php
  2. function loteria ($numero) {
  3.     $aleatorio = mt_rand(1, 49);
  4.     if (!in_array($aleatorio, $numero)) $numero[] = $aleatorio;
  5.     return count($numero) < 5 ? loteria($numero) : $numero;
  6. }
  7. echo "Numero de loteria: ";
  8. foreach (loteria(array()) as $valor) echo $valor . "\t";
  9. ?>
Saludos
Si ya no era buena solución "reintentar" rand hasta que salga un número que no hubiera salido previamente, esta solución añade un problema aún mayor: reintenta recursivamente.

Supongamos que el bombo tiene 1000 numeros.Cuando se hayan sacado 999 números (sólo queda 1), esta solución intentará de media 1000 llamadas recursivas para intentar sacar el único número que queda del bombo.No sólo es ineficiente..algunas extensiones típicas (xdebug), matarán la petición (el tamaño de la pila de llamadas es 100 por defecto).

Un programador, cuando ve que para sacar un número que está determinado unívocamente (es el único que queda en el bombo) está haciendo 1000 llamadas a función, debería replantearse su solución.
Aparte, como ya he dicho, en php esto se resuelve en 2 líneas con funciones estándar.
  #13 (permalink)  
Antiguo 24/11/2013, 09:36
Avatar de carlos_belisario
Colaborador
 
Fecha de Ingreso: abril-2010
Ubicación: Venezuela Maracay Aragua
Mensajes: 3.156
Antigüedad: 14 años, 7 meses
Puntos: 461
Respuesta: Número aleatorio pero evitando uno en particular

es que en teoría esto debe resolver su problema

Cita:
Iniciado por dashtrash Ver Mensaje
Código PHP:
Ver original
  1. $a=range(1,49);
  2. shuffle($a);
  3. echo implode(",",$a);
y simplemente si quieres los 2,3,4,5, ... 49 números recorres el array
Código PHP:
Ver original
  1. $premiados = 5; // si son los 5 premeados
  2. for ($i = 0; $i < $premiados; $i++) {
  3.     $n = $i + 1;
  4.     echo "el premio " . $n . " es " . $a[$i] . "<br />";
  5. }
te muestra la cantidad de premiados que deseas, claro esta que el implode que te colocaron es para que veas que hay un array con 49 números ordenados aleatoriamente cada vez que llamas al script. Saludos
__________________
aprende d tus errores e incrementa tu conocimientos
it's not a bug, it's an undocumented feature By @David
php the right way
  #14 (permalink)  
Antiguo 24/11/2013, 13:27
Avatar de Alexis88
Philosopher
 
Fecha de Ingreso: noviembre-2011
Ubicación: Tacna, Perú
Mensajes: 5.552
Antigüedad: 13 años
Puntos: 977
Respuesta: Número aleatorio pero evitando uno en particular

Para los 49 números planteados (no son mil, si fuera así, plantearía de otra forma la solución), creo que está bien. Tu solución es más eficiente, eso está claro, pero al menos obtuve un promedio de entre 5 y 8 iteraciones (utilizando un contador que fue aumentando su valor en cada llamada), no me parece tan malo.

Por último, es solo una solución más, así como la planteada por Triby. Si crees que para este caso es ineficiente, está bien, pero particularmente, no lo creo así.

Saludos
  #15 (permalink)  
Antiguo 24/11/2013, 14:52
Avatar de Dalam  
Fecha de Ingreso: septiembre-2010
Mensajes: 409
Antigüedad: 14 años, 2 meses
Puntos: 56
Respuesta: Número aleatorio pero evitando uno en particular

Mejorando la funcion de triby ( No te ofendas )

Código PHP:
Ver original
  1. <?php
  2. $numeros = array(); // Este array contendra los resultados extraidos
  3. $n = range(1, 49);  // Generamos un array con todas las posibilidades
  4. shuffle($n); // Desordenamos el array
  5. // Un ciclo para generar 5 numeros
  6. for($i = 0; $i < 5; $i++) {
  7.     $numeros[] = $n[$i]; // Añadimos el resultado al array de resultados
  8. }
  9. // Para ver el contenido del array
  10. var_dump($numeros);
  11. ?>
  #16 (permalink)  
Antiguo 24/11/2013, 15:14
Avatar de Dalam  
Fecha de Ingreso: septiembre-2010
Mensajes: 409
Antigüedad: 14 años, 2 meses
Puntos: 56
De acuerdo Respuesta: Número aleatorio pero evitando uno en particular

Podriamos reducir mas el codigo de esta forma
Código PHP:
<?php
$n 
range(149);  // Generamos un array con todas las posibilidades
shuffle($n); // Desordenamos el array
$array array_slice($n05); // Extraemos los 5 primeros valores de las posibilidades
var_dump($array);
?>
No e chequeado el tiempo ni los recursos que consumen cada uno de los dos scripts que te e mandado, pero con consultas de tan pocos valores no es critico el tiempo ni los recursos.
  #17 (permalink)  
Antiguo 24/11/2013, 15:22
Avatar de Alexis88
Philosopher
 
Fecha de Ingreso: noviembre-2011
Ubicación: Tacna, Perú
Mensajes: 5.552
Antigüedad: 13 años
Puntos: 977
Respuesta: Número aleatorio pero evitando uno en particular

Cita:
Iniciado por Dalam Ver Mensaje
No he chequeado el tiempo ni los recursos que consumen cada uno de los dos scripts que te e mandado, pero con consultas de tan pocos valores no es critico el tiempo ni los recursos.
Exacto.
  #18 (permalink)  
Antiguo 24/11/2013, 18:57
Avatar de Triby
Mod on free time
 
Fecha de Ingreso: agosto-2008
Ubicación: $MX->Gto['León'];
Mensajes: 10.106
Antigüedad: 16 años, 3 meses
Puntos: 2237
Respuesta: Número aleatorio pero evitando uno en particular

Dalam, no me ofendo, al contrario, siempre es bueno ver opciones más eficientes y desde que vi la propuesta por dashtrash me quedó claro que es la mejor.
__________________
- León, Guanajuato
- GV-Foto
  #19 (permalink)  
Antiguo 25/11/2013, 12:05
Avatar de rodrypaladin
Moderador
 
Fecha de Ingreso: abril-2010
Ubicación: Madrid
Mensajes: 2.127
Antigüedad: 14 años, 7 meses
Puntos: 468
Respuesta: Número aleatorio pero evitando uno en particular

Cita:
Iniciado por Dalam Ver Mensaje
Mejorando la funcion de triby ( No te ofendas )

Código PHP:
Ver original
  1. <?php
  2. $numeros = array(); // Este array contendra los resultados extraidos
  3. $n = range(1, 49);  // Generamos un array con todas las posibilidades
  4. shuffle($n); // Desordenamos el array
  5. // Un ciclo para generar 5 numeros
  6. for($i = 0; $i < 5; $i++) {
  7.     $numeros[] = $n[$i]; // Añadimos el resultado al array de resultados
  8. }
  9. // Para ver el contenido del array
  10. var_dump($numeros);
  11. ?>
Una de mis preguntas es que el número 2º que se muestre no sea igual que el 1º, y así con el 3º,4º y 5º.

Pero me parece que por fin entendí bien ese código a pesar de mis escasos conocimientos XD... La manera de que en este código no se repitan los números es que simplemente desordena los números del array $n y luego añadimos los 5 primeros números al array $numeros. de esa manera es totalmente imposible que se repitan. ¿ Es cierto ?
__________________
No te olvides de dar +1 a quien te echa un cable ;)
  #20 (permalink)  
Antiguo 25/11/2013, 12:50
Avatar de marlanga  
Fecha de Ingreso: enero-2011
Ubicación: Murcia
Mensajes: 1.024
Antigüedad: 13 años, 10 meses
Puntos: 206
Respuesta: Número aleatorio pero evitando uno en particular

Hacer range para crear un array de valores consecutivos y un suffle al array para desordenarlo, y escoger después varios de esos números aleatorios, sólo tiene sentido si el número de elementos a extrar es una gran porción del conjunto total de números, por ejemplo si queremos obtener 50 valores distintos con un rango entre 1 y 100. Esta filosofía tiene una mejora, y es no hacer un shuffle, si no un array_rand, que saca valores al azar. Así mejoras algo el tiempo de ejecución, pero no el consumo de memoria, que sigue siendo disparatado en comparación al problema a resolver.

Si la porción de números a sacar es escasa, como este caso (queremos 5 de 49), la fuerza bruta es mejor porque además de consumir menos recursos de memoria (siempre consume menos), también tardará menos.

La evidencia:
http://ideone.com/CDC6om


Código PHP:
Ver original
  1. <?php
  2. function azarSuffle($min, $max, $cantidad) {
  3.     $numeros = range($min, $max);
  4.     shuffle($numeros );
  5.     return array_slice($numeros, 0, $cantidad);
  6. }
  7. function azarBruto($min, $max, $cantidad) {
  8.     $numeros=array();
  9.     while (count($numeros)<$cantidad){
  10.         $aleatorio=rand($min, $max);
  11.         if (!in_array($aleatorio, $numeros)) $numeros[]=$aleatorio;
  12.     }
  13.     return $numeros;
  14. }
  15. function azarMejor($min, $max, $cantidad) {
  16.     $numeros = range($min, $max);
  17.     return array_rand($numeros, $cantidad);
  18. }
  19.  
  20. $ini=round(microtime(true) * 1000);
  21. for ($i=0;$i<1000;$i++)
  22.     azarSuffle(1,49,5);
  23. $fin=round(microtime(true) * 1000);
  24. $tiempoSuffle=$fin-$ini;
  25.  
  26. $ini=round(microtime(true) * 1000);
  27. for ($i=0;$i<1000;$i++)
  28.     azarBruto(1,49,5);
  29. $fin=round(microtime(true) * 1000);
  30. $tiempoBruto=$fin-$ini;
  31.  
  32. $ini=round(microtime(true) * 1000);
  33. for ($i=0;$i<1000;$i++)
  34.     azarMejor(1,49,5);
  35. $fin=round(microtime(true) * 1000);
  36. $tiempoMejor=$fin-$ini;
  37.  
  38. echo "Con suffle: ".$tiempoSuffle."\n";
  39. echo "Fuerza bruta: ".$tiempoBruto."\n";
  40. echo "Mejor: ".$tiempoMejor;
  41. ?>
Resultado:
Código BASH:
Ver original
  1. Con suffle: 13
  2. Fuerza bruta: 7
  3. Mejor: 11

Última edición por marlanga; 25/11/2013 a las 13:03
  #21 (permalink)  
Antiguo 25/11/2013, 16:08
Avatar de dashtrash
Colaborador
 
Fecha de Ingreso: abril-2007
Ubicación: Ni en Sevilla,ni en Sanlúcar..qué más da..
Mensajes: 927
Antigüedad: 17 años, 7 meses
Puntos: 270
Respuesta: Número aleatorio pero evitando uno en particular

Pues no sé qué le pasará entonces a mi implementación de rand()
Código PHP:
Ver original
  1. Con suffle: 14
  2. Fuerza bruta: 26
  3. Mejor: 14
  4.  
  5. Con suffle: 12
  6. Fuerza bruta: 24
  7. Mejor: 10
  8.  
  9. Con suffle: 13
  10. Fuerza bruta: 28
  11. Mejor: 12
  12.  
  13. Con suffle: 13
  14. Fuerza bruta: 23
  15. Mejor: 11
  16.  
  17. Con suffle: 14
  18. Fuerza bruta: 24
  19. Mejor: 11
  20.  
  21. Con suffle: 13
  22. Fuerza bruta: 24
  23. Mejor: 10
  24.  
  25. Con suffle: 13
  26. Fuerza bruta: 24
  27. Mejor: 11
  28.  
  29. Con suffle: 13
  30. Fuerza bruta: 23
  31. Mejor: 11
  32.  
  33. Con suffle: 13
  34. Fuerza bruta: 24
  35. Mejor: 11
  36.  
  37. Con suffle: 13
  38. Fuerza bruta: 23
  39. Mejor: 11

Así que he ido a: http://sandbox.onlinephpfunctions.com/, a ver qué salía:
PHP 5.5.5 : Con suffle: 10 Fuerza bruta: 121 Mejor: 10
PHP 5.5.0 Con suffle: 10 Fuerza bruta: 127 Mejor: 9
PHP 5.4.21 Con suffle: 10 Fuerza bruta: 137 Mejor: 9
PHP 5.4.5: Con suffle: 9 Fuerza bruta: 131 Mejor: 9
PHP 5.3.23 :Con suffle: 9 Fuerza bruta: 135 Mejor: 9

Cita:
Esta filosofía tiene una mejora, y es no hacer un shuffle, si no un array_rand, que saca valores al azar. Así mejoras algo el tiempo de ejecución, pero no el consumo de memoria, que sigue siendo disparatado en comparación al problema a resolver.
Si quieres comparar shuffle con array_rand, no añadas un array_slice.Quita el array_slice (que no es necesario), y vuelve a ejecutar tus tests.

Veamos que dice el sandbox sin el array_slice:

5.5.5: Con suffle: 7 Fuerza bruta: 127 Mejor: 9
5.5.0: Con suffle: 6 Fuerza bruta: 129 Mejor: 10
5.4.1:Con suffle: 7 Fuerza bruta: 130 Mejor: 9
5.3.23:Con suffle: 6 Fuerza bruta: 134 Mejor: 9

Sobre el consumo de memoria "disparatado" en comparación al problema a resolver (aprox. 4*44 bytes): Declara 1 sola estructura de control en PHP (un if, un for, cualquier cosa), y va a consumir más que eso.El algoritmo de fuerza bruta declara un bucle, variables, aparte de arrays de destino, cosa que en shuffle no necestas....Mucho más de 4*44 bytes.No veo lo "disparatado".

PD : resultados de http://writecodeonline.com/php:
Con suffle: 12 Fuerza bruta: 236 Mejor: 17 (sin array_slice)

PD2: Ideone da los resultados que indicas.Para n=8, el tiempo es el mismo en shuffle y rand.La única cosa donde he visto resultados "parecidos" es en codepad.org, pero es demasiado inestable como para ser significativo.

Última edición por dashtrash; 25/11/2013 a las 16:48
  #22 (permalink)  
Antiguo 25/11/2013, 17:09
Avatar de marlanga  
Fecha de Ingreso: enero-2011
Ubicación: Murcia
Mensajes: 1.024
Antigüedad: 13 años, 10 meses
Puntos: 206
Respuesta: Número aleatorio pero evitando uno en particular

Sin array_slice, ¿Qué devuelves?¿Un array de 49 posiciones desordenado? Se debe devolver un array con los números aleatorios, asi pues el array_slice es necesario. Esa operación solo corta el array por el principio. Las funciones shuffle y range son mas costosas.

Y cuando lo ejecuto yo sobre http://sandbox.onlinephpfunctions.com/, me sale:
Código BASH:
Ver original
  1. Con suffle: 4
  2. Fuerza bruta: 3
  3. Mejor: 3
Código PHP:
Ver original
  1. Con suffle: 8
  2. Fuerza bruta: 3
  3. Mejor: 5

Pero no hace falta ni ejecutar, es solo cuestión de pensar qué hace cada algoritmo. El único peligro que tiene el algoritmo de fuerza bruta de resultar costoso, es cuando falla muchas veces al tratar de insertar un número aleatorio en un array que ya lo contiene. Y ese caso ocurre cuando el número de números a obtener es una porción significativa del rango de números posibles. No he hecho pruebas, pero estimo que a partir de 1/3, es posible que la fuerza bruta no sea la mejor opción.

La operación shuffle no es atómica. Su implementación por cojones tiene que recorrer todo el array suministrado, y desordenarlo usando seguramente un FOR.

En cuanto a coste de memoria, tanto el shuffle como el mejorado usan un array de 49 posiciones obtenido con range, y devuelve otro array de 6 posiciones. Si un entero ocupa en php 4 bytes, entonces (49+6)*4=220 bytes.
Sin embargo, el e fuerza bruta sólo hace uso de un array que tendrá 6 posiciones, y una variable. (6+1)*4=28 bytes.

Última edición por marlanga; 25/11/2013 a las 17:26
  #23 (permalink)  
Antiguo 25/11/2013, 18:15
Avatar de dashtrash
Colaborador
 
Fecha de Ingreso: abril-2007
Ubicación: Ni en Sevilla,ni en Sanlúcar..qué más da..
Mensajes: 927
Antigüedad: 17 años, 7 meses
Puntos: 270
Respuesta: Número aleatorio pero evitando uno en particular

Cita:
Iniciado por marlanga Ver Mensaje
Sin array_slice, ¿Qué devuelves?¿Un array de 49 posiciones desordenado? Se debe devolver un array con los números aleatorios, asi pues el array_slice es necesario.
No.Se van a usar 5 posiciones del array.Mientras los números del array se utilicen según índice (bucle), y se muestren 5 resultados, da lo mismo que el array tenga 5 o 49 posiciones.
En cualquier caso, meter array_slice por enmedio significa que no se está comparando array_rand con shuffle.

Cita:
Iniciado por marlanga Ver Mensaje
Esa operación solo corta el array por el principio. Las funciones shuffle y range son mas costosas.
Lo suficiente para hacerla estadisticamente igual a range

Cita:
Iniciado por marlanga Ver Mensaje
Y cuando lo ejecuto yo sobre http://sandbox.onlinephpfunctions.com/, me sale:
Código BASH:
Ver original
  1. Con suffle: 4
  2. Fuerza bruta: 3
  3. Mejor: 3
Código PHP:
Ver original
  1. Con suffle: 8
  2. Fuerza bruta: 3
  3. Mejor: 5
Tienes razón, se me fue otro test.Había algo que no me cuadraba en todo esto.Por algún motivo, en mi ordenador los resultados siguen siendo los mismos.

Cita:
Iniciado por marlanga Ver Mensaje
Pero no hace falta ni ejecutar, es solo cuestión de pensar qué hace cada algoritmo. El único peligro que tiene el algoritmo de fuerza bruta de resultar costoso, es cuando falla muchas veces al tratar de insertar un número aleatorio en un array que ya lo contiene. Y ese caso ocurre cuando el número de números a obtener es una porción significativa del rango de números posibles. No he hecho pruebas, pero estimo que a partir de 1/3, es posible que la fuerza bruta no sea la mejor opción.
Al pensar más profundamente, verás que en el rango de tiempos que nos movemos (8 milisegundos en 1000 iteraciones), interpretar código PHP es tan costoso como mover unos cuantos punteros de C.Según mis pruebas, con 8 números (1/6), el rendimiento es el mismo, según los sandbox online.

Cita:
Iniciado por marlanga Ver Mensaje
La operación shuffle no es atómica. Su implementación por cojones tiene que recorrer todo el array suministrado, y desordenarlo usando seguramente un FOR.
En C.No en PHP.PHP es un lenguaje MUY ineficiente.Todo lo que haga C, es mucho más rápido que lo que haga PHP.En C,49 elementos se recorren en nada.El desordenarlo es una operación simple de punteros.Aunque no sea "atómica", las conversiones necesarias entre un array PHP y la estructura de datos internas usada por el parser, son menos.Y no son triviales (un array PHP no es un array C).
Tu lógica es sencilla...Pero estás suponiendo lo que hace PHP por dentro.array_rand hace chequeos sobre las keys que shuffle no hace.shuffle es casi exclusivamente manipulación de punteros, mas llamadas a rand().array_rand, no.

Cita:
Iniciado por marlanga Ver Mensaje
En cuanto a coste de memoria, tanto el shuffle como el mejorado usan un array de 49 posiciones obtenido con range, y devuelve otro array de 6 posiciones.
Shuffle no devuelve ningún array.Tu función devuelve un array.Se pueden utilizar tranquilamente los 5 primeros elementos sin necesidad de recortar el array.

Cita:
Iniciado por marlanga Ver Mensaje
Si un entero ocupa en php 4 bytes, entonces (49+6)*4=22' bytes.
Sin embargo, el e fuerza bruta sólo hace uso de un array que tendrá 6 posiciones, y una variable. (6+1)*4=28 bytes.
Te pego aqui lo que hace PHP al declarar *una* variable (ni siquiera 1 array,que tiene sus propias estructuras de control)
Código C:
Ver original
  1. PHPAPI void php_register_variable_ex(char *var_name, zval *val, zval *track_vars_array TSRMLS_DC)
  2. {
  3.     char *p = NULL;
  4.     char *ip = NULL;        /* index pointer */
  5.     char *index;
  6.     char *var, *var_orig;
  7.     int var_len, index_len;
  8.     zval *gpc_element, **gpc_element_p;
  9.     zend_bool is_array = 0;
  10.     HashTable *symtable1 = NULL;
  11.     ALLOCA_FLAG(use_heap)
  12.  
  13.     assert(var_name != NULL);
  14.  
  15.     if (track_vars_array) {
  16.         symtable1 = Z_ARRVAL_P(track_vars_array);
  17.     }
  18.  
  19.     if (!symtable1) {
  20.         /* Nothing to do */
  21.         zval_dtor(val);
  22.         return;
  23.     }
  24.  
  25.  
  26.     /* ignore leading spaces in the variable name */
  27.     while (*var_name && *var_name==' ') {
  28.         var_name++;
  29.     }
  30.    
  31.     /*
  32.      * Prepare variable name
  33.      */
  34.     var_len = strlen(var_name);
  35.     var = var_orig = do_alloca(var_len + 1, use_heap);
  36.     memcpy(var_orig, var_name, var_len + 1);
  37.  
  38.     /* ensure that we don't have spaces or dots in the variable name (not binary safe) */
  39.     for (p = var; *p; p++) {
  40.         if (*p == ' ' || *p == '.') {
  41.             *p='_';
  42.         } else if (*p == '[') {
  43.             is_array = 1;
  44.             ip = p;
  45.             *p = 0;
  46.             break;
  47.         }
  48.     }
  49.     var_len = p - var;
  50.  
  51.     if (var_len==0) { /* empty variable name, or variable name with a space in it */
  52.         zval_dtor(val);
  53.         free_alloca(var_orig, use_heap);
  54.         return;
  55.     }
  56.  
  57.     /* GLOBALS hijack attempt, reject parameter */
  58.     if (symtable1 == EG(active_symbol_table) &&
  59.         var_len == sizeof("GLOBALS")-1 &&
  60.         !memcmp(var, "GLOBALS", sizeof("GLOBALS")-1)) {
  61.         zval_dtor(val);
  62.         free_alloca(var_orig, use_heap);
  63.         return;
  64.     }
  65.  
  66.     index = var;
  67.     index_len = var_len;
  68.  
  69.     if (is_array) {
  70.         int nest_level = 0;
  71.         while (1) {
  72.             char *index_s;
  73.             int new_idx_len = 0;
  74.  
  75.             if(++nest_level > PG(max_input_nesting_level)) {
  76.                 HashTable *ht;
  77.                 /* too many levels of nesting */
  78.  
  79.                 if (track_vars_array) {
  80.                     ht = Z_ARRVAL_P(track_vars_array);
  81.                     zend_symtable_del(ht, var, var_len + 1);
  82.                 }
  83.  
  84.                 zval_dtor(val);
  85.  
  86.                 /* do not output the error message to the screen,
  87.                  this helps us to to avoid "information disclosure" */
  88.                 if (!PG(display_errors)) {
  89.                     php_error_docref(NULL TSRMLS_CC, E_WARNING, "Input variable nesting level exceeded %ld. To increase the limit change max_input_nesting_level in php.ini.", PG(max_input_nesting_level));
  90.                 }
  91.                 free_alloca(var_orig, use_heap);
  92.                 return;
  93.             }
  94.  
  95.             ip++;
  96.             index_s = ip;
  97.             if (isspace(*ip)) {
  98.                 ip++;
  99.             }
  100.             if (*ip==']') {
  101.                 index_s = NULL;
  102.             } else {
  103.                 ip = strchr(ip, ']');
  104.                 if (!ip) {
  105.                     /* PHP variables cannot contain '[' in their names, so we replace the character with a '_' */
  106.                     *(index_s - 1) = '_';
  107.  
  108.                     index_len = 0;
  109.                     if (index) {
  110.                         index_len = strlen(index);
  111.                     }
  112.                     goto plain_var;
  113.                     return;
  114.                 }
  115.                 *ip = 0;
  116.                 new_idx_len = strlen(index_s); 
  117.             }
  118.  
  119.             if (!index) {
  120.                 MAKE_STD_ZVAL(gpc_element);
  121.                 array_init(gpc_element);
  122.                 if (zend_hash_next_index_insert(symtable1, &gpc_element, sizeof(zval *), (void **) &gpc_element_p) == FAILURE) {
  123.                     zval_ptr_dtor(&gpc_element);
  124.                     zval_dtor(val);
  125.                     free_alloca(var_orig, use_heap);
  126.                     return;
  127.                 }
  128.             } else {
  129.                 if (zend_symtable_find(symtable1, index, index_len + 1, (void **) &gpc_element_p) == FAILURE
  130.                     || Z_TYPE_PP(gpc_element_p) != IS_ARRAY) {
  131.                     MAKE_STD_ZVAL(gpc_element);
  132.                     array_init(gpc_element);
  133.                     zend_symtable_update(symtable1, index, index_len + 1, &gpc_element, sizeof(zval *), (void **) &gpc_element_p);
  134.                 }
  135.             }
  136.             symtable1 = Z_ARRVAL_PP(gpc_element_p);
  137.             /* ip pointed to the '[' character, now obtain the key */
  138.             index = index_s;
  139.             index_len = new_idx_len;
  140.  
  141.             ip++;
  142.             if (*ip == '[') {
  143.                 is_array = 1;
  144.                 *ip = 0;
  145.             } else {
  146.                 goto plain_var;
  147.             }
  148.         }
  149.     } else {
  150. plain_var:
  151.         MAKE_STD_ZVAL(gpc_element);
  152.         gpc_element->value = val->value;
  153.         Z_TYPE_P(gpc_element) = Z_TYPE_P(val);
  154.         if (!index) {
  155.             if (zend_hash_next_index_insert(symtable1, &gpc_element, sizeof(zval *), (void **) &gpc_element_p) == FAILURE) {
  156.                 zval_ptr_dtor(&gpc_element);
  157.             }
  158.         } else {
  159.             /*
  160.              * According to rfc2965, more specific paths are listed above the less specific ones.
  161.              * If we encounter a duplicate cookie name, we should skip it, since it is not possible
  162.              * to have the same (plain text) cookie name for the same path and we should not overwrite
  163.              * more specific cookies with the less specific ones.
  164.              */
  165.             if (PG(http_globals)[TRACK_VARS_COOKIE] &&
  166.                 symtable1 == Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_COOKIE]) &&
  167.                 zend_symtable_exists(symtable1, index, index_len + 1)) {
  168.                 zval_ptr_dtor(&gpc_element);
  169.             } else {
  170.                 zend_symtable_update(symtable1, index, index_len + 1, &gpc_element, sizeof(zval *), (void **) &gpc_element_p);
  171.             }
  172.         }
  173.     }
  174.     free_alloca(var_orig, use_heap);
  175. }
Crees que 1 variable más, simplemente son 4 bytes?
Sólo la estructura ZVAL que la contiene, en el caso muy simple de un entero (long) ocupa unos 20 bytes.Sin contar el nombre de la variable, y su posición en la tabla de símbolos.Un array es más grande.
La aritmética de la memoria **real** no es tan simple como "resto la longitud de los arrays".Si nos estamos molestando por esos bytes, *todo* cuenta.
En cualquier caso, dónde está el uso de memoria "disparatado"?

Bueno, todo esto es una discusión divertida (para variar), pero, por mi parte como conclusión, ni en este caso en particular, y, ni mucho menos en general, la fuerza bruta es la forma de hacer lo que el OP quería.

Última edición por dashtrash; 25/11/2013 a las 18:30
  #24 (permalink)  
Antiguo 26/11/2013, 02:26
Avatar de marlanga  
Fecha de Ingreso: enero-2011
Ubicación: Murcia
Mensajes: 1.024
Antigüedad: 13 años, 10 meses
Puntos: 206
Respuesta: Número aleatorio pero evitando uno en particular

Bueno, pónmelo mas fácil. Imagina que quieres 10 números sin repetir entre 1 y 10.000.000.
¿Cuál algoritmo escogerías? Fuerza bruta. Bien. Ahora la cuestión sólo es ¿a cuánto bajarías el límite para seguir usando fuerza bruta?. Yo dije que alrededor de 1/3. Es decir, para diez valores usaría fuerza bruta para el rango (1-30).

Todo mi texto se basa en esta idea.
  #25 (permalink)  
Antiguo 26/11/2013, 03:32
Avatar de dashtrash
Colaborador
 
Fecha de Ingreso: abril-2007
Ubicación: Ni en Sevilla,ni en Sanlúcar..qué más da..
Mensajes: 927
Antigüedad: 17 años, 7 meses
Puntos: 270
Respuesta: Número aleatorio pero evitando uno en particular

Cita:
Iniciado por marlanga Ver Mensaje
Bueno, pónmelo mas fácil. Imagina que quieres 10 números sin repetir entre 1 y 10.000.000.
¿Cuál algoritmo escogerías? Fuerza bruta. Bien. Ahora la cuestión sólo es ¿a cuánto bajarías el límite para seguir usando fuerza bruta?. Yo dije que alrededor de 1/3. Es decir, para diez valores usaría fuerza bruta para el rango (1-30).

Todo mi texto se basa en esta idea.
Así que 10.000.000 de entradas, si tuvieras que elegir 3.333.333 números (1/3) usarías fuerza bruta....
Ejecuta el benchmark.Para 30 vs 10.Y luego súbelo sólo a 33 entre 100.


No es una cuestión sólo de proporción, sino del número de elementos, ya que, aunque la probabilidad se mantenga, el número de veces que se prueba es mayor, y el tiempo crece.El algoritmo de shuffle es proporcional sólo al número de elementos.El fuerza bruta , no.A medida que el número de elementos crece, shuffle **rápidamente** se hace más eficiente en términos de tiempo de ejecución.

El problema es que elegir usar fuerza bruta es sensible al entorno, así que no puedes cambiar las condiciones de entrada, sin meterte en otro problema distinto.

Aunque tienes razón en que para el caso de 10 elementos entre 10.000.000, es más eficiente tomar 10 elementos al azar, yo no lo veo en el caso inicial, y, sobre todo, en el 99% de los casos prácticos, shuffle es la mejor solución.Hay casos en los que ni shuffle, ni array_rand, ni fuerza bruta son los más **eficientes**.

Intenta ahora elaborar una respuesta intentando basarla en la eficiencia, que sea *exacta*.Cuál es la relación entre número de elementos inicial - número de elementos a tomar - probabilidad de colisión (que no es constante, es un sumatorio)... o simplemente, dices "usa shuffle".
  #26 (permalink)  
Antiguo 26/11/2013, 04:26
Avatar de marlanga  
Fecha de Ingreso: enero-2011
Ubicación: Murcia
Mensajes: 1.024
Antigüedad: 13 años, 10 meses
Puntos: 206
Respuesta: Número aleatorio pero evitando uno en particular

Sé de los peligros que tiene la fuerza bruta, y también que es mas cómodo utilizar alguno de los otros dos métodos porque sus tiempos de ejecución no dependen de probabilidades, son constantes en relación a los datos de entrada.

Y puestos a elegir entre los constantes, ¿por qué usas shuffle en vez del array_rand? El trabajo que hace array_rand es mas ligero que el de shuffle cuando sólo necesitas obtener una porción aleatoria del array original. La diferencia de tiempos se incrementa cuanto mayor es la diferencia entre el rango total de números, y la cantidad de números que quieres obtener. Haz la prueba usando sólo esas dos funciones, array_rand gana mientras la proporción de elementos a devolver sea menor a 4/5. Cuando el array a devolver es casi tan grande como el que crea range, es evidente que lo mejor es shuffle.
  #27 (permalink)  
Antiguo 26/11/2013, 07:41
Avatar de Dalam  
Fecha de Ingreso: septiembre-2010
Mensajes: 409
Antigüedad: 14 años, 2 meses
Puntos: 56
Respuesta: Número aleatorio pero evitando uno en particular

Me parece que estais haciendo una montaña de un grano de arena.
el chico esta aprendiendo php y se lo e puesto para que lo que quiere hacer lo consiga con 3 lineas de codigo.
Una vez que aprenda el lenguaje, ya tendra tiempo de aprender a optimizar codigo.
Sin enbargo con todo esto que estais poniendo, seguro que lo unico que estais consiguiendo es liarle con el codigo "ACORDAROS QUE ES NOVATO".
Por otro lado 30.000 repeticiones del script tardan 0,32 segundos, que creo que el tiempo que tarde en ejecutar el script es lo unico que por el momento deberia de importarle a rodrypaladin.
Con todo esto no quiero quitaros la razon, pero bajo mi punto de vista, si le cuesta entender mi codigo, ya no digo todo el que estais poniendo vosotros.
Un saludo
  #28 (permalink)  
Antiguo 26/11/2013, 07:50
Avatar de marlanga  
Fecha de Ingreso: enero-2011
Ubicación: Murcia
Mensajes: 1.024
Antigüedad: 13 años, 10 meses
Puntos: 206
Respuesta: Número aleatorio pero evitando uno en particular

Deja que los que ya sabemos algo de php, debatamos y sepamos más php.

Etiquetas: evitando
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 15:12.