Algoritmo teóricamente óptimo
Aquí hay una mejora de la otra respuesta que publiqué. La otra respuesta tiene la ventaja de que es más fácil extenderse al caso más general de generar una distribución discreta a partir de otra. De hecho, la otra respuesta es un caso especial del algoritmo debido a Han y Hoshi.
El algoritmo que describiré aquí se basa en Knuth y Yao (1976). En su artículo, también demostraron que este algoritmo logra el mínimo número esperado posible de lanzamientos de monedas.
Para ilustrarlo, considere el método de muestreo de rechazo descrito por otras respuestas. Como ejemplo, suponga que desea generar uno de 5 números de manera uniforme [0, 4]. La siguiente potencia de 2 es 8, por lo que lanza la moneda 3 veces y genera un número aleatorio hasta 8. Si el número es 0 a 4, entonces lo devuelve. De lo contrario, lo tira y genera otro número hasta el 8 e intenta nuevamente hasta que tenga éxito. Pero cuando arrojas el número, desperdicias algo de entropía. En su lugar, puede condicionar el número que arrojó para reducir el número de futuros lanzamientos de monedas que necesitará con expectativa. Concretamente, una vez que genera el número [0, 7], si es [0, 4], regrese. De lo contrario, son 5, 6 o 7, y haces algo diferente en cada caso. Si es 5, voltea la moneda nuevamente y devuelve 0 o 1 según el lanzamiento. Si son las 6, voltee la moneda y devuelva 2 o 3. Si es 7, voltee la moneda; si es cara, devuelve 4, si es cruz comienza de nuevo.
La entropía sobrante de nuestro intento fallido inicial nos dio 3 casos (5, 6 o 7). Si solo tiramos esto, tiramos los lanzamientos de monedas log2 (3). En cambio, lo guardamos y lo combinamos con el resultado de otro cambio para generar 6 casos posibles (5H, 5T, 6H, 6T, 7H, 7T), lo que nos permite volver a intentarlo de inmediato para generar una respuesta final con probabilidad de éxito 5/6 .
Aquí está el código:
# returns an int from [0, b)
def __gen(b):
rand_num = 0
num_choices = 1
while True:
num_choices *= 2
rand_num *= 2
if coin.flip():
rand_num += 1
if num_choices >= b:
if rand_num < b:
return rand_num
num_choices -= b
rand_num -= b
# returns an int from [a, b)
def gen(a, b):
return a + __gen(b - a)