3

Voy a iniciar con un ejemplo: tengo una caja, de la cual tienes 5 oportunidades de sacar una pelota, de estas existen 4 tipos, dorada roja azul y verde, el porcentaje global (es decir, por caja) de sacar una dorada es del 15%, roja 15%, azul 30% y verde 40% Para programar este ejemplo no estoy seguro si se divide el porcentaje global (por caja) entre el numero de intentos (5) o se realiza indiscriminadamente la aleatorizacion (es decir, tener por ejemplo 15% en cada intento para una dorada) Si juntaramos los porcentajes divididos por intento tendríamos 3% 3% 6% 8% respectivamente, pero... donde quedan los otros 80%?

Pasandolo a codigo sería...

int i,d;
for(d=0;d<5;d++){
   rnd=rand() % 100;
   if(rnd<6)i=5; //5%
   if(rnd<21 && rnd>5)i=15; //15%
   if(rnd<36 && rnd>20)i=15; //15%
   if(rnd<66 && rnd>35)i=30; //30%
   if(rnd<101 && rnd>65)i=40; //40%
}
switch(rnd){
   case 15: blabla
   case 30: blabla
   case 40: blabla
}

No estoy seguro que realmente sea fidedigno este codigo respecto a los porcentajes del problema inicial o si mi logica sea la correcta. Agradecería bastante algo de ayuda.

Winebous
  • 55
  • 3
  • tu lógica está bien, pero las condiciones no. Primero, no debería ser `rand() % 101`?? si quieres entre 0 y 100 (aunque en esta ocasión quizá el 0 cuenta como 1 y 99 como 100). por otra parte, las condiciones deben ser algo como `(rdn < 15)`, `(rdn >= 15 && rdn < 30)` , `(rdn >= 30 && rdn < 60)` y por ultimo `(rdn >= 60)`. No creo que debas dividir las proporciones, ya que cada vez que ejecutes la función debería devolverte que tipo de pelota se obtuvo según los porcentajes – UselesssCat Oct 12 '17 at 00:53

2 Answers2

6

¡No uses rand()!

Si buscas precisión en las distribuciones de la aleatoriedad, abandona rand(). Utilizar rand() no sólo es inadecuado por ser una utilidad del lenguaje C (siendo tu pregunta sobre C++) si no que también lo es porque el uso que le estás dando falsea la distribución.

Distribución de rand().

La función rand() devuelve un número entero, pseudoaleatorio entre 0 y RAND_MAX (ambos incluídos). La distribución de los valores resultantes de llamar a rand() es uniforme, es decir: todos los números entre 0 y RAND_MAX tienen la misma probabilidad de obtenerse.

Rompes la distribución de rand().

Suponiendo que el intervalo de rand() sea [0, 32767], si el resultado lo operas con módulo (%) sobre un número que no es divisor del valor máximo del intervalo (en tu caso 100) estarás falseando la distribución: dado que el residuo de dividir 32767 entre 100 es 67, los números de 0 a 67 tienen más probabilidades de aparecer que los números 68 a 99.

¿Qué debes hacer?

Como ya he comentado, debes dejar de usar rand(), que además de no ser una utilidad de C++ ¡lo estabas usando mal!. La manera adecuada de aproximar tu problema es usar <random> la librería de números pseudoaleatorios de C++ que permite escoger la distribución de probabilidad (uniforme, Bernoulli, Poisson, normal, discreta, constante, lineal...), el tipo subyacente del valor generado e incluso el algoritmo a usar (minstd, mt19937, ranlux, knuth...).

En tu caso, dado que tus probabilidades se dividen en dos:

  1. ¿He sacado pelota? se cumplirá 1 de cada 20 veces (5%).
  2. Si he sacado pelota ¿de qué color es? (15% dorada, 15% roja, 30% azul y 40% verde).

Para la primera probabilidad, podrías usar una distribución de Bernoulli, que permite configurar la probabilidad de que un suceso (sacar pelota) suceda:

std::random_device device;
std::mt19937 generador(device());
std::bernoulli_distribution distribucion(0.05);

if (distribucion(generador));
{
    // Entramos en este if un 5% de las veces.
}

Para la segunda probabilidad, podrías usar una distribución discreta, que permite distribuir el peso de cada probabilidad (las probabilidades de aparición de cada pelota):

std::random_device device;
std::mt19937 generador(device());
std::discrete_distribution<> distribucion({15, 15, 30, 40});

switch(distribucion(generador))
{
    case 0: std::cout << "Pelota dorada\n"; break;
    case 1: std::cout << "Pelota roja\n"; break;
    case 2: std::cout << "Pelota azul\n"; break;
    case 3: std::cout << "Pelota verde\n"; break;
}

Puedes ver un ejemplo de esto funcionando en Wandbox 三へ( へ՞ਊ ՞)へ ハッハッ.

PaperBirdMaster
  • 44,474
  • 6
  • 44
  • 82
3

Las probabilidades, como comentas, son:

  • 15% dorada
  • 15% roja
  • 30% azul
  • 40% verde

Puedes verificar que la suma da 100.

Así pues lo de dividir el número aleatorio entre 100 es relativamente correcto.

Ahora bien. ¿Cómo repartimos las probabilidades? Muy facil:

enum Color
{
  Indefinido,
  Dorado,
  Rojo,
  Azul,
  Verde
};

for(d=0;d<5;d++){
  Color color = Indefinido;
  rnd=rand() % 100;
  if( rnd < 15 )
    color = Dorado;
  else if( rnd < 30 )
    color = Rojo;
  else if( rnd < 60 )
    color = Azul
  else
    color = Verde;

  // Hacer algo con el color
  // ...
}

¿Cómo funciona? Es sencillo de entender:

  • Si el número es menor de 15 (entre 0 y 14, es decir, 15 valores sobre 100) es Dorada
  • Si el número es mayor de 14 y menor de 30 (por eso el else if), entonces es Roja (15-29 son otros 15 valores sobre 100).
  • El el número es menor de 60 entonces es azul ( Rango 30-59 -> 30 valores)
  • Si no se cumple ninguna de las anteriores, es decir, si el número es igual o mayor a 60 entonces la bola es verde ( Rango 60-99 -> 40 valores ).
eferion
  • 49,291
  • 5
  • 30
  • 72
  • Un problema de `rand() % 100` es que los números [0, 67] son ligeramente más probables que los números [68, 99] (asumiendo un `RAND_MAX` de 32767). – PaperBirdMaster Oct 13 '17 at 07:44
  • @PaperBirdMaster por eso he dicho *relativamente correcto*. En cualquier caso es una diferencia bastante pobre y para aprender hasta asumible... sigo diciendo que aun hay muchos sistemas que no son compatibles con c++11... aunque nos pese es así – eferion Oct 13 '17 at 07:48
  • pues perdiste una oportunidad de hablar de las bondades de la cabecera `` y a la vez hablar de las maldades de la cabecera `` ;P – PaperBirdMaster Oct 13 '17 at 07:51
  • 1
    @PaperBirdMaster no es una oportunidad desperdiciada... imaginaba que ibas a acabar sacando tu ese tema y es una forma de que haya más de una respueta por pregunta – eferion Oct 13 '17 at 07:53