Algoritmo teoricamente ottimale
Ecco un miglioramento dell'altra risposta che ho pubblicato. L'altra risposta ha il vantaggio che è più facile estendere al caso più generale di generare una distribuzione discreta da un'altra. In effetti, l'altra risposta è un caso speciale dell'algoritmo dovuto a Han e Hoshi.
L'algoritmo che descriverò qui si basa su Knuth e Yao (1976). Nel loro documento, hanno anche dimostrato che questo algoritmo raggiunge il numero minimo possibile di lanci di monete.
Per illustrarlo, considera il metodo di campionamento del rifiuto descritto da altre risposte. Ad esempio, supponiamo di voler generare uno dei 5 numeri in modo uniforme [0, 4]. La potenza successiva di 2 è 8, quindi lanci la moneta 3 volte e generi un numero casuale fino a 8. Se il numero è compreso tra 0 e 4, la restituisci. Altrimenti, lo butti fuori e generi un altro numero fino a 8 e riprova fino a quando non ci riesci. Ma quando butti il numero, hai appena sprecato dell'entropia. Puoi invece condizionare il numero che hai lanciato per ridurre il numero di lanci di monete futuri di cui avrai bisogno in attesa. Concretamente, una volta generato il numero [0, 7], se è [0, 4], restituire. Altrimenti, è 5, 6 o 7 e fai qualcosa di diverso in ogni caso. Se è 5, lancia nuovamente la moneta e restituisci 0 o 1 in base al lancio. Se è 6, lancia la moneta e restituisci 2 o 3. Se è 7, lancia la moneta; se è testa, restituisce 4, se è la coda ricominciare.
L'entropia rimanente del nostro tentativo iniziale fallito ci ha dato 3 casi (5, 6 o 7). Se lo buttiamo fuori, buttiamo via i lanci di monete log2 (3). Invece lo manteniamo e lo combiniamo con il risultato di un altro capovolgimento per generare 6 possibili casi (5H, 5T, 6H, 6T, 7H, 7T) che proviamo immediatamente a generare nuovamente una risposta finale con probabilità di successo 5/6 .
Ecco il codice:
# 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)