Expande un rango aleatorio de 1–5 a 1–7


692

Dada una función que produce un entero aleatorio en el rango de 1 a 5, escriba una función que produzca un entero aleatorio en el rango de 1 a 7.

  1. ¿Qué es una solución simple?
  2. ¿Cuál es una solución efectiva para reducir el uso de memoria o ejecutarse en una CPU más lenta?

Demostró ser un problema inesperado interesante, sigo pensando cómo 1) hago en el tiempo fijo y 2) no estropear la distribución uniforme (si había)
eugensk

Tuvimos el mismo problema al elegir un jugador de 5 con un dado. Lanzamos los dados por turnos, uno que obtiene el puntaje máximo es elegido. Se logró la uniformidad, pero no la
constancia de

¿Me rechazarían si publicara una respuesta que diga que el problema no exige que tenga que usar la función dada y simplemente escriba una que devuelva 1-7 al azar?
Doctor Blue

¿Qué hay de 7 * rand5() / 5?
kiwixz

@kiwixz, eso producirá "entre 1 y 7", pero no obtendrá 3 o 6: {1: 19.96, 2: 20.02, 4: 20.01, 5: 19.99, 7: 20.02} porcentajes aproximados que se prueban manualmente. 7 * .2, 7 * .4, 7 * .6, 7 * .8, 7 * 1.
pythonlarry

Respuestas:


572

Esto es equivalente a la solución de Adam Rosenfield, pero puede ser un poco más claro para algunos lectores. Se supone que rand5 () es una función que devuelve un entero estadísticamente aleatorio en el rango de 1 a 5 inclusive.

int rand7()
{
    int vals[5][5] = {
        { 1, 2, 3, 4, 5 },
        { 6, 7, 1, 2, 3 },
        { 4, 5, 6, 7, 1 },
        { 2, 3, 4, 5, 6 },
        { 7, 0, 0, 0, 0 }
    };

    int result = 0;
    while (result == 0)
    {
        int i = rand5();
        int j = rand5();
        result = vals[i-1][j-1];
    }
    return result;
}

¿Como funciona? Piénselo de esta manera: imagine imprimir esta matriz de doble dimensión en papel, pegarla en un tablero de dardos y arrojarle al azar dardos. Si alcanza un valor distinto de cero, es un valor estadísticamente aleatorio entre 1 y 7, ya que hay un número igual de valores distintos de cero para elegir. Si golpeas un cero, sigue tirando el dardo hasta que golpees un no cero. Eso es lo que está haciendo este código: los índices i y j seleccionan aleatoriamente una ubicación en el tablero de dardos, y si no obtenemos un buen resultado, seguimos lanzando dardos.

Como dijo Adam, esto puede durar para siempre en el peor de los casos, pero estadísticamente el peor de los casos nunca sucede. :)


55
Comprendí la lógica detrás de esta solución, pero no puedo comprender que, ¿cómo resulta en una probabilidad uniforme? ¿Alguien puede explicar las matemáticas?
user1071840

66
@ user1071840: si rand5es uniforme, cada celda de la valscuadrícula tiene la misma probabilidad de ser seleccionada. La cuadrícula contiene exactamente tres copias de cada número entero en el intervalo [1, 7], más cuatro ceros. Por lo tanto, la secuencia de resultados "en bruto" tiende a una mezcla uniforme de valores [1, 7], más algunos ceros que ocurren un poco más frecuentemente que cualquier valor individual permitido. Pero eso no importa porque los ceros se eliminan, dejando solo una mezcla uniforme de valores [1, 7].
Daniel Earwicker

3
La forma abreviada de darse cuenta del problema con eso: si solo está llamando a rand5 () una vez, entonces solo tiene 5 resultados posibles. Obviamente, no hay forma de convertir eso en más de 5 resultados posibles sin agregar más aleatoriedad.
Daniel Earwicker

1
La versión más larga: rand5 () solo puede tener los valores (1, 2, 3, 4, 5). Por lo tanto, rand5 () * 5 solo puede tener los valores (5, 10, 15, 20, 25), que no es lo mismo que un rango completo (1 ... 25). Si lo hiciera, restando 4 lo haría (-3 ... 21), pero en este caso se convierte en (1, 6, 11, 16, 21), por lo que los puntos finales son correctos pero hay cuatro grandes agujeros: ( 2..5), (7..10), (12..15), (17..21). Finalmente haces mod 7 y agregas 1, dando (2, 7, 5, 3, 1). Entonces ni 4 ni 6 ocurren nunca. Pero (vea el acceso directo anterior) sabíamos que solo podía haber 5 números en el rango resultante todo el tiempo, por lo que tenía que haber dos huecos.
Daniel Earwicker

1
Ah, porque solo tenemos rand5 (), no rand2 () :-)
gzak

353

No existe una solución (exactamente correcta) que se ejecute en una cantidad de tiempo constante, ya que 1/7 es un decimal infinito en la base 5. Una solución simple sería utilizar un muestreo de rechazo, por ejemplo:


int i;
do
{
  i = 5 * (rand5() - 1) + rand5();  // i is now uniformly random between 1 and 25
} while(i > 21);
// i is now uniformly random between 1 and 21
return i % 7 + 1;  // result is now uniformly random between 1 and 7

Esto tiene un tiempo de ejecución esperado de 25/21 = 1.19 iteraciones del bucle, pero hay una probabilidad infinitamente pequeña de bucle para siempre.


77
el -1 no es necesario si el> 21 se voltea a> 26 b / c, no importa dónde esté el límite inferior de los mapas,
BCS

26
Mi opinión sobre la explicación de por qué esto es correcto: digamos que quiero escribir un programa que genere una secuencia de números aleatorios uniformes del 1 al 25; para eso solo devolvería 5 * (rand5 () - 1) + rand5 () como en el código de la respuesta. Ahora, si quiero construir un flujo de números aleatorios uniformes entre 1 y 21, si solo uso el primer flujo pero lo filtro para que los números en [22, 25] sean rechazados, también puedo construir ese flujo. A continuación, si tomo esta secuencia y la filtro para que para cada elemento x produzca x% 7 + 1, ¡tengo una secuencia de números aleatorios uniformes del 1 al 7! Muy simple, ¿no es así? : D
Paggas 05 de

66
Y tiene razón en que se reduce a si desea una distribución perfecta con un tiempo de ejecución ilimitado en el peor de los casos, o una distribución imperfecta con un tiempo de ejecución limitado. Esto es una consecuencia del hecho de que todas las potencias 5 no son divisibles por 7, o de manera equivalente si tiene 5 ^ n secuencias igualmente probables de longitud n, no hay forma de asignar a cada secuencia un número del 1 al 7 de manera que cada 1..7 es igualmente probable.
Adam Rosenfield

55
@Jules Olléon: Supongamos que hay una solución ejecutándose en tiempo constante que garantiza que no hará más que Nllamadas rand5()en el peor de los casos. Entonces, hay 5 ^ N posibles resultados de la secuencia de llamadas a rand5, cada una de las cuales tiene una salida de 1-7. Entonces, si suma todas las secuencias posibles de llamadas cuya salida es kpara cada 1≤k≤7, entonces la probabilidad de que la salida ksea ​​m / 5 ^ N, donde m es el número de tales secuencias. Entonces, m / 5 ^ N = 1/7, pero no hay posibles soluciones enteras (N, m) a esta ==> contradicción.
Adam Rosenfield

44
@paxdiablo: Estás incorrecto. La posibilidad de que un verdadero RNG genere una secuencia infinita de 5 es exactamente 0, utilizando un razonamiento similar al hecho de que lanzar una moneda un número infinito de veces no garantiza generar un número infinito de caras consecutivas . Esto también significa que la posibilidad de que este código se repita para siempre es exactamente 0 (aunque existe una posibilidad positiva de que se repita para cualquier número arbitrario de iteraciones).
BlueRaja - Danny Pflughoeft

153

Me gustaría agregar otra respuesta, además de mi primera respuesta . Esta respuesta intenta minimizar el número de llamadas a rand5()por llamada rand7(), para maximizar el uso de la aleatoriedad. Es decir, si considera que la aleatoriedad es un recurso valioso, queremos usar la mayor cantidad posible, sin tirar ningún fragmento aleatorio. Esta respuesta también tiene algunas similitudes con la lógica presentada en la respuesta de Ivan .

La entropía de una variable aleatoria es una cantidad bien definida. Para una variable aleatoria que toma N estados con probabilidades iguales (una distribución uniforme), la entropía es log 2 N. Por lo tanto, rand5()tiene aproximadamente 2.32193 bits de entropía y rand7()tiene aproximadamente 2.80735 bits de entropía. Si esperamos maximizar nuestro uso de aleatoriedad, necesitamos usar todos los 2.32193 bits de entropía de cada llamada a rand5(), y aplicarlos para generar 2.80735 bits de entropía necesarios para cada llamada a rand7(). El límite fundamental, entonces, es que no podemos hacer nada mejor que log (7) / log (5) = 1.20906 llamadas a rand5()por llamada a rand7().

