La solución de Mark (la solución aceptada) es casi perfecta.
int x;
do {
x = rand();
} while (x >= (RAND_MAX - RAND_MAX % n));
x %= n;
editado el 25 de marzo de 16 a las 23:16
Mark Amery 39k21170211
Sin embargo, tiene una advertencia que descarta 1 conjunto válido de resultados en cualquier escenario donde RAND_MAX
( RM
) es 1 menor que un múltiplo de N
(Donde N
= el Número de posibles resultados válidos).
es decir, cuando el 'conteo de valores descartados' ( D
) es igual a N
, entonces en realidad son un conjunto válido ( V)
no un conjunto no válido ( I
).
Lo que causa esto es que en algún momento Mark pierde de vista la diferencia entre N
y Rand_Max
.
N
es un conjunto cuyos miembros válidos están compuestos solo por números enteros positivos, ya que contiene un recuento de respuestas que serían válidas. (por ejemplo: Set N
= {1, 2, 3, ... n }
)
Rand_max
Sin embargo, es un conjunto que (como se define para nuestros propósitos) incluye cualquier número de enteros no negativos.
En su forma más genérica, lo que se define aquí Rand Max
es el Conjunto de todos los resultados válidos, que teóricamente podría incluir números negativos o valores no numéricos.
Por Rand_Max
lo tanto, se define mejor como el conjunto de "Posibles respuestas".
Sin embargo, N
opera contra el recuento de los valores dentro del conjunto de respuestas válidas, por lo que incluso según lo definido en nuestro caso específico, Rand_Max
será un valor uno menos que el número total que contiene.
Usando la solución de Mark, los valores se descartan cuando: X => RM - RM% N
EG:
Ran Max Value (RM) = 255
Valid Outcome (N) = 4
When X => 252, Discarded values for X are: 252, 253, 254, 255
So, if Random Value Selected (X) = {252, 253, 254, 255}
Number of discarded Values (I) = RM % N + 1 == N
IE:
I = RM % N + 1
I = 255 % 4 + 1
I = 3 + 1
I = 4
X => ( RM - RM % N )
255 => (255 - 255 % 4)
255 => (255 - 3)
255 => (252)
Discard Returns $True
Como puede ver en el ejemplo anterior, cuando el valor de X (el número aleatorio que obtenemos de la función inicial) es 252, 253, 254 o 255, lo descartaríamos aunque estos cuatro valores comprendan un conjunto válido de valores devueltos .
IE: Cuando el recuento de los valores descartados (I) = N (el número de resultados válidos), la función original descartará un conjunto válido de valores de retorno.
Si describimos la diferencia entre los valores N y RM como D, es decir:
D = (RM - N)
Luego, a medida que el valor de D se vuelve más pequeño, el porcentaje de repeticiones innecesarias debido a este método aumenta en cada multiplicativo natural. (Cuando RAND_MAX NO es igual a un número primo, esto es una preocupación válida)
P.EJ:
RM=255 , N=2 Then: D = 253, Lost percentage = 0.78125%
RM=255 , N=4 Then: D = 251, Lost percentage = 1.5625%
RM=255 , N=8 Then: D = 247, Lost percentage = 3.125%
RM=255 , N=16 Then: D = 239, Lost percentage = 6.25%
RM=255 , N=32 Then: D = 223, Lost percentage = 12.5%
RM=255 , N=64 Then: D = 191, Lost percentage = 25%
RM=255 , N= 128 Then D = 127, Lost percentage = 50%
Dado que el porcentaje de Rerolls necesario aumenta cuanto más se acerca N a RM, esto puede ser una preocupación válida en muchos valores diferentes dependiendo de las restricciones del sistema que ejecuta el código y los valores que se buscan.
Para negar esto, podemos hacer una enmienda simple Como se muestra aquí:
int x;
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) );
x %= n;
Esto proporciona una versión más general de la fórmula que explica las peculiaridades adicionales del uso del módulo para definir sus valores máximos.
Ejemplos de uso de un valor pequeño para RAND_MAX que es un multiplicativo de N.
Versión original de Mark:
RAND_MAX = 3, n = 2, Values in RAND_MAX = 0,1,2,3, Valid Sets = 0,1 and 2,3.
When X >= (RAND_MAX - ( RAND_MAX % n ) )
When X >= 2 the value will be discarded, even though the set is valid.
Versión generalizada 1:
RAND_MAX = 3, n = 2, Values in RAND_MAX = 0,1,2,3, Valid Sets = 0,1 and 2,3.
When X > (RAND_MAX - ( ( RAND_MAX % n ) + 1 ) % n )
When X > 3 the value would be discarded, but this is not a vlue in the set RAND_MAX so there will be no discard.
Además, en el caso donde N debería ser el número de valores en RAND_MAX; en este caso, puede establecer N = RAND_MAX +1, a menos que RAND_MAX = INT_MAX.
En cuanto al bucle, podría usar N = 1, y cualquier valor de X será aceptado, sin embargo, y colocará una declaración IF para su multiplicador final. Pero quizás tenga un código que pueda tener una razón válida para devolver un 1 cuando la función se llama con n = 1 ...
Por lo tanto, puede ser mejor usar 0, que normalmente proporcionaría un error Div 0, cuando desee tener n = RAND_MAX + 1
Versión generalizada 2:
int x;
if n != 0 {
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) );
x %= n;
} else {
x = rand();
}
Ambas soluciones resuelven el problema con resultados válidos descartados innecesariamente que ocurrirán cuando RM + 1 sea un producto de n.
La segunda versión también cubre el escenario de caso límite cuando necesita n para igualar el conjunto total posible de valores contenidos en RAND_MAX.
El enfoque modificado en ambos es el mismo y permite una solución más general a la necesidad de proporcionar números aleatorios válidos y minimizar los valores descartados.
Reiterar:
La solución general básica que amplía el ejemplo de mark:
// Assumes:
// RAND_MAX is a globally defined constant, returned from the environment.
// int n; // User input, or externally defined, number of valid choices.
int x;
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) ) );
x %= n;
La solución general extendida que permite un escenario adicional de RAND_MAX + 1 = n:
// Assumes:
// RAND_MAX is a globally defined constant, returned from the environment.
// int n; // User input, or externally defined, number of valid choices.
int x;
if n != 0 {
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) ) );
x %= n;
} else {
x = rand();
}
En algunos idiomas (particularmente los idiomas interpretados) hacer los cálculos de la operación de comparación fuera de la condición while puede conducir a resultados más rápidos, ya que este es un cálculo único, sin importar cuántas repeticiones se requieran. YMMV!
// Assumes:
// RAND_MAX is a globally defined constant, returned from the environment.
// int n; // User input, or externally defined, number of valid choices.
int x; // Resulting random number
int y; // One-time calculation of the compare value for x
if n != 0 {
y = RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n)
do {
x = rand();
} while (x > y);
x %= n;
} else {
x = rand();
}
RAND_MAX%n == n - 1
_ es(RAND_MAX + 1) % n == 0
. Cuando leo el código, tiendo a entenderlo% something == 0
como "divisible por igual" más fácilmente que otras formas de calcularlo. Por supuesto, si su C ++ stdlib tieneRAND_MAX
el mismo valor queINT_MAX
,(RAND_MAX + 1)
seguramente no funcionaría; entonces el cálculo de Mark sigue siendo la implementación más segura.