Algoritmo teoricamente ideal
Aqui está uma melhoria da outra resposta que eu postei. A outra resposta tem a vantagem de ser mais fácil estender ao caso mais geral de gerar uma distribuição discreta a partir de outra. De fato, a outra resposta é um caso especial do algoritmo devido a Han e Hoshi.
O algoritmo que descreverei aqui é baseado em Knuth e Yao (1976). Em seu artigo, eles também provaram que esse algoritmo atinge o número mínimo possível de lançamentos de moedas.
Para ilustrá-lo, considere o método de amostragem Rejeição descrito por outras respostas. Como exemplo, suponha que você queira gerar um de 5 números de maneira uniforme [0, 4]. A próxima potência de 2 é 8, para que você jogue a moeda 3 vezes e gere um número aleatório até 8. Se o número for de 0 a 4, você o retornará. Caso contrário, você o joga fora e gera outro número até 8 e tenta novamente até conseguir. Mas quando você joga fora o número, acaba de desperdiçar um pouco de entropia. Em vez disso, você pode condicionar o número que jogou para reduzir o número de lançamentos futuros de moedas que você precisa em expectativa. Concretamente, depois de gerar o número [0, 7], se for [0, 4], retorne. Caso contrário, são 5, 6 ou 7 e você fará algo diferente em cada caso. Se for 5, jogue a moeda novamente e retorne 0 ou 1 com base no flip. Se é 6, jogue a moeda e retorne 2 ou 3. Se for 7, jogue a moeda; se for cara, retorne 4, se a coroa começar de novo.
A entropia restante de nossa tentativa inicial fracassada nos deu três casos (5, 6 ou 7). Se jogarmos isso fora, jogamos fora o log2 (3) lançamentos de moedas. Em vez disso, mantemos e combinamos com o resultado de outro flip para gerar 6 casos possíveis (5H, 5T, 6H, 6T, 7H, 7T), os quais vamos imediatamente tentar novamente para gerar uma resposta final com probabilidade de sucesso 5/6 .
Aqui está o 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)