Notas al margen: todos los logaritmos en esta respuesta serán de base 2 a menos que se especifique lo contrario. rand5()se supondrá que devuelve números en el rango [0, 4], y rand7()se supondrá que devuelve números en el rango [0, 6]. Ajustar los rangos a [1, 5] y [1, 7] respectivamente es trivial.

Entonces, ¿Cómo lo hacemos? Generamos un número real aleatorio infinitamente preciso entre 0 y 1 (simule por el momento que realmente podríamos calcular y almacenar un número tan infinitamente preciso; lo arreglaremos más adelante). Podemos generar dicho número generando sus dígitos en la base 5: elegimos el número aleatorio 0. a1 a2 a3 ..., donde cada dígito a ies elegido por una llamada a rand5(). Por ejemplo, si nuestro RNG eligió a i= 1 para todos i, ignorando el hecho de que eso no es muy aleatorio, eso correspondería al número real 1/5 + 1/5 2 + 1/5 3 + ... = 1/4 (suma de una serie geométrica).

Bien, entonces hemos elegido un número real aleatorio entre 0 y 1. Ahora afirmo que dicho número aleatorio está distribuido uniformemente. Intuitivamente, esto es fácil de entender, ya que cada dígito se seleccionó de manera uniforme y el número es infinitamente preciso. Sin embargo, una prueba formal de esto es algo más complicada, ya que ahora estamos tratando con una distribución continua en lugar de una distribución discreta, por lo que debemos demostrar que la probabilidad de que nuestro número se encuentre en un intervalo [ a, b] es igual a la longitud de ese intervalo, b - a. La prueba se deja como un ejercicio para el lector =).

Ahora que tenemos un número real aleatorio seleccionado uniformemente del rango [0, 1], necesitamos convertirlo a una serie de números aleatorios uniformes en el rango [0, 6] para generar la salida de rand7(). Cómo hacemos esto? Justo lo contrario de lo que acabamos de hacer: lo convertimos a un decimal infinitamente preciso en base 7, y luego cada dígito de base 7 corresponderá a una salida de rand7().

Tomando el ejemplo de antes, si nuestro rand5()produce un flujo infinito de 1, entonces nuestro número real aleatorio será 1/4. Con la conversión de 1/4 a base 7, obtenemos el decimal infinito 0.15151515 ..., por lo que produciremos como salida 1, 5, 1, 5, 1, 5, etc.

Bien, tenemos la idea principal, pero nos quedan dos problemas: no podemos calcular o almacenar un número real infinitamente preciso, entonces, ¿cómo lidiamos con solo una porción finita? En segundo lugar, ¿cómo lo convertimos realmente a base 7?

Una forma de convertir un número entre 0 y 1 a base 7 es la siguiente:

  1. Multiplicar por 7
  2. La parte integral del resultado es la siguiente base de 7 dígitos
  3. Resta la parte integral, dejando solo la parte fraccional
  4. Ir al paso 1

Para lidiar con el problema de la precisión infinita, calculamos un resultado parcial y también almacenamos un límite superior sobre cuál podría ser el resultado. Es decir, supongamos que llamamos rand5()dos veces y devolvió 1 las dos veces. El número que hemos generado hasta ahora es 0.11 (base 5). Cualquiera que sea el resto de las infinitas series de llamadas que se rand5()producirán, el número real aleatorio que estamos generando nunca será mayor que 0.12: siempre es cierto que 0.11 ≤ 0.11xyz ... <0.12.

Por lo tanto, al realizar un seguimiento del número actual hasta el momento y el valor máximo que podría tomar, convertimos ambos números a la base 7. Si están de acuerdo con los primeros kdígitos, entonces podemos generar con seguridad los siguientes kdígitos, independientemente de lo que ¡flujo infinito de base 5 dígitos son, nunca afectarán los siguientes kdígitos de la representación de base 7!

Y ese es el algoritmo: para generar la siguiente salida de rand7(), generamos solo tantos dígitos rand5()como sea necesario para garantizar que sepamos con certeza el valor del siguiente dígito en la conversión del número real aleatorio a la base 7. Aquí está Una implementación de Python, con un arnés de prueba:

import random

rand5_calls = 0
def rand5():
    global rand5_calls
    rand5_calls += 1
    return random.randint(0, 4)

def rand7_gen():
    state = 0
    pow5 = 1
    pow7 = 7
    while True:
        if state / pow5 == (state + pow7) / pow5:
            result = state / pow5
            state = (state - result * pow5) * 7
            pow7 *= 7
            yield result
        else:
            state = 5 * state + pow7 * rand5()
            pow5 *= 5

if __name__ == '__main__':
    r7 = rand7_gen()
    N = 10000
    x = list(next(r7) for i in range(N))
    distr = [x.count(i) for i in range(7)]
    expmean = N / 7.0
    expstddev = math.sqrt(N * (1.0/7.0) * (6.0/7.0))

    print '%d TRIALS' % N
    print 'Expected mean: %.1f' % expmean
    print 'Expected standard deviation: %.1f' % expstddev
    print
    print 'DISTRIBUTION:'
    for i in range(7):
        print '%d: %d   (%+.3f stddevs)' % (i, distr[i], (distr[i] - expmean) / expstddev)
    print
    print 'Calls to rand5: %d (average of %f per call to rand7)' % (rand5_calls, float(rand5_calls) / N)

Tenga en cuenta que rand7_gen()devuelve un generador, ya que tiene un estado interno que implica la conversión del número a la base 7. El arnés de prueba llama next(r7)10000 veces para producir 10000 números aleatorios, y luego mide su distribución. Solo se usa matemática entera, por lo que los resultados son exactamente correctos.

También tenga en cuenta que los números aquí se vuelven muy grandes, muy rápidos. Las potencias de 5 y 7 crecen rápidamente. Por lo tanto, el rendimiento comenzará a degradarse notablemente después de generar muchos números aleatorios, debido a la aritmética bignum. Pero recuerde aquí, mi objetivo era maximizar el uso de bits aleatorios, no maximizar el rendimiento (aunque ese es un objetivo secundario).

En una corrida de esto, hice 12091 llamadas a rand5()10000 llamadas a rand7(), logrando el mínimo de llamadas log (7) / log (5) en promedio a 4 cifras significativas, y el resultado resultante fue uniforme.

Para portar este código a un idioma que no tenga enteros arbitrariamente grandes incorporados, tendrá que limitar los valores pow5y pow7al valor máximo de su tipo integral nativo; si se vuelven demasiado grandes, reinicie todo y empezar de nuevo. Esto aumentará el número promedio de llamadas rand5()por llamada a rand7()muy ligeramente, pero es de esperar que no aumente demasiado incluso para enteros de 32 o 64 bits.


77
+1 para una respuesta realmente interesante. ¿Sería posible, en lugar de restablecer a un cierto valor, simplemente cambiar los bits que se han utilizado y mover los otros bits hacia arriba, y básicamente solo mantener los bits que se van a utilizar? ¿O me estoy perdiendo algo?
Chris Lutz

1
No estoy 100% seguro, pero creo que si hicieras eso, distorsionarías la distribución muy ligeramente (aunque dudo que tal sesgo sea medible sin billones de ensayos).
Adam Rosenfield

FTW! Traté de hacer los bignums más pequeños pero no se puede hacer porque ninguna potencia de 5 tiene factores en común con una potencia de 7. Además, buen uso de la palabra clave de rendimiento. Muy bien hecho.
Eyal

2
¡Muy agradable! ¿Podemos retener la entropía adicional sin un estado de crecimiento? El truco consiste en notar que los límites superior e inferior son en todo momento números racionales. Podemos sumar, restar y multiplicar estos sin perder precisión. Si lo hacemos todo en base 35, ya casi estamos allí. El resto (multiplicando por siete y reteniendo la parte fraccionaria) se deja como ejercicio.
Ian

@adam Debe referirse a "limitar los valores de pow5 y pow7 al valor máximo de su tipo integral nativo". En segundo lugar, cree que esto sesgará la distribución, al menos si se hace ingenuamente.
catalizador

36

(He robado la respuesta de Adam Rosenfeld y la hice correr un 7% más rápido).

Suponga que rand5 () devuelve uno de {0,1,2,3,4} con igual distribución y el objetivo es devolver {0,1,2,3,4,5,6} con igual distribución.

int rand7() {
  i = 5 * rand5() + rand5();
  max = 25;
  //i is uniform among {0 ... max-1}
  while(i < max%7) {
    //i is uniform among {0 ... (max%7 - 1)}
    i *= 5;
    i += rand5(); //i is uniform {0 ... (((max%7)*5) - 1)}
    max %= 7;
    max *= 5; //once again, i is uniform among {0 ... max-1}
  }
  return(i%7);
}

Estamos realizando un seguimiento del valor más grande que el ciclo puede hacer en la variable max. Si el resultado hasta ahora es entre max% 7 y max-1, el resultado se distribuirá uniformemente en ese rango. Si no, usamos el resto, que es aleatorio entre 0 y max% 7-1, y otra llamada a rand () para hacer un nuevo número y un nuevo max. Entonces comenzamos de nuevo.

Editar: esperar número de veces para llamar a rand5 () es x en esta ecuación:

x =  2     * 21/25
   + 3     *  4/25 * 14/20
   + 4     *  4/25 *  6/20 * 28/30
   + 5     *  4/25 *  6/20 *  2/30 * 7/10
   + 6     *  4/25 *  6/20 *  2/30 * 3/10 * 14/15
   + (6+x) *  4/25 *  6/20 *  2/30 * 3/10 *  1/15
x = about 2.21 calls to rand5()

2
Resultados catalogados en 1,000,000 de intentos: 1 = 47216; 2 = 127444; 3 = 141407; 4 = 221453; 5 = 127479; 6 = 167536; 7 = 167465. Como puede ver, falta distribución con respecto a las probabilidades de obtener un 1.
Robert K

2
@ The Wicked Flea: Creo que te equivocas. ¿Está seguro de que la entrada rand5 () que estaba usando para su prueba produjo 0-4 en lugar de 1-5, como se especifica en esta solución?
Adam Rosenfield

55
agregar números distribuidos uniformemente no da como resultado un número distribuido uniformemente. De hecho, solo necesita sumar 6 variables distribuidas uniformemente para obtener una aproximación razonable a una distribución normal.
Mitch Wheat

2
@MitchWheat: agregar dos enteros distribuidos uniformemente, de hecho, da como resultado un entero aleatorio distribuido uniformemente, siempre que cada suma posible se pueda generar exactamente de una manera. Ese es el caso en la expresión 5 * rand5() + rand5().
Ted Hopp

28

Algoritmo:

7 se puede representar en una secuencia de 3 bits

Use rand (5) para llenar aleatoriamente cada bit con 0 o 1.
Por ejemplo: llame a rand (5) y

si el resultado es 1 o 2, llene el bit con 0
si el resultado es 4 o 5, llene el bit con 1
si el resultado es 3, luego ignórelo y vuelva a hacerlo (rechazo)

De esta manera podemos llenar 3 bits al azar con 0/1 y así obtener un número del 1 al 7.

EDITAR: Esta parece ser la respuesta más simple y eficiente, así que aquí hay un código para ello:

public static int random_7() {
    int returnValue = 0;
    while (returnValue == 0) {
        for (int i = 1; i <= 3; i++) {
            returnValue = (returnValue << 1) + random_5_output_2();
        }
    }
    return returnValue;
}

private static int random_5_output_2() {
    while (true) {
        int flip = random_5();

        if (flip < 3) {
            return 0;
        }
        else if (flip > 3) {
            return 1;
        }
    }
}

1
Siempre existe el débil espectro del problema de detención, ya que un generador de números aleatorios deficiente podría generar muchos tres en algún momento.
Alex North-Keys

"si el resultado es 1 o 2, llene el bit con 0 si el resultado es 4 o 5, llene el bit con 1" ¿Cuál es la lógica por la cual se aceptaron 1,2,4,5 y se rechazó 3? ¿Puede explicar esto?
gkns

@gkns No hay lógica, podría tener 1 y 2 relleno medio con 0 bits y 3 y 4 relleno medio con 1. Lo importante es que cada opción tiene un 50% de posibilidades de ocurrir, garantizando así la aleatoriedad de su función. al menos tan aleatorio como la función original rand (5). ¡Es una gran solución!
Mo Beigi

Esto no es simple ni eficiente. El número de cals a random_5 por random_7 es, en el mejor de los casos, 3 generalmente más. Otras soluciones en esta página están más cerca de la mejor, que es alrededor de 2.2.
Eyal

1
No importa, me perdí la parte "while returnValue == 0"
NicholasFolk

19
int randbit( void )
{
    while( 1 )
    {
        int r = rand5();
        if( r <= 4 ) return(r & 1);
    }
}

int randint( int nbits )
{
    int result = 0;
    while( nbits-- )
    {
        result = (result<<1) | randbit();
    }
    return( result );
}

int rand7( void )
{
    while( 1 )
    {
        int r = randint( 3 ) + 1;
        if( r <= 7 ) return( r );
    }
}

2
Una solución correcta, haciendo un promedio de 30/7 = 4.29 llamadas a rand5 () por llamada a rand7 ().
Adam Rosenfield

17
rand7() = (rand5()+rand5()+rand5()+rand5()+rand5()+rand5()+rand5())%7+1

Editar: Eso no funciona del todo. Está apagado por aproximadamente 2 partes en 1000 (suponiendo un rand5 perfecto). Los cubos obtienen:

value   Count  Error%
1       11158  -0.0035
2       11144  -0.0214
3       11144  -0.0214
4       11158  -0.0035
5       11172  +0.0144
6       11177  +0.0208
7       11172  +0.0144

Al cambiar a una suma de

n   Error%
10  +/- 1e-3,
12  +/- 1e-4,
14  +/- 1e-5,
16  +/- 1e-6,
...
28  +/- 3e-11

parece ganar un orden de magnitud por cada 2 agregados

Por cierto: la tabla de errores anterior no se generó a través del muestreo, sino por la siguiente relación de recurrencia:

p[x,n]es la cantidad de formas en que output=xpueden suceder las nllamadas a rand5.

  p[1,1] ... p[5,1] = 1
  p[6,1] ... p[7,1] = 0

  p[1,n] = p[7,n-1] + p[6,n-1] + p[5,n-1] + p[4,n-1] + p[3,n-1]
  p[2,n] = p[1,n-1] + p[7,n-1] + p[6,n-1] + p[5,n-1] + p[4,n-1]
  p[3,n] = p[2,n-1] + p[1,n-1] + p[7,n-1] + p[6,n-1] + p[5,n-1]
  p[4,n] = p[3,n-1] + p[2,n-1] + p[1,n-1] + p[7,n-1] + p[6,n-1]
  p[5,n] = p[4,n-1] + p[3,n-1] + p[2,n-1] + p[1,n-1] + p[7,n-1]
  p[6,n] = p[5,n-1] + p[4,n-1] + p[3,n-1] + p[2,n-1] + p[1,n-1]
  p[7,n] = p[6,n-1] + p[5,n-1] + p[4,n-1] + p[3,n-1] + p[2,n-1]

8
Esta no es una distribución uniforme. Está muy cerca del uniforme, pero no perfectamente uniforme.
Adam Rosenfield el

Ah! Dados y 7's. Si va a decir que estoy equivocado, no debe dejar la prueba como un ejercicio para el lector.
BCS

45
La prueba de que no es uniforme es simple: hay 5 ^ 7 posibles formas de aleatoriedad, y como 5 ^ 7 no es un múltiplo de 7, no es posible que las 7 sumas sean igualmente probables. (Básicamente, se reduce a 7 siendo relativamente primo a 5, o equivalentemente 1/7 no siendo un decimal final en la base 5.) De hecho, ni siquiera es el "más uniforme" posible bajo esta restricción: el cálculo directo muestra el de 5 ^ 7 = 78125 sumas, el número de veces que obtiene los valores 1 a 7 es {1: 11145, 2: 11120, 3: 11120, 4: 11145, 5: 11190, 6: 11215, 7: 11190}.
ShreevatsaR

@ShreevatsaR Entonces, ¿qué pasaría si en lugar de tomar la suma de rand5 () siete veces, lo hiciéramos 5 * 7, ¿no funcionaría? 35 ^ 7% 7 = 35 ^ 5% 7 = 0.
kba

44
@ KristianAntonsen: cuántas veces haces rand5 (), no obtendrás una distribución uniforme. Si lo haces N veces, hay 5 ^ N salidas posibles, que no es divisible por 7. (Si lo haces 35 veces, hay 5 ^ 35, no 35 ^ 7.) Te acercarás más y más a uniforme la mayor cantidad de llamadas que usa (y puede ser cualquier número, no tiene que ser divisible por 7), pero en mi humilde opinión en lugar de usar una gran cantidad de llamadas a rand (), también puede usar la probabilidad algoritmo en las respuestas principales, que proporciona una distribución uniforme exacta y cuyo número esperado de llamadas a rand () es pequeño.
ShreevatsaR

15
int ans = 0;
while (ans == 0) 
{
     for (int i=0; i<3; i++) 
     {
          while ((r = rand5()) == 3){};
          ans += (r < 3) >> i
     }
}

2
Una solución correcta, haciendo un promedio de 30/7 = 4.29 llamadas a rand5 () por llamada a rand7 ().
Adam Rosenfield

3
Es necesario que haya desviación a la izquierda para el algoritmo de trabajo:ans += (r < 3) << i
woolfie

13

Lo siguiente produce una distribución uniforme en {1, 2, 3, 4, 5, 6, 7} usando un generador de números aleatorios que produce una distribución uniforme en {1, 2, 3, 4, 5}. El código es desordenado, pero la lógica es clara.

public static int random_7(Random rg) {
    int returnValue = 0;
    while (returnValue == 0) {
        for (int i = 1; i <= 3; i++) {
            returnValue = (returnValue << 1) + SimulateFairCoin(rg);
        }
    }
    return returnValue;
}

private static int SimulateFairCoin(Random rg) {
    while (true) {
        int flipOne = random_5_mod_2(rg);
        int flipTwo = random_5_mod_2(rg);

        if (flipOne == 0 && flipTwo == 1) {
            return 0;
        }
        else if (flipOne == 1 && flipTwo == 0) {
            return 1;
        }
    }
}

private static int random_5_mod_2(Random rg) {
    return random_5(rg) % 2;
}

private static int random_5(Random rg) {
    return rg.Next(5) + 1;
}    

2
Una solución correcta (que te pone por delante de la curva), aunque no muy eficiente. Esto hace un promedio de 25/6 = 4.17 llamadas a random_5_mod_2 por lanzamiento de moneda justo, para un promedio total de 100/7 = 14.3 llamadas a random_5 () por llamada a random_7 ().
Adam Rosenfield

La ventaja de esta solución sobre las demás es que se puede expandir fácilmente para producir cualquier otro rango distribuido uniformemente. Simplemente seleccione aleatoriamente cada uno de los bits, volviendo sobre valores no válidos (como el valor 0 en nuestra solución actual que produce 8 números).
DenTheMan

1
posibles bucles infinitos, etc.
robermorales

1
@robermorales: extremadamente improbable.
Jason

13
int rand7() {
    int value = rand5()
              + rand5() * 2
              + rand5() * 3
              + rand5() * 4
              + rand5() * 5
              + rand5() * 6;
    return value%7;
}

A diferencia de la solución elegida, el algoritmo se ejecutará en tiempo constante. Sin embargo, realiza 2 llamadas más a rand5 que el tiempo de ejecución promedio de la solución elegida.

Tenga en cuenta que este generador no es perfecto (el número 0 tiene un 0.0064% más de posibilidades que cualquier otro número), pero para la mayoría de los propósitos prácticos, la garantía de tiempo constante probablemente supere esta inexactitud.

Explicación

Esta solución se deriva del hecho de que el número 15,624 es divisible por 7 y, por lo tanto, si podemos generar de manera aleatoria y uniforme números del 0 al 15,624 y luego tomar mod 7, podemos obtener un generador rand7 casi uniforme. Los números del 0 al 15,624 pueden generarse de manera uniforme haciendo rodar rand5 6 veces y usándolos para formar los dígitos de un número base 5 de la siguiente manera:

rand5 * 5^5 + rand5 * 5^4 + rand5 * 5^3 + rand5 * 5^2 + rand5 * 5 + rand5

Sin embargo, las propiedades del mod 7 nos permiten simplificar un poco la ecuación:

5^5 = 3 mod 7
5^4 = 2 mod 7
5^3 = 6 mod 7
5^2 = 4 mod 7
5^1 = 5 mod 7

Entonces

rand5 * 5^5 + rand5 * 5^4 + rand5 * 5^3 + rand5 * 5^2 + rand5 * 5 + rand5

se convierte

rand5 * 3 + rand5 * 2 + rand5 * 6 + rand5 * 4 + rand5 * 5 + rand5

Teoría

El número 15,624 no se eligió al azar, pero se puede descubrir utilizando el pequeño teorema de fermat, que establece que si p es un número primo, entonces

a^(p-1) = 1 mod p

Entonces esto nos da,

(5^6)-1 = 0 mod 7

(5 ^ 6) -1 es igual a

4 * 5^5 + 4 * 5^4 + 4 * 5^3 + 4 * 5^2 + 4 * 5 + 4

Este es un número en forma de base 5 y, por lo tanto, podemos ver que este método puede usarse para pasar de cualquier generador de números aleatorios a cualquier otro generador de números aleatorios. Aunque siempre se introduce un pequeño sesgo hacia 0 cuando se usa el exponente p-1.

Para generalizar este enfoque y ser más precisos, podemos tener una función como esta:

def getRandomconverted(frm, to):
    s = 0
    for i in range(to):
        s += getRandomUniform(frm)*frm**i
    mx = 0
    for i in range(to):
        mx = (to-1)*frm**i 
    mx = int(mx/to)*to # maximum value till which we can take mod
    if s < mx:
        return s%to
    else:
        return getRandomconverted(frm, to)

1
Este generador es preciso, pero no perfectamente uniforme. Para ver esto, considere el hecho de que un generador uniforme en [0,15624] tiene 15625 resultados posibles, que no es divisible por 7. Esto introduce un sesgo en el número 0 (que tiene una probabilidad 2233/15625, y los otros simplemente 2232/15625). Después de todo, aunque usar el pequeño teorema de Fermat puede parecer correcto a primera vista, dice que (5 ^ 6)% 7 = 1, y no (5 ^ 6)% 7 = 0. Esto último es obviamente imposible para cualquier exponente porque 5 y 7 son números primos. Creo que sigue siendo una solución aceptable, y he editado tu publicación para reflejar esto.
aviador

12

¿Se permiten problemas de tarea aquí?

Esta función realiza matemática cruda "base 5" para generar un número entre 0 y 6.

function rnd7() {
    do {
        r1 = rnd5() - 1;
        do {
            r2=rnd5() - 1;
        } while (r2 > 1);
        result = r2 * 5 + r1;
    } while (result > 6);
    return result + 1;
}

3
Una solución correcta (que te pone por delante de la curva), aunque no muy eficiente. Esto hace un promedio de 5 llamadas a rnd5 () por cada llamada a rnd7 ().
Adam Rosenfield

necesita más explicación por favor
Barry

1
@Barry - Primero, no puedes simplemente sumar dos números aleatorios, no obtienes una solución lineal (considera un par de dados). Ahora considere "Base 5": 00, 01, 02, 03, 04, 10, 11. Ese 0-6 en la base 5. Entonces, simplemente necesitamos generar 2 dígitos del número de la base 5 y sumarlos hasta que consigue uno que esté dentro del rango. Eso es lo que hace el r2 * 5 + r1. El r2> 1 loop está ahí porque nunca querríamos un dígito alto de> 1.
Will Hartung

Esta solución no genera una distribución uniforme. Los números 1 y 7 solo se pueden generar de una manera, pero del 2 al 6 se pueden generar de dos maneras: con r1 igual al número menos 1 y r2 igual a 0 o con r1 igual al número menos 2 y r2 igual a 1. Así, de 2 a 6 se devolverán en promedio el doble de veces que 1 o 7.
Ted Hopp

12

Si consideramos la restricción adicional de tratar de dar la respuesta más eficiente, es decir, una que da un flujo de entrada I, de enteros distribuidos uniformemente de longitud mde 1 a 5 salidas, un flujo Ode enteros distribuidos uniformemente de 1 a 7 de la longitud relativa más larga a m, por ejemplo L(m).

La forma más sencilla de analizar esto es tratar los flujos I y Ocomo números 5-ary y 7-ary respectivamente. Esto se logra mediante la idea de la respuesta principal de tomar la transmisión a1, a2, a3,... -> a1+5*a2+5^2*a3+..y de manera similar para la transmisión O.

Luego, si tomamos una sección de la secuencia de entrada de longitud m choose n s.t. 5^m-7^n=cdonde c>0y es lo más pequeña posible. Luego hay un mapa uniforme de la secuencia de entrada de longitud m a enteros de 1a 5^my otra correspondencia uniforme de enteros de 1 7^na la secuencia de salida de longitud n donde es posible que tengamos que perder algunos casos de la secuencia de entrada cuando el entero asignado supera 7^n.

Entonces esto da un valor L(m)de alrededor de lo m (log5/log7)cual es aproximadamente .82m.

La dificultad con el análisis anterior es la ecuación 5^m-7^n=cque no es fácil de resolver con exactitud y el caso en que el valor uniforme desde 1que 5^mexcede 7^ny perder eficiencia.

La pregunta es qué tan cerca se puede alcanzar el mejor valor posible de m (log5 / log7). Por ejemplo, cuando este número se aproxima a un número entero, ¿podemos encontrar una manera de lograr este número entero exacto de valores de salida?

Si 5^m-7^n=ca continuación de la corriente de entrada que generamos efectivamente un número aleatorio uniforme de 0a (5^m)-1y no utilizamos cualquier valor más alto que 7^n. Sin embargo, estos valores pueden ser rescatados y utilizados nuevamente. Generan efectivamente una secuencia uniforme de números del 1 al 5^m-7^n. Entonces podemos intentar usarlos y convertirlos en números de 7 arios para poder crear más valores de salida.

Si dejamos T7(X)que sea la longitud promedio de la secuencia de salida de random(1-7)enteros derivados de una entrada uniforme de tamaño X, y suponiendo eso 5^m=7^n0+7^n1+7^n2+...+7^nr+s, s<7.

Entonces, T7(5^m)=n0x7^n0/5^m + ((5^m-7^n0)/5^m) T7(5^m-7^n0)dado que tenemos una longitud sin secuencia con probabilidad 7 ^ n0 / 5 ^ m con un residual de longitud 5^m-7^n0con probabilidad (5^m-7^n0)/5^m).

Si seguimos sustituyendo obtenemos:

T7(5^m) = n0x7^n0/5^m + n1x7^n1/5^m + ... + nrx7^nr/5^m  = (n0x7^n0 + n1x7^n1 + ... + nrx7^nr)/5^m

Por lo tanto

L(m)=T7(5^m)=(n0x7^n0 + n1x7^n1 + ... + nrx7^nr)/(7^n0+7^n1+7^n2+...+7^nr+s)

Otra forma de decir esto es:

If 5^m has 7-ary representation `a0+a1*7 + a2*7^2 + a3*7^3+...+ar*7^r
Then L(m) = (a1*7 + 2a2*7^2 + 3a3*7^3+...+rar*7^r)/(a0+a1*7 + a2*7^2 + a3*7^3+...+ar*7^r)

El mejor caso posible es mi original sobre dónde 5^m=7^n+s, dónde s<7.

Entonces T7(5^m) = nx(7^n)/(7^n+s) = n+o(1) = m (Log5/Log7)+o(1)como antes.

El peor de los casos es cuando solo podemos encontrar k y st 5 ^ m = kx7 + s.

Then T7(5^m) = 1x(k.7)/(k.7+s) = 1+o(1)

Otros casos están en algún punto intermedio. Sería interesante ver qué tan bien podemos hacerlo para m muy grande, es decir, qué tan bueno podemos obtener el término de error:

T7(5^m) = m (Log5/Log7)+e(m)

Parece imposible de lograr e(m) = o(1)en general, pero esperamos poder demostrarlo e(m)=o(m).

Todo se basa en la distribución de los dígitos de 7 arios de 5^mvarios valores de m.

Estoy seguro de que hay mucha teoría por ahí que cubre esto, puedo echar un vistazo e informar en algún momento.


+2 (si pudiera): esta fue la única buena respuesta (en lugar de simplemente adecuada). Tienes la segunda mejor respuesta que cabe en enteros de 32 bits.
Rex Kerr

10

Aquí hay una implementación de Python en funcionamiento de la respuesta de Adam .

import random

def rand5():
    return random.randint(1, 5)

def rand7():
    while True:
        r = 5 * (rand5() - 1) + rand5()
        #r is now uniformly random between 1 and 25
        if (r <= 21):
            break
    #result is now uniformly random between 1 and 7
    return r % 7 + 1

Me gusta lanzar algoritmos que estoy viendo en Python para poder jugar con ellos, pensé en publicarlo aquí con la esperanza de que sea útil para alguien allá afuera, no es que me tomó mucho tiempo armarlo.


No, eso es bastante diferente de mi respuesta. Estás dando vueltas 21 veces y descartando los resultados de las primeras 20 iteraciones. También está usando un rand4 () y un rand5 () como entrada, lo que obviamente rompe las reglas de usar solo rand5 (). Finalmente, produce una distribución no uniforme.
Adam Rosenfield

Lo siento por eso. Estaba bastante cansado cuando revisé esta pregunta, lo suficientemente cansado como para haber leído completamente su algoritmo. De hecho, lo tiré a Python porque no podía entender por qué estabas dando vueltas 21 veces. Tiene mucho mas sentido ahora. Hice la cosa random.randint (1, 4) como una taquigrafía, pero supongo que estás en lo correcto, va en contra del espíritu de la pregunta. He corregido el código.
James McMahon

@robermorales - Como Adam Rosenfeld explicó en su respuesta , cada solución que ofrezca una distribución uniforme verdadera en [1, 7] implicará algún tipo de bucle de aceptación-rechazo que es potencialmente infinito. (Sin embargo, si rand5()es un PRNG decente, entonces el ciclo no será infinito porque finalmente 5*(rand5() - 1) + rand5()será <= 21.)
Ted Hopp

10

¿Por qué no hacerlo simple?

int random7() {
  return random5() + (random5() % 3);
}

Las posibilidades de obtener 1 y 7 en esta solución son menores debido al módulo, sin embargo, si solo desea una solución rápida y legible, este es el camino a seguir.


13
Esto no produce una distribución uniforme. Esto produce los números 0-6 con probabilidades 2/25, 4/25, 5/25, 5/25, 5/25, 3/25, 1/25, como se puede verificar contando los 25 resultados posibles.
Adam Rosenfield

8

Suponiendo que rand (n) aquí significa "entero aleatorio en una distribución uniforme de 0 a n-1 ", aquí hay una muestra de código usando randint de Python, que tiene ese efecto. Utiliza solo randint (5) y constantes para producir el efecto de randint (7) . Un poco tonto, en realidad

from random import randint
sum = 7
while sum >= 7:
    first = randint(0,5)   
    toadd = 9999
    while toadd>1:
        toadd = randint(0,5)
    if toadd:
        sum = first+5
    else:
        sum = first

assert 7>sum>=0 
print sum

1
@robermorales Porque Python no tiene do ... while. Podría haber sido 1337, o 12345, o cualquier número> 1.
tckmn

8

La premisa detrás de la respuesta correcta de Adam Rosenfield es:

  • x = 5 ^ n (en su caso: n = 2)
  • manipule n llamadas rand5 para obtener un número y dentro del rango [1, x]
  • z = ((int) (x / 7)) * 7
  • si y> z, intente nuevamente. más retorno y% 7 + 1

Cuando n es igual a 2, tiene 4 posibilidades de descarte: y = {22, 23, 24, 25}. Si usa n es igual a 6, solo tiene 1 descarte: y = {15625}.

5 ^ 6 = 15625
7 * 2232 = 15624

Llamas a rand5 más veces. Sin embargo, tiene muchas menos posibilidades de obtener un valor de descarte (o un bucle infinito). Si hay una manera de no obtener un valor de descarte posible para y, todavía no lo he encontrado.


1
Probablemente no haya ningún caso sin valores de descarte: si no hubiera descarte, 5 ^ ny 7 ^ m tendrían un factor en común. Pero son (poderes de) primos, por lo que no lo hacen.
Rex Kerr

8

Aquí está mi respuesta:

static struct rand_buffer {
  unsigned v, count;
} buf2, buf3;

void push (struct rand_buffer *buf, unsigned n, unsigned v)
{
  buf->v = buf->v * n + v;
  ++buf->count;
}

#define PUSH(n, v)  push (&buf##n, n, v)

int rand16 (void)
{
  int v = buf2.v & 0xf;
  buf2.v >>= 4;
  buf2.count -= 4;
  return v;
}

int rand9 (void)
{
  int v = buf3.v % 9;
  buf3.v /= 9;
  buf3.count -= 2;
  return v;
}

int rand7 (void)
{
  if (buf3.count >= 2) {
    int v = rand9 ();

    if (v < 7)
      return v % 7 + 1;

    PUSH (2, v - 7);
  }

  for (;;) {
    if (buf2.count >= 4) {
      int v = rand16 ();

      if (v < 14) {
        PUSH (2, v / 7);
        return v % 7 + 1;
      }

      PUSH (2, v - 14);
    }

    // Get a number between 0 & 25
    int v = 5 * (rand5 () - 1) + rand5 () - 1;

    if (v < 21) {
      PUSH (3, v / 7);
      return v % 7 + 1;
    }

    v -= 21;
    PUSH (2, v & 1);
    PUSH (2, v >> 1);
  }
}

Es un poco más complicado que otros, pero creo que minimiza las llamadas a rand5. Al igual que con otras soluciones, existe una pequeña probabilidad de que se pueda repetir durante mucho tiempo.


Esto produce una distribución no muy diferente de las otras soluciones, pero tiene la desventaja adicional de ser innecesariamente compleja. También sufre de la posibilidad indebidamente probada de bucle no determinista para siempre si los números son verdaderamente aleatorios. Todavía creo que los que producen una distribución ligeramente menos uniforme (aunque todavía mucho más que adecuada) pero que garantizan un comportamiento determinista son mejores.
paxdiablo

@Pax: Por favor, explícame cómo esto produce una distribución no uniforme. Mi análisis del código, así como mis propias pruebas, indican que esto produce una distribución uniforme. Como hemos discutido anteriormente, es imposible producir una distribución perfectamente uniforme y tener un límite superior de tiempo constante garantizado del tiempo de ejecución.
Adam Rosenfield


6

Mientras no queden siete posibilidades para elegir, dibuje otro número aleatorio, que multiplique el número de posibilidades por cinco. En perl:

$num = 0;
$possibilities = 1;

sub rand7
{
  while( $possibilities < 7 )
  {
    $num = $num * 5 + int(rand(5));
    $possibilities *= 5;
  }
  my $result = $num % 7;
  $num = int( $num / 7 );
  $possibilities /= 7;
  return $result;
}

su distribución no es uniforme, al menos en la primera llamada. De hecho, $possibilitiessiempre tiene que crecer hasta 25 para salir del bucle y volver. Entonces, su primer resultado es [0-124] % 7, que no se distribuye uniformemente porque 125 % 7 != 0(esto es 6, en realidad).
bernard paulus

6

No me gustan los rangos que comienzan desde 1, así que comenzaré desde 0 :-)

unsigned rand5()
{
    return rand() % 5;
}

unsigned rand7()
{
    int r;

    do
    {
        r =         rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
    } while (r > 15623);

    return r / 2232;
}

Este es un ganador. Esto produce los 7 resultados con la misma probabilidad. from collections import defaultdict def r7(n): if not n: yield [] else: for i in range(1, 6): for j in r7(n-1): yield [i] + j def test_r7(): d = defaultdict(int) for x in r7(6): s = (((((((((x[5] * 5) + x[4]) * 5) + x[3]) * 5) + x[2]) * 5) + x[1]) * 5) + x[0] if s <= 15623: d[s % 7] += 1 print d
hughdbrown

5

Ahí tienes, distribución uniforme y cero llamadas rand5.

def rand7:
    seed += 1
    if seed >= 7:
        seed = 0
    yield seed

Necesidad de establecer semillas de antemano.


5

Sé que ha sido respondido, pero parece que esto funciona bien, pero no puedo decirte si tiene un sesgo. Mi 'prueba' sugiere que es, al menos, razonable.

¿Quizás Adam Rosenfield sería tan amable de comentar?

Mi idea (ingenua) es esta:

Acumula rand5's hasta que haya suficientes bits aleatorios para hacer un rand7. Esto toma como máximo 2 rand5's. Para obtener el número rand7, uso el valor acumulado mod 7.

Para evitar que el acumulador se desborde, y dado que el acumulador es mod 7, entonces tomo el mod 7 del acumulador:

(5a + rand5) % 7 = (k*7 + (5a%7) + rand5) % 7 = ( (5a%7) + rand5) % 7

La función rand7 () sigue:

(Dejé que el rango de rand5 sea 0-4 y rand7 también sea 0-6).

int rand7(){
  static int    a=0;
  static int    e=0;
  int       r;
  a = a * 5 + rand5();
  e = e + 5;        // added 5/7ths of a rand7 number
  if ( e<7 ){
    a = a * 5 + rand5();
    e = e + 5;  // another 5/7ths
  }
  r = a % 7;
  e = e - 7;        // removed a rand7 number
  a = a % 7;
  return r;
}

Editar: Resultados agregados para 100 millones de ensayos.

Funciones rand 'reales' mod 5 o 7

rand5: avg = 1.999802 0: 20003944 1: 19999889 2: 20003690 3: 19996938 4: 19995539 rand7: avg = 3.000111 0: 14282851 1: 14282879 2: 14284554 3: 14288546 4: 14292388 5: 14288736 6: 14280046

Mi rand7

El promedio se ve bien y las distribuciones de números también se ven bien.

randt: promedio = 3.000080 0: 14288793 1: 14280135 2: 14287848 3: 14285277 4: 14286341 5: 14278663 6: 14292943


Probablemente deberías mirar la correlación secuencial. Creo que si toma pares sucesivos (cada número "aleatorio" emparejado con su predecesor) puede encontrar cosas sorprendentes. En cualquier caso, no ha explicado por qué debería mantener la distribución uniforme. Un programa de trabajo normalmente debería comenzar con una explicación de por qué funciona.
Ian

¿Se aplicaría la correlación secuencial a muchas de estas soluciones?
philcolbourn

¿Se aplicaría la correlación secuencial a muchas de estas soluciones? Ha pasado un tiempo desde que intenté esto y pensé que lo había explicado. Mirándolo ahora, parece que estoy acumulando bits aleatorios en un grupo de rand5, asegurando que se haya acumulado suficiente antes de retirar lo suficiente para hacer un número rand7 y asegurando que no se desborde mi acumulador.
philcolbourn

4

Hay algoritmos elegantes citados anteriormente, pero aquí hay una forma de abordarlo, aunque podría ser indirecto. Asumo valores generados a partir de 0.

R2 = generador de números aleatorios que proporciona valores inferiores a 2 (espacio muestral = {0, 1})
R8 = generador de números aleatorios que proporciona valores inferiores a 8 (espacio muestral = {0, 1, 2, 3, 4, 5, 6, 7 })

Para generar R8 a partir de R2, ejecutará R2 tres veces y utilizará el resultado combinado de las 3 ejecuciones como un número binario con 3 dígitos. Aquí está el rango de valores cuando R2 se ejecuta tres veces:

0 0 0 -> 0
.
.
1 1 1 -> 7

Ahora para generar R7 a partir de R8, simplemente ejecutamos R7 nuevamente si devuelve 7:

int R7() {
  do {
    x = R8();
  } while (x > 6)
  return x;
}

La solución indirecta es generar R2 a partir de R5 (al igual que generamos R7 a partir de R8), luego R8 a partir de R2 y luego R7 a partir de R8.


Al igual que muchos otros, este enfoque podría tomar un tiempo arbitrariamente largo por llamada R7, ya que podría obtener una larga cadena de sietes de R8.
Alex North-Keys

4

Aquí hay una solución que se ajusta completamente a enteros y está dentro de aproximadamente el 4% de lo óptimo (es decir, usa 1.26 números aleatorios en {0..4} para cada uno en {0..6}). El código está en Scala, pero las matemáticas deben ser razonablemente claras en cualquier idioma: aprovecha el hecho de que 7 ^ 9 + 7 ^ 8 está muy cerca de 5 ^ 11. Por lo tanto, elige un número de 11 dígitos en la base 5 y luego lo interpreta como un número de 9 dígitos en la base 7 si está dentro del rango (dando 9 números de base 7), o como un número de 8 dígitos si está por encima del número de 9 dígitos, etc. .:

abstract class RNG {
  def apply(): Int
}

class Random5 extends RNG {
  val rng = new scala.util.Random
  var count = 0
  def apply() = { count += 1 ; rng.nextInt(5) }
}

class FiveSevener(five: RNG) {
  val sevens = new Array[Int](9)
  var nsevens = 0
  val to9 = 40353607;
  val to8 = 5764801;
  val to7 = 823543;
  def loadSevens(value: Int, count: Int) {
    nsevens = 0;
    var remaining = value;
    while (nsevens < count) {
      sevens(nsevens) = remaining % 7
      remaining /= 7
      nsevens += 1
    }
  }
  def loadSevens {
    var fivepow11 = 0;
    var i=0
    while (i<11) { i+=1 ; fivepow11 = five() + fivepow11*5 }
    if (fivepow11 < to9) { loadSevens(fivepow11 , 9) ; return }
    fivepow11 -= to9
    if (fivepow11 < to8) { loadSevens(fivepow11 , 8) ; return }
    fivepow11 -= to8
    if (fivepow11 < 3*to7) loadSevens(fivepow11 % to7 , 7)
    else loadSevens
  }
  def apply() = {
    if (nsevens==0) loadSevens
    nsevens -= 1
    sevens(nsevens)
  }
}

Si pega una prueba en el intérprete (REPL en realidad), obtiene:

scala> val five = new Random5
five: Random5 = Random5@e9c592

scala> val seven = new FiveSevener(five)
seven: FiveSevener = FiveSevener@143c423

scala> val counts = new Array[Int](7)
counts: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0)

scala> var i=0 ; while (i < 100000000) { counts( seven() ) += 1 ; i += 1 }
i: Int = 100000000

scala> counts
res0: Array[Int] = Array(14280662, 14293012, 14281286, 14284836, 14287188,
14289332, 14283684)

scala> five.count
res1: Int = 125902876

La distribución es agradable y plana (dentro de aproximadamente 10k de 1/7 de 10 ^ 8 en cada contenedor, como se esperaba de una distribución aproximadamente gaussiana).


3

Al usar un total rodante , ambos pueden

  • mantener una distribución equitativa; y
  • No tiene que sacrificar ningún elemento en la secuencia aleatoria.

Ambos problemas son un problema con las rand(5)+rand(5)...soluciones de tipo simplista . El siguiente código de Python muestra cómo implementarlo (la mayor parte de esto es probar la distribución).

import random
x = []
for i in range (0,7):
    x.append (0)
t = 0
tt = 0
for i in range (0,700000):
    ########################################
    #####            qq.py             #####
    r = int (random.random () * 5)
    t = (t + r) % 7
    ########################################
    #####       qq_notsogood.py        #####
    #r = 20
    #while r > 6:
        #r =     int (random.random () * 5)
        #r = r + int (random.random () * 5)
    #t = r
    ########################################
    x[t] = x[t] + 1
    tt = tt + 1
high = x[0]
low = x[0]
for i in range (0,7):
    print "%d: %7d %.5f" % (i, x[i], 100.0 * x[i] / tt)
    if x[i] < low:
        low = x[i]
    if x[i] > high:
        high = x[i]
diff = high - low
print "Variation = %d (%.5f%%)" % (diff, 100.0 * diff / tt)

Y esta salida muestra los resultados:

pax$ python qq.py
0:   99908 14.27257
1:  100029 14.28986
2:  100327 14.33243
3:  100395 14.34214
4:   99104 14.15771
5:   99829 14.26129
6:  100408 14.34400
Variation = 1304 (0.18629%)

pax$ python qq.py
0:   99547 14.22100
1:  100229 14.31843
2:  100078 14.29686
3:   99451 14.20729
4:  100284 14.32629
5:  100038 14.29114
6:  100373 14.33900
Variation = 922 (0.13171%)

pax$ python qq.py
0:  100481 14.35443
1:   99188 14.16971
2:  100284 14.32629
3:  100222 14.31743
4:   99960 14.28000
5:   99426 14.20371
6:  100439 14.34843
Variation = 1293 (0.18471%)

Una simplista rand(5)+rand(5), ignorando aquellos casos en los que esto devuelve más de 6 tiene una variación típica del 18%, 100 veces la del método que se muestra arriba:

pax$ python qq_notsogood.py
0:   31756 4.53657
1:   63304 9.04343
2:   95507 13.64386
3:  127825 18.26071
4:  158851 22.69300
5:  127567 18.22386
6:   95190 13.59857
Variation = 127095 (18.15643%)

pax$ python qq_notsogood.py
0:   31792 4.54171
1:   63637 9.09100
2:   95641 13.66300
3:  127627 18.23243
4:  158751 22.67871
5:  126782 18.11171
6:   95770 13.68143
Variation = 126959 (18.13700%)

pax$ python qq_notsogood.py
0:   31955 4.56500
1:   63485 9.06929
2:   94849 13.54986
3:  127737 18.24814
4:  159687 22.81243
5:  127391 18.19871
6:   94896 13.55657
Variation = 127732 (18.24743%)

Y, siguiendo los consejos de Nixuz, he limpiado el script para que pueda extraer y usar las rand7...cosas:

import random

# rand5() returns 0 through 4 inclusive.

def rand5():
    return int (random.random () * 5)

# rand7() generator returns 0 through 6 inclusive (using rand5()).

def rand7():
    rand7ret = 0
    while True:
        rand7ret = (rand7ret + rand5()) % 7
        yield rand7ret

# Number of test runs.

count = 700000

# Work out distribution.

distrib = [0,0,0,0,0,0,0]
rgen =rand7()
for i in range (0,count):
    r = rgen.next()
    distrib[r] = distrib[r] + 1

# Print distributions and calculate variation.

high = distrib[0]
low = distrib[0]
for i in range (0,7):
    print "%d: %7d %.5f" % (i, distrib[i], 100.0 * distrib[i] / count)
    if distrib[i] < low:
        low = distrib[i]
    if distrib[i] > high:
        high = distrib[i]
diff = high - low
print "Variation = %d (%.5f%%)" % (diff, 100.0 * diff / count)

2
Err, déjame reformular eso. Dado que se produjo una x particular en algún momento de la secuencia, solo se pueden producir 5 de 7 números para el siguiente número de la secuencia. Un verdadero RNG haría que todas las muestras fueran independientes entre sí, pero en este caso claramente no lo son.
Adam Rosenfield

3
Es cierto que la pregunta original no especifica si las funciones de entrada y salida producen muestras independientes e idénticamente distribuidas (iid), pero creo que es una expectativa razonable que si la entrada rand5 () es iid, entonces la salida rand7 () También debe ser iid. Si no crees que eso sea razonable, diviértete usando tu RNG no iid.
Adam Rosenfield

1
Entonces, ¿cuál es la palabra de los matemáticos en la universidad?
Adam Rosenfield

1
Esta solución está claramente rota. Es obvio que necesita llamar a rand5 (en promedio) más de una vez por llamada a rand7 y esta solución no lo hace. Por lo tanto, los resultados no pueden ser aleatorios por ninguna definición sensata de aleatorio.
Chris Suter

1
@Pax En cada iteración de su función, solo puede devolver uno de los cinco valores diferentes (aunque en el rango 0-6). La primera iteración solo puede devolver un número en el rango 0-4. Por lo tanto, debe quedar claro que si bien su función puede tener una distribución uniforme, las muestras no son independientes, es decir, están correlacionadas, lo que no es algo que desee en un generador de números aleatorios.
Chris Suter

3

Esta respuesta es más un experimento para obtener la mayor entropía posible de la función Rand5. Por lo tanto, no está claro y es casi seguro que es mucho más lento que otras implementaciones.

Suponiendo la distribución uniforme de 0-4 y la distribución uniforme resultante de 0-6:

public class SevenFromFive
{
  public SevenFromFive()
  {
    // this outputs a uniform ditribution but for some reason including it 
    // screws up the output distribution
    // open question Why?
    this.fifth = new ProbabilityCondensor(5, b => {});
    this.eigth = new ProbabilityCondensor(8, AddEntropy);
  } 

  private static Random r = new Random();
  private static uint Rand5()
  {
    return (uint)r.Next(0,5);
  }

  private class ProbabilityCondensor
  {
    private readonly int samples;
    private int counter;
    private int store;
    private readonly Action<bool> output;

    public ProbabilityCondensor(int chanceOfTrueReciprocal,
      Action<bool> output)
    {
      this.output = output;
      this.samples = chanceOfTrueReciprocal - 1;  
    }

    public void Add(bool bit)
    {
      this.counter++;
      if (bit)
        this.store++;   
      if (counter == samples)
      {
        bool? e;
        if (store == 0)
          e = false;
        else if (store == 1)
          e = true;
        else
          e = null;// discard for now       
        counter = 0;
        store = 0;
        if (e.HasValue)
          output(e.Value);
      }
    }
  }

  ulong buffer = 0;
  const ulong Mask = 7UL;
  int bitsAvail = 0;
  private readonly ProbabilityCondensor fifth;
  private readonly ProbabilityCondensor eigth;

  private void AddEntropy(bool bit)
  {
    buffer <<= 1;
    if (bit)
      buffer |= 1;      
    bitsAvail++;
  }

  private void AddTwoBitsEntropy(uint u)
  {
    buffer <<= 2;
    buffer |= (u & 3UL);    
    bitsAvail += 2;
  }

  public uint Rand7()
  {
    uint selection;   
    do
    {
      while (bitsAvail < 3)
      {
        var x = Rand5();
        if (x < 4)
        {
          // put the two low order bits straight in
          AddTwoBitsEntropy(x);
          fifth.Add(false);
        }
        else
        { 
          fifth.Add(true);
        }
      }
      // read 3 bits
      selection = (uint)((buffer & Mask));
      bitsAvail -= 3;     
      buffer >>= 3;
      if (selection == 7)
        eigth.Add(true);
      else
        eigth.Add(false);
    }
    while (selection == 7);   
    return selection;
  }
}

El número de bits agregados al búfer por llamada a Rand5 es actualmente 4/5 * 2, entonces 1.6. Si se incluye el valor de probabilidad de 1/5 que aumenta en 0.05, entonces 1.65, pero vea el comentario en el código donde he tenido que desactivar esto.

Bits consumidos por llamada a Rand7 = 3 + 1/8 * (3 + 1/8 * (3 + 1/8 * (...
Esto es 3 + 3/8 + 3/64 + 3/512 ... entonces aproximadamente 3,42

Al extraer información de los sietes, reclamo 1/8 * 1/7 bits por llamada, por lo que aproximadamente 0.018

Esto proporciona un consumo neto de 3,4 bits por llamada, lo que significa que la proporción es de 2.125 llamadas a Rand5 por cada Rand7. El óptimo debe ser 2.1.

Me imagino que este enfoque es significativamente más lento que muchos de los otros aquí a menos que el costo de la llamada a Rand5 sea extremadamente costoso (por ejemplo, llamar a alguna fuente externa de entropía).


Su solución parece correcta, aparte de algunos errores simples: "if (count> 1)" debería ser "if (count <= 1)", y el "i ++" que ocurre poco después debe estar dentro de las llaves que lo preceden. No estoy seguro de si BitsSet () es correcto, pero eso es algo irrelevante.
Adam Rosenfield

En general, sin embargo, su función es muy difícil de entender. Hace un uso ligeramente mejor de la entropía de lo que podría hacerlo, a costa de una mayor complicación. Tampoco hay razón para llenar inicialmente el búfer con 35 bits aleatorios en la primera llamada, cuando 3 serían suficientes.
Adam Rosenfield

Corregí el <= gracias, aunque el i ++ realmente debería estar allí. Debería suceder en el caso cero y 1 (agregando un 1 o un cero respectivamente al búfer). Esto no es absolutamente lo que sugeriría usar, es terriblemente complicado. Me interesaba lo cerca que podía llegar a los límites teóricos de entropía inherentes al problema ... Gracias por los comentarios. Irónicamente, el llenado del búfer en la primera llamada fue para que sea más fácil de escribir :)
ShuggyCoUk

Reformé esto para que fuera más fácil de entender (a costa de la velocidad) pero también lo hice correcto. Todavía no es óptimo, por alguna razón los 1/5 bits causan problemas a pesar de que son uniformes en conteo.
ShuggyCoUk

3

en php

function rand1to7() {
    do {
        $output_value = 0;
        for ($i = 0; $i < 28; $i++) {
            $output_value += rand1to5();
        }
    while ($output_value != 140);
    $output_value -= 12;
    return floor($output_value / 16);
}

realiza un bucle para producir un número aleatorio entre 16 y 127, se divide por dieciséis para crear un flotante entre 1 y 7.9375, luego se redondea hacia abajo para obtener un int entre 1 y 7. Si no me equivoco, hay una probabilidad de 16/112 de obtener cualquiera de los 7 resultados.


aunque es probable que haya una respuesta más fácil similar a esta usando ningún bucle condicional y módulo en lugar de piso. Simplemente no puedo descifrar los números en este momento.
dqhendricks 01 de

3
extern int r5();

int r7() {
    return ((r5() & 0x01) << 2 ) | ((r5() & 0x01) << 1 ) | (r5() & 0x01);
}

problema: esto devuelve de manera no uniforme en el rango 0-7, no 0-6. De hecho, puede tener 7 = 111bconp(7) = 8 / 125
bernard paulus

3

Creo que tengo cuatro respuestas, dos que dan soluciones exactas como la de @Adam Rosenfield pero sin el problema del bucle infinito, y otras dos con una solución casi perfecta pero una implementación más rápida que la primera.

La mejor solución exacta requiere 7 llamadas a rand5 , pero procedamos para entender.

Método 1 - Exacto

La fortaleza de la respuesta de Adam es que proporciona una distribución uniforme perfecta, y hay una probabilidad muy alta (21/25) de que solo se necesiten dos llamadas a rand5 (). Sin embargo, el peor de los casos es el bucle infinito.

La primera solución a continuación también ofrece una distribución uniforme perfecta, pero requiere un total de 42 llamadas a rand5 . No hay bucles infinitos.

Aquí hay una implementación de R:

rand5 <- function() sample(1:5,1)

rand7 <- function()  (sum(sapply(0:6, function(i) i + rand5() + rand5()*2 + rand5()*3 + rand5()*4 + rand5()*5 + rand5()*6)) %% 7) + 1

Para las personas que no están familiarizadas con R, aquí hay una versión simplificada:

rand7 = function(){
  r = 0 
  for(i in 0:6){
    r = r + i + rand5() + rand5()*2 + rand5()*3 + rand5()*4 + rand5()*5 + rand5()*6
  }
  return r %% 7 + 1
}

La distribución de rand5será preservada. Si hacemos los cálculos, cada una de las 7 iteraciones del ciclo tiene 5 ^ 6 combinaciones posibles, por lo tanto, el número total de combinaciones posibles es (7 * 5^6) %% 7 = 0. Por lo tanto, podemos dividir los números aleatorios generados en grupos iguales de 7. Vea el método dos para más discusión sobre esto.

Aquí están todas las combinaciones posibles:

table(apply(expand.grid(c(outer(1:5,0:6,"+")),(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6),1,sum) %% 7 + 1)

    1     2     3     4     5     6     7 
15625 15625 15625 15625 15625 15625 15625 

Creo que es sencillo demostrar que el método de Adam se ejecutará mucho más rápido. La probabilidad de que haya 42 o más llamadas rand5en la solución de Adam es muy pequeña ((4/25)^21 ~ 10^(-17) ).

Método 2: no exacto

Ahora el segundo método, que es casi uniforme, pero requiere 6 llamadas a rand5:

rand7 <- function() (sum(sapply(1:6,function(i) i*rand5())) %% 7) + 1

Aquí hay una versión simplificada:

rand7 = function(){
  r = 0 
  for(i in 1:6){
    r = r + i*rand5()
  }
  return r %% 7 + 1
}

Esto es esencialmente una iteración del método 1. Si generamos todas las combinaciones posibles, aquí están los conteos resultantes:

table(apply(expand.grid(1:5,(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6),1,sum) %% 7 + 1)

   1    2    3    4    5    6    7 
2233 2232 2232 2232 2232 2232 2232

Un número aparecerá una vez más en las 5^6 = 15625pruebas.

Ahora, en el Método 1, al sumar 1 a 6, movemos el número 2233 a cada uno de los puntos sucesivos. Por lo tanto, el número total de combinaciones coincidirá. Esto funciona porque 5 ^ 6 %% 7 = 1, y luego hacemos 7 variaciones apropiadas, entonces (7 * 5 ^ 6 %% 7 = 0).

Método 3 - Exacto

Si se entiende el argumento de los métodos 1 y 2, sigue el método 3 y solo requiere 7 llamadas a rand5. En este punto, siento que esta es la cantidad mínima de llamadas necesarias para una solución exacta.

Aquí hay una implementación de R:

rand5 <- function() sample(1:5,1)

rand7 <- function()  (sum(sapply(1:7, function(i) i * rand5())) %% 7) + 1

Para las personas que no están familiarizadas con R, aquí hay una versión simplificada:

rand7 = function(){
  r = 0 
  for(i in 1:7){
    r = r + i * rand5()
  }
  return r %% 7 + 1
}

La distribución de rand5será preservada. Si hacemos los cálculos, cada una de las 7 iteraciones del ciclo tiene 5 resultados posibles, por lo tanto, el número total de combinaciones posibles es(7 * 5) %% 7 = 0 . Por lo tanto, podemos dividir los números aleatorios generados en grupos iguales de 7. Vea el método uno y dos para más discusión sobre esto.

Aquí están todas las combinaciones posibles:

table(apply(expand.grid(0:6,(1:5)),1,sum) %% 7 + 1)

1 2 3 4 5 6 7  
5 5 5 5 5 5 5 

Creo que es sencillo demostrar que el método de Adam seguirá funcionando más rápido. La probabilidad de que haya 7 o más llamadas rand5en la solución de Adam sigue siendo pequeña ( (4/25)^3 ~ 0.004).

Método 4: no exacto

Esta es una variación menor del segundo método. Es casi uniforme, pero requiere 7 llamadas a rand5, que es una adicional al método 2:

rand7 <- function() (rand5() + sum(sapply(1:6,function(i) i*rand5())) %% 7) + 1

Aquí hay una versión simplificada:

rand7 = function(){
  r = 0 
  for(i in 1:6){
    r = r + i*rand5()
  }
  return (r+rand5()) %% 7 + 1
}

Si generamos todas las combinaciones posibles, aquí están los conteos resultantes:

table(apply(expand.grid(1:5,(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6,1:5),1,sum) %% 7 + 1)

    1     2     3     4     5     6     7 
11160 11161 11161 11161 11161 11161 11160

Dos números aparecerán una vez menos en las 5^7 = 78125pruebas. Para la mayoría de los propósitos, puedo vivir con eso.


1
No estoy familiarizado con R, pero a menos que no entienda cómo funcionan, entonces el método 1 no es exacto. Tiene (5 ^ 6) ^ 7 = 5 ^ 42 posibles resultados, no (5 ^ 6) * 7; 5 ^ 42 no es divisible por 7. Del mismo modo, el método 3 no es exacto. Tiene 5 ^ 7 resultados posibles, no 5 * 7. (La última iteración del bucle en el método 3 i=7tampoco tiene efecto, ya que agregar 7*rand5()a rno cambia el valor del rmod 7.)
Adam Rosenfield

2

La función que necesita es rand1_7 () , escribí rand1_5 () para que pueda probarla y trazarla.

import numpy
def rand1_5():
    return numpy.random.randint(5)+1

def rand1_7():
    q = 0
    for i in xrange(7):  q+= rand1_5()
    return q%7 + 1
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.