Espandi un intervallo casuale da 1–5 a 1–7


692

Data una funzione che produce un numero intero casuale nell'intervallo da 1 a 5, scrivere una funzione che produce un numero intero casuale nell'intervallo da 1 a 7.

  1. Che cos'è una soluzione semplice?
  2. Qual è una soluzione efficace per ridurre l'utilizzo della memoria o eseguire su una CPU più lenta?

Si è rivelato un problema inaspettatamente interessante, penso ancora come 1) farlo a tempo fisso e 2) non rovinare la distribuzione uniforme (se c'era)
eugensk,

Abbiamo avuto il problema simile scegliendo un giocatore su 5 con un dado. Abbiamo lanciato i dadi a turno, viene scelto chi ottiene il punteggio massimo. L'uniformità è stata raggiunta, ma non la costanza del tempo :)
eugensk,

Vorrei ottenere il downgrade se avessi pubblicato una risposta dicendo che il problema non impone che tu debba usare la funzione data e scriverne una che restituisca 1-7 in modo casuale?
Dottor Blue

Che dire 7 * rand5() / 5?
Kiwixz,

@kiwixz, che produrrà "tra 1 e 7", ma non otterrai 3 o 6: {1: 19.96, 2: 20.02, 4: 20.01, 5: 19.99, 7: 20.02} test approssimativi manuali. 7 * .2, 7 * .4, 7 * .6, 7 * .8, 7 * 1.
pythonlarry,

Risposte:


572

Ciò equivale alla soluzione di Adam Rosenfield, ma potrebbe essere un po 'più chiaro per alcuni lettori. Presuppone che rand5 () sia una funzione che restituisce un numero intero statisticamente casuale nell'intervallo da 1 a 5 incluso.

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;
}

Come funziona? Pensala in questo modo: immagina di stampare su carta questo array a doppia dimensione, fissandolo a un bersaglio per le freccette e lanciandogli casualmente le freccette. Se colpisci un valore diverso da zero, è un valore statisticamente casuale compreso tra 1 e 7, poiché esiste un numero uguale di valori diversi da zero tra cui scegliere. Se colpisci uno zero, continua a lanciare il dardo finché non colpisci un zero. Ecco cosa sta facendo questo codice: gli indici iej selezionano casualmente una posizione sul bersaglio per le freccette e, se non otteniamo un buon risultato, continuiamo a lanciare freccette.

Come ha detto Adam, questo può durare per sempre nel caso peggiore, ma statisticamente il caso peggiore non si verifica mai. :)


5
Ho capito la logica alla base di questa soluzione, ma non riesco a capire come si traduca in probabilità uniforme? Qualcuno può spiegare la matematica?
user1071840

6
@ user1071840 - se rand5è uniforme, ogni cella della valsgriglia ha la stessa probabilità di essere selezionata. La griglia contiene esattamente tre copie di ciascun numero intero nell'intervallo [1, 7], più quattro zero. Quindi il flusso "grezzo" di risultati tende a una miscela uniforme di [1, 7] valori, più alcuni zeri che si verificano un po 'più frequentemente rispetto a qualsiasi singolo valore consentito. Ma non importa perché gli zeri vengono eliminati, lasciando solo una miscela uniforme di valori [1, 7].
Daniel Earwicker,

3
Il modo scorciatoia per capire il problema con questo: se stai chiamando rand5 () una sola volta, allora hai solo 5 possibili esiti. Ovviamente non c'è modo di trasformarlo in più di 5 possibili esiti senza aggiungere più casualità.
Daniel Earwicker,

1
La versione più lunga: rand5 () può avere solo i valori (1, 2, 3, 4, 5). Pertanto rand5 () * 5 può avere solo i valori (5, 10, 15, 20, 25), che non corrisponde a un intervallo completo (1 ... 25). Se lo facesse, sottraendo 4 lo farebbe (-3 ... 21), ma in questo caso diventa (1, 6, 11, 16, 21), quindi i punti finali sono corretti ma ci sono quattro grandi buchi: ( 2..5), (7..10), (12..15), (17..21). Infine fai mod 7 e aggiungi 1, dando (2, 7, 5, 3, 1). Quindi non si verificano mai né 4 né 6. Ma (vedi scorciatoia sopra) sapevamo che ci potevano essere solo 5 numeri nell'intervallo risultante, quindi dovevano esserci due lacune.
Daniel Earwicker,

1
Ah, perché abbiamo solo rand5 (), non rand2 () :-)
gzak,

352

Non esiste una soluzione (esattamente corretta) che verrà eseguita per un periodo di tempo costante, poiché 1/7 è un decimale infinito in base 5. Una soluzione semplice sarebbe quella di utilizzare il campionamento del rifiuto, ad esempio:


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

Questo ha un tempo di esecuzione previsto di 25/21 = 1,19 iterazioni del loop, ma esiste una probabilità infinitamente piccola di loop per sempre.


7
il -1 non è necessario se il> 21 è capovolto a> 26 b / c, non importa dove sono le mappe con il limite inferiore,
BCS

26
Il mio punto di vista spiega perché questo è corretto: supponiamo che io voglia scrivere un programma che emetta un flusso di numeri casuali uniformi da 1 a 25; per questo vorrei solo restituire 5 * (rand5 () - 1) + rand5 () come nel codice nella risposta. Ora, se voglio costruire un flusso di numeri casuali uniformi tra 1 e 21, se uso solo il primo flusso ma lo filtro in modo che i numeri in [22, 25] vengano rifiutati, posso anche costruire quel flusso. Quindi, se prendo questo flusso e lo filtro in modo che per ogni elemento x I output x% 7 + 1, ho un flusso di numeri casuali uniformi da 1 a 7! Abbastanza semplice, no? : D
Paggas,

6
E hai ragione che si riduce a se desideri una distribuzione perfetta con runtime nel caso peggiore illimitato o una distribuzione imperfetta con un runtime limitato. Questa è una conseguenza del fatto che tutti i poteri 5 non sono divisibili per 7, o equivalentemente se hai 5 ^ n ugualmente probabilmente sequenze di lunghezza n, non c'è modo di assegnare a ciascuna sequenza un numero da 1 a 7 tale che ciascuno di 1..7 è altrettanto probabile.
Adam Rosenfield,

5
@Jules Olléon: Supponiamo che ci fosse una soluzione in esecuzione in tempo costante che non avrebbe potuto fare altro che Nchiamate rand5()nel caso peggiore. Quindi, ci sono 5 ^ N possibili esiti della sequenza di chiamate a rand5, ciascuno dei quali ha un output di 1-7. Quindi, se ksommi tutte le possibili sequenze di chiamate il cui output è per ogni 1≤k≤7, allora la probabilità che l'output ksia m / 5 ^ N, dove m è il numero di tali sequenze. Quindi, m / 5 ^ N = 1/7, ma non ci sono possibili soluzioni intere (N, m) a questa contraddizione ==>.
Adam Rosenfield,

4
@paxdiablo: non sei corretto. La possibilità che un vero RNG generi una sequenza infinita di 5 è esattamente 0, usando un ragionamento simile al fatto che lanciare una moneta un numero infinito di volte è garantito per non generare un numero infinito di teste consecutive . Ciò significa anche che la possibilità che questo codice esegua il ciclo per sempre è esattamente 0 (anche se esiste una possibilità positiva che esegua il ciclo per qualsiasi numero arbitrario di iterazioni).
BlueRaja - Danny Pflughoeft

153

Vorrei aggiungere un'altra risposta, oltre alla mia prima risposta . Questa risposta tenta di ridurre al minimo il numero di chiamate rand5()per chiamata rand7(), per massimizzare l'utilizzo della casualità. Cioè, se consideri la casualità come una risorsa preziosa, vogliamo usarne il più possibile, senza buttare via pezzi casuali. Questa risposta ha anche alcune somiglianze con la logica presentata nella risposta di Ivan .

L' entropia di una variabile casuale è una quantità ben definita. Per una variabile casuale che assume N stati con pari probabilità (una distribuzione uniforme), l'entropia è log 2 N. Pertanto, rand5()ha circa 2.32193 bit di entropia e rand7()circa 2.80735 bit di entropia. Se speriamo di massimizzare il nostro uso della casualità, dobbiamo usare tutti i 2.32193 bit di entropia da ogni chiamata a rand5()e applicarli per generare 2.80735 bit di entropia necessari per ogni chiamata a rand7(). Il limite fondamentale, quindi, è che non possiamo fare di meglio che log (7) / log (5) = 1.20906 chiamate rand5()per chiamata a rand7().

Note a margine: tutti i logaritmi in questa risposta saranno base 2 se non diversamente specificato. rand5()si presume che restituiscano numeri nell'intervallo [0, 4] e rand7()che si presume restituisca numeri nell'intervallo [0, 6]. Regolare gli intervalli su [1, 5] e [1, 7] rispettivamente è banale.

Quindi come lo facciamo? Generiamo un numero reale casuale infinitamente preciso tra 0 e 1 (fingiamo per il momento che potremmo effettivamente calcolare e memorizzare un numero così infinitamente preciso - lo aggiusteremo in seguito). Possiamo generare un tale numero generando le sue cifre nella base 5: scegliamo il numero casuale 0. a1 a2 a3 ..., dove ogni cifra a iviene scelta da una chiamata a rand5(). Ad esempio, se il nostro RNG scegliesse a i= 1 per tutti i, ignorando il fatto che non è molto casuale, corrisponderebbe al numero reale 1/5 + 1/5 2 + 1/5 3 + ... = 1/4 (somma di una serie geometrica).

Ok, quindi abbiamo scelto un numero reale casuale compreso tra 0 e 1. Ora sostengo che tale numero casuale sia distribuito uniformemente. Intuitivamente, questo è facile da capire, poiché ogni cifra è stata selezionata in modo uniforme e il numero è infinitamente preciso. Tuttavia, una dimostrazione formale di questo è un po 'più implicata, poiché ora abbiamo a che fare con una distribuzione continua anziché una distribuzione discreta, quindi dobbiamo dimostrare che la probabilità che il nostro numero si trovi in ​​un intervallo [ a, b] è uguale alla lunghezza di tale intervallo, b - a. La prova è lasciata come esercizio per il lettore =).

Ora che abbiamo un numero reale casuale selezionato uniformemente nell'intervallo [0, 1], dobbiamo convertirlo in una serie di numeri uniformemente casuali nell'intervallo [0, 6] per generare l'output di rand7() . Come facciamo questo? Proprio il contrario di quello che abbiamo appena fatto: lo convertiamo in un decimale infinitamente preciso in base 7, e quindi ogni cifra in base 7 corrisponderà a un'uscita di rand7().

Prendendo l'esempio da prima, se nostro rand5() produce un flusso infinito di 1, il nostro numero reale casuale sarà 1/4. Convertendo 1/4 in base 7, otteniamo il decimale infinito 0,15151515 ..., quindi produrremo come output 1, 5, 1, 5, 1, 5, ecc.

Ok, quindi abbiamo l'idea principale, ma ci sono ancora due problemi: non possiamo effettivamente calcolare o memorizzare un numero reale infinitamente preciso, quindi come possiamo gestirne solo una parte finita? In secondo luogo, come possiamo effettivamente convertirlo in base 7?

Un modo in cui possiamo convertire un numero compreso tra 0 e 1 in base 7 è il seguente:

  1. Moltiplicare per 7
  2. La parte integrante del risultato è la successiva cifra di 7 cifre
  3. Sottrarre la parte integrale, lasciando solo la parte frazionaria
  4. Vai al passaggio 1

Per affrontare il problema della precisione infinita, calcoliamo un risultato parziale e memorizziamo anche un limite superiore su quale potrebbe essere il risultato. Cioè, supponiamo che abbiamo chiamato rand5()due volte e che sia tornato 1 entrambe le volte. Il numero che abbiamo generato finora è 0,11 (base 5). Qualunque sia il resto della serie infinita di chiamate arand5() produrre, il numero reale casuale che stiamo generando non sarà mai maggiore di 0,12: è sempre vero che 0,11 ≤ 0,11xyz ... <0,12.

Quindi, tenendo traccia del numero corrente finora e del valore massimo che potrebbe mai assumere, convertiamo entrambi i numeri in base 7. Se sono d'accordo sulle prime kcifre, allora possiamo produrre in sicurezza le kcifre successive , indipendentemente da quale sia il il flusso infinito di cifre di base 5 sono, non influenzeranno mai le kcifre successive della rappresentazione di base 7!

E questo è l'algoritmo: per generare il prossimo output di rand7(), generiamo solo tutte le cifre di rand5()cui abbiamo bisogno per assicurarci di conoscere con certezza il valore della prossima cifra nella conversione del numero reale casuale in base 7. Ecco un'implementazione di Python, con un cablaggio di test:

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)

Si noti che rand7_gen()restituisce un generatore, poiché ha uno stato interno che comporta la conversione del numero in base 7. Il cablaggio di test chiama next(r7)10000 volte per produrre 10000 numeri casuali, quindi misura la loro distribuzione. Viene utilizzata solo la matematica dei numeri interi, quindi i risultati sono esattamente corretti.

Si noti inoltre che i numeri qui diventano molto grandi, molto veloci. I poteri di 5 e 7 crescono rapidamente. Quindi, le prestazioni inizieranno a peggiorare notevolmente dopo aver generato molti numeri casuali, a causa dell'aritmetica del bignum. Ma ricorda qui, il mio obiettivo era quello di massimizzare l'uso di bit casuali, non di massimizzare le prestazioni (anche se questo è un obiettivo secondario).

In una sequenza di questo, ho effettuato 12091 chiamate a rand5()10000 chiamate a rand7(), ottenendo il minimo di log (7) / log (5) chiamate in media a 4 cifre significative e l'output risultante era uniforme.

Per eseguire il porting di questo codice in una lingua che non ha numeri interi arbitrariamente grandi integrati, dovrai limitare i valori pow5e pow7il valore massimo del tuo tipo integrale nativo - se diventano troppo grandi, quindi resetta tutto e ricominciare. Ciò aumenterà il numero medio di chiamate arand5()rand7() leggermente per chiamata , ma si spera che non aumenti troppo anche per numeri interi a 32 o 64 bit.


7
+1 per una risposta davvero interessante. Sarebbe possibile, piuttosto che resettare ad un certo valore, semplicemente spostare i bit che sono stati usati e spostare gli altri bit verso l'alto, e sostanzialmente mantenere solo i bit che verranno utilizzati? Oppure mi sfugge qualcosa?
Chris Lutz,

1
Non sono sicuro al 100%, ma credo che se lo facessi, inclineresti la distribuzione in modo così lieve (anche se dubito che tale inclinazione sarebbe misurabile senza trilioni di prove).
Adam Rosenfield,

FTW! Ho provato a rimpicciolire i bignum ma non è possibile perché nessuna potenza di 5 ha fattori in comune con una potenza di 7! Inoltre, buon uso della parola chiave yield. Molto ben fatto.
Eyal,

2
Molto bella! Possiamo conservare l'entropia extra senza stato crescente? Il trucco è notare che entrambi i limiti superiore e inferiore sono sempre numeri razionali. Possiamo aggiungere, sottrarre e moltiplicarli senza perdere precisione. Se facciamo tutto nella base 35, ci siamo quasi. Il resto (moltiplicando per sette e mantenendo la parte frazionaria) viene lasciato come esercizio.
Ian,

@adam Devi fare riferimento a "cap i valori di pow5 e pow7 sul valore massimo del tuo tipo integrale nativo". In secondo luogo, tu credi che questo distorcerà la distribuzione, almeno se fatto in modo ingenuo.
catalizzatore

36

(Ho rubato la risposta di Adam Rosenfeld e l'ho fatta correre circa il 7% più veloce.)

Supponiamo che rand5 () restituisca uno di {0,1,2,3,4} con uguale distribuzione e l'obiettivo è di ritorno {0,1,2,3,4,5,6} con uguale distribuzione.

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);
}

Stiamo tenendo traccia del valore più grande che il ciclo può fare nella variabile max . Se il risultato finora è compreso tra max% 7 e max-1, il risultato sarà uniformemente distribuito in quell'intervallo. In caso contrario, utilizziamo il resto, che è casuale tra 0 e max% 7-1 e un'altra chiamata a rand () per creare un nuovo numero e un nuovo max. Quindi ricominciamo.

Modifica: il numero di volte in cui si chiama rand5 () è x in questa equazione:

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
Risultati catalogati in 1.000.000 di tentativi: 1 = 47216; 2 = 127444; 3 = 141407; 4 = 221.453; 5 = 127.479; 6 = 167.536; 7 = 167465. Come puoi vedere, la distribuzione è carente rispetto alle probabilità di ottenere un 1.
Robert K

2
@The Wicked Flea: penso che ti sbagli. Sei sicuro che l'input rand5 () che stavi utilizzando per il tuo test abbia prodotto 0-4 anziché 1-5, come specificato in questa soluzione?
Adam Rosenfield,

5
l'aggiunta di numeri distribuiti uniformemente non comporta un numero distribuito uniformemente. In effetti, devi solo sommare 6 variabili così uniformemente distribuite per ottenere una ragionevole approssimazione a una distribuzione normale.
Mitch Wheat,

2
@MitchWheat - L'aggiunta di due numeri interi distribuiti uniformemente, di fatto, si traduce in un numero intero casuale uniformemente distribuito, purché ogni possibile somma possa essere generata esattamente in un modo. Questo sembra essere il caso nell'espressione 5 * rand5() + rand5().
Ted Hopp,

28

Algoritmo:

7 possono essere rappresentati in una sequenza di 3 bit

Utilizzare rand (5) per riempire casualmente ogni bit con 0 o 1.
Ad esempio: call rand (5) e

se il risultato è 1 o 2, riempire il bit con 0
se il risultato è 4 o 5, riempire il bit con 1
se il risultato è 3, quindi ignorare e ripetere l'operazione (rifiuto)

In questo modo possiamo riempire 3 bit casualmente con 0/1 e quindi ottenere un numero da 1-7.

EDIT: questa sembra la risposta più semplice ed efficiente, quindi ecco un po 'di codice:

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
C'è sempre il debole spettro del problema di arresto, poiché un povero generatore di numeri casuali potrebbe generare molti tre a un certo punto.
Alex North-Keys,

"se il risultato è 1 o 2, riempire il bit con 0 se il risultato è 4 o 5, riempire il bit con 1" Qual è la logica con cui sono stati accettati 1,2,4,5 e 3 è stato rifiutato? Puoi spiegarlo?
gkns l'

@gkns Non c'è logica, potresti avere 1 e 2 riempimento medio con 0 bit e 3 e 4 riempimento medio con 1. L'importante è che ogni opzione abbia il 50% di possibilità di verificarsi, garantendo così che la casualità della tua funzione sia almeno casuale come la funzione originale di rand (5). È un'ottima soluzione!
Mo Beigi,

Questo non è né semplice né efficiente. Il numero di cal a random_5 per random_7 è al massimo 3 di solito di più. Altre soluzioni in questa pagina sono più vicine al meglio che è circa 2.2.
Eyal,

1
Non importa, ho perso 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 soluzione corretta, che effettua in media 30/7 = 4,29 chiamate a rand5 () per chiamata a rand7 ().
Adam Rosenfield,

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

Modifica: non funziona del tutto. È spento di circa 2 parti su 1000 (supponendo un perfetto rand5). I secchi ottengono:

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

Passando a una somma di

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

sembra ottenere un ordine di grandezza per ogni 2 aggiunti

A proposito: la tabella degli errori sopra non è stata generata tramite campionamento ma dalla seguente relazione di ricorrenza:

p[x,n]è il numero di modi in cui output=xpossono accadere determinate nchiamate 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
Questa non è una distribuzione uniforme. È molto vicino all'uniforme, ma non perfettamente uniforme.
Adam Rosenfield,

Ah! Dadi e 7's. Se hai intenzione di dire che ho torto, non dovresti lasciare la prova come esercizio per il lettore.
BCS

45
La prova che non è uniforme è semplice: ci sono 5 ^ 7 possibili modi in cui la casualità può andare, e poiché 5 ^ 7 non è un multiplo di 7, non è possibile che tutte e 7 le somme siano ugualmente probabili. (Fondamentalmente, si riduce a 7 essendo relativamente primo a 5, o equivalentemente 1/7 non essendo un decimale finale nella base 5.) In realtà non è nemmeno il "più uniforme" possibile sotto questo vincolo: il calcolo diretto mostra quello del 5 ^ 7 = 78125 somme, il numero di volte che ottieni valori da 1 a 7 è {1: 11145, 2: 11120, 3: 11120, 4: 11145, 5: 11190, 6: 11215, 7: 11190}.
ShreevatsaR,

@ShreevatsaR E se invece di prendere la somma di rand5 () sette volte, facessimo 5 * 7, non funzionerebbe? 35 ^ 7% 7 = 35 ^ 5% 7 = 0.
kba

4
@KristianAntonsen: quante volte fai rand5 (), non otterrai una distribuzione uniforme. Se lo fai N volte, ci sono 5 ^ N possibili uscite, che non è divisibile per 7. (Se lo fai 35 volte, ci sono 5 ^ 35, non 35 ^ 7). Ti avvicinerai sempre di più a uniformare il maggior numero di chiamate che usi (e può essere qualsiasi numero, non deve essere divisibile per 7), ma IMHO invece di usare un numero molto elevato di chiamate a rand (), puoi anche usare il probabilistico algoritmo nelle risposte migliori, che fornisce una distribuzione uniforme esatta e il cui numero previsto di chiamate a rand () è piccolo.
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 soluzione corretta, che effettua in media 30/7 = 4,29 chiamate a rand5 () per chiamata a rand7 ().
Adam Rosenfield,

3
Deve essere lasciato il turno per far funzionare l'algoritmo:ans += (r < 3) << i
woolfie

13

Quanto segue produce una distribuzione uniforme su {1, 2, 3, 4, 5, 6, 7} utilizzando un generatore di numeri casuali che produce una distribuzione uniforme su {1, 2, 3, 4, 5}. Il codice è disordinato, ma la logica è chiara.

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 soluzione corretta (che ti porta molto avanti rispetto alla curva), sebbene non molto efficiente. Ciò fa una media di 25/6 = 4,17 chiamate a random_5_mod_2 per il lancio di una moneta corretta, per una media totale di 100/7 = 14,3 chiamate a random_5 () per chiamata a random_7 ().
Adam Rosenfield,

Il vantaggio di questa soluzione rispetto alle altre è che può essere facilmente espanso per produrre qualsiasi altra gamma uniformemente distribuita. Basta selezionare casualmente ciascuno dei bit, eseguendo il roll-over su valori non validi (come il valore 0 nella nostra soluzione corrente che produce 8 numeri).
DenTheMan il

1
possibili cicli infiniti, ecc.
robermorales,

1
@robermorales: estremamente improbabile.
Jason,

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

A differenza della soluzione scelta, l'algoritmo verrà eseguito in tempo costante. Effettua tuttavia 2 chiamate in più a rand5 rispetto al tempo medio di esecuzione della soluzione scelta.

Si noti che questo generatore non è perfetto (il numero 0 ha una probabilità dello 0,0064% in più rispetto a qualsiasi altro numero), ma per la maggior parte degli scopi pratici la garanzia del tempo costante probabilmente supera questa inesattezza.

Spiegazione

Questa soluzione deriva dal fatto che il numero 15.624 è divisibile per 7 e quindi se possiamo generare casualmente e uniformemente numeri da 0 a 15.624 e quindi prendere il mod 7 possiamo ottenere un generatore rand7 quasi uniforme. I numeri da 0 a 15.624 possono essere generati uniformemente ruotando rand5 6 volte e utilizzandoli per formare le cifre di un numero di base 5 come segue:

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

Le proprietà di mod 7 ci consentono tuttavia di semplificare un po 'l'equazione:

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

Così

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

diventa

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

Teoria

Il numero 15.624 non è stato scelto a caso, ma può essere scoperto usando il piccolo teorema di fermat, che afferma che se p è un numero primo allora

a^(p-1) = 1 mod p

Quindi questo ci dà

(5^6)-1 = 0 mod 7

(5 ^ 6) -1 è uguale a

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

Questo è un numero in forma base 5 e quindi possiamo vedere che questo metodo può essere usato per passare da qualsiasi generatore di numeri casuali a qualsiasi altro generatore di numeri casuali. Anche se viene sempre introdotto un piccolo orientamento verso 0 quando si usa l'esponente p-1.

Per generalizzare questo approccio e per essere più precisi possiamo avere una funzione come questa:

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
Questo generatore è preciso, ma non perfettamente uniforme. Per vedere questo, considera il fatto che un generatore uniforme in [0,15624] ha 15625 possibili esiti, che non è divisibile per 7. Questo introduce un pregiudizio al numero 0 (che ha una probabilità di 2233/15625, e gli altri solo 2232/15625). Dopotutto, mentre usando il piccolo teorema di Fermat potrebbe sembrare corretto a prima vista, dice che (5 ^ 6)% 7 = 1, e non (5 ^ 6)% 7 = 0. Quest'ultimo è ovviamente impossibile per qualsiasi esponente perché 5 e 7 sono entrambi numeri primi. Penso che sia ancora una soluzione accettabile e ho modificato il tuo post per riflettere questo.
aviatore il

12

Qui sono ammessi problemi con i compiti?

Questa funzione esegue la matematica "base 5" per generare un numero compreso tra 0 e 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 soluzione corretta (che ti porta molto avanti rispetto alla curva), sebbene non molto efficiente. Ciò fa una media di 5 chiamate a rnd5 () per ogni chiamata a rnd7 ().
Adam Rosenfield,

ho bisogno di qualche spiegazione in più
Barry,

1
@ Barry - In primo luogo, non puoi semplicemente aggiungere due numeri casuali insieme, non ottieni una soluzione lineare (considera una coppia di dadi). Ora considera "Base 5": 00, 01, 02, 03, 04, 10, 11. Quello 0-6 nella base 5. Quindi, dobbiamo semplicemente generare 2 cifre del numero della base 5 e aggiungerle fino a quando prendine uno che rientri nell'intervallo. Ecco cosa fa r2 * 5 + r1. Il ciclo r2> 1 è lì perché non vorremmo mai una cifra alta di> 1.
Will Hartung,

Questa soluzione non genera una distribuzione uniforme. I numeri 1 e 7 possono essere generati solo in un modo, ma da 2 a 6 possono essere generati ciascuno in due modi: con r1 uguale al numero meno 1 e r2 uguale a 0 o con r1 uguale al numero meno 2 e r2 uguale a 1. Pertanto, da 2 a 6 verranno restituiti in media due volte più spesso di 1 o 7.
Ted Hopp,

12

Se consideriamo il vincolo aggiuntivo del tentativo di dare la risposta più efficiente, ovvero quella che ha dato un flusso di input I, di interi distribuiti uniformemente di lunghezza mda 1-5 output a stream O, di numeri interi distribuiti uniformemente da 1-7 del relativo più lungo a mdire L(m).

Il modo più semplice per analizzarlo è trattare i flussi I e Orispettivamente i numeri 5-ary e 7-ary. Ciò è ottenuto dall'idea della risposta principale di prendere lo stream a1, a2, a3,... -> a1+5*a2+5^2*a3+..e allo stesso modo per lo stream O.

Quindi se prendiamo una sezione del flusso di input di lunghezza m choose n s.t. 5^m-7^n=cdove c>0ed è il più piccolo possibile. Quindi c'è una mappa uniforme dal flusso di input di lunghezza m agli interi da 1a 5^me un'altra mappa uniforme da interi da 1 a 7^nal flusso di output di lunghezza n dove potremmo dover perdere alcuni casi dal flusso di input quando l'intero mappato supera 7^n.

Quindi questo dà un valore L(m)di circa m (log5/log7)che è approssimativamente .82m.

La difficoltà con l'analisi di cui sopra è l'equazione 5^m-7^n=cche non è di facile soluzione e esattamente il caso in cui il valore di uniforme da 1al 5^msupera 7^ne perdiamo efficienza.

La domanda è: quanto vicino può essere raggiunto il miglior valore possibile di m (log5 / log7). Ad esempio, quando questo numero si avvicina a un numero intero, possiamo trovare un modo per ottenere questo numero integrale esatto di valori di output?

Se 5^m-7^n=cquindi dal flusso di input generiamo effettivamente un numero casuale uniforme da 0a (5^m)-1e non utilizziamo valori superiori a 7^n. Tuttavia, questi valori possono essere salvati e riutilizzati. Generano effettivamente una sequenza uniforme di numeri da 1 a 5^m-7^n. Quindi possiamo quindi provare a usarli e convertirli in numeri 7-ary in modo da poter creare più valori di output.

Se lasciamo T7(X)essere la lunghezza media della sequenza di output di random(1-7)numeri interi derivati ​​da un input uniforme di dimensione X, e supponendo che 5^m=7^n0+7^n1+7^n2+...+7^nr+s, s<7.

Quindi T7(5^m)=n0x7^n0/5^m + ((5^m-7^n0)/5^m) T7(5^m-7^n0)poiché non abbiamo una lunghezza nessuna sequenza con probabilità 7 ^ n0 / 5 ^ m con un residuo di lunghezza 5^m-7^n0con probabilità (5^m-7^n0)/5^m).

Se continuiamo a sostituire, otteniamo:

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

Quindi

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

Un altro modo per dirlo è:

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)

Il miglior caso possibile è il mio originale sopra dove 5^m=7^n+s, doves<7 .

Quindi T7(5^m) = nx(7^n)/(7^n+s) = n+o(1) = m (Log5/Log7)+o(1)come prima.

Il caso peggiore è quando possiamo trovare solo k e st 5 ^ m = kx7 + s.

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

Altri casi sono da qualche parte nel mezzo. Sarebbe interessante vedere quanto bene possiamo fare per m molto grandi, cioè quanto possiamo ottenere il termine di errore:

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

Sembra impossibile da raggiungere e(m) = o(1)in generale, ma speriamo di poterlo dimostrare e(m)=o(m).

Il tutto si basa quindi sulla distribuzione delle cifre 7-ary di 5^mvari valori di m.

Sono sicuro che ci sia molta teoria là fuori che copre questo, potrei dare un'occhiata e riferire ad un certo punto.


+2 (se potessi) - questa è stata l'unica buona risposta (al contrario di semplicemente adeguata). Hai la seconda migliore risposta che si adatta a numeri interi a 32 bit.
Rex Kerr

10

Ecco un'implementazione Python funzionante della risposta di 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

Mi piace lanciare algoritmi che sto esaminando in Python in modo da poter giocare con loro, ho pensato di pubblicarlo qui nella speranza che sia utile a qualcuno là fuori, non che ci sia voluto molto tempo per metterli insieme.


No, è abbastanza diverso dalla mia risposta. Stai eseguendo il loop 21 volte e scarti i risultati delle prime 20 iterazioni. Stai anche usando un rand4 () e un rand5 () come input, che ovviamente infrange le regole dell'uso solo di rand5 (). Infine, produci una distribuzione non uniforme.
Adam Rosenfield,

Mi dispiace per quello. Ero piuttosto stanco quando ho esaminato questa domanda, abbastanza stanco che ho letto male il tuo algoritmo. In realtà l'ho lanciato in Python perché non riuscivo a capire perché stavi facendo un loop 21 volte. Adesso ha molto più senso. Ho fatto la cosa random.randint (1, 4) come una scorciatoia ma immagino che tu abbia ragione, è contro lo spirito della domanda. Ho corretto il codice.
James McMahon,

@robermorales - Come ha spiegato Adam Rosenfeld nella sua risposta , ogni soluzione che fornisce una vera distribuzione uniforme su [1, 7] comporterà una sorta di ciclo di accettazione-rifiuto potenzialmente infinito. (Tuttavia, se si rand5()tratta di un PRNG decente, il loop non sarà infinito perché alla fine 5*(rand5() - 1) + rand5()sarà sicuramente <= 21.)
Ted Hopp

10

Perché non farlo semplice?

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

Le possibilità di ottenere 1 e 7 in questa soluzione sono inferiori a causa del modulo, tuttavia, se si desidera solo una soluzione rapida e leggibile, questa è la strada da percorrere.


13
Ciò non produce una distribuzione uniforme. Questo produce i numeri 0-6 con probabilità 2/25, 4/25, 5/25, 5/25, 5/25, 3/25, 1/25, come si può verificare contando tutti i 25 possibili risultati.
Adam Rosenfield,

8

Supponendo che rand (n) qui significhi "intero casuale in una distribuzione uniforme da 0 a n-1 ", ecco un esempio di codice che usa il randint di Python, che ha quell'effetto. Usa solo randint (5) e costanti per produrre l'effetto di randint (7) . Un po 'sciocco, in realtà

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 Perché Python non ha do ... while. Potrebbe essere stato 1337, o 12345, o qualsiasi numero> 1.
tckmn

8

La premessa alla base della risposta corretta di Adam Rosenfield è:

  • x = 5 ^ n (nel suo caso: n = 2)
  • manipola n rand5 chiamate per ottenere un numero y entro l'intervallo [1, x]
  • z = ((int) (x / 7)) * 7
  • se y> z, riprova. altrimenti restituisce y% 7 + 1

Quando n è uguale a 2, hai 4 possibilità di eliminazione: y = {22, 23, 24, 25}. Se usi n uguale a 6, hai solo 1 lancio: y = {15625}.

5 ^ 6 = 15625
7 * 2232 = 15624

Chiami rand5 più volte. Tuttavia, hai una probabilità molto più bassa di ottenere un valore di lancio (o un ciclo infinito). Se c'è un modo per non ottenere alcun valore da buttare via per y, non l'ho ancora trovato.


1
Non esiste alcun caso senza valori usa e getta - se non esistesse un lancio, 5 ^ ne 7 ^ m avrebbero un fattore in comune. Ma sono (poteri di) numeri primi, quindi no.
Rex Kerr

8

Ecco la mia risposta:

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);
  }
}

È un po 'più complicato di altri, ma credo che minimizzi le chiamate a rand5. Come con altre soluzioni, c'è una piccola probabilità che possa essere ripetuta a lungo.


Ciò produce una distribuzione non molto diversa dalle altre soluzioni, ma presenta l'ulteriore svantaggio di essere inutilmente complessa. Soffre anche della possibilità non deterministica di loop per sempre dimostrabilmente errata se i numeri sono veramente casuali. Continuo a pensare che quelli che producono una distribuzione leggermente meno uniforme (anche se ancora molto più che adeguata) ma garantiscono un comportamento deterministico migliore.
paxdiablo,

@Pax: Per favore, mi illumini su come questo produce una distribuzione non uniforme. La mia analisi del codice, così come i miei test, indicano che ciò produce una distribuzione uniforme. Come abbiamo discusso in precedenza, è impossibile produrre una distribuzione perfettamente uniforme e avere un limite superiore di tempo costante garantito del tempo di esecuzione.
Adam Rosenfield,


6

Finché non ci sono sette possibilità tra cui scegliere, disegna un altro numero casuale, che moltiplica il numero di possibilità per cinque. In 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;
}

la tua distribuzione non è uniforme, almeno alla prima chiamata. In effetti, $possibilitiesdeve sempre crescere fino a 25 per uscire dal loop e tornare. Quindi, il tuo primo risultato è [0-124] % 7, che non è distribuito uniformemente perché 125 % 7 != 0(questo è 6, in realtà).
bernard paulus,

6

Non mi piacciono gli intervalli a partire da 1, quindi inizierò da 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;
}

Questo è un vincitore Questo produce tutti e 7 i risultati con uguale probabilità. 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

Ecco fatto, distribuzione uniforme e zero chiamate rand5.

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

È necessario impostare il seme in anticipo.


5

So che ha avuto una risposta, ma sembra che funzioni bene, ma non posso dirti se ha una propensione. Il mio "test" suggerisce che è, almeno, ragionevole.

Forse Adam Rosenfield sarebbe così gentile da commentare?

La mia (ingenua?) Idea è questa:

Accumula rand5 finché non ci sono abbastanza bit casuali per creare un rand7. Questo richiede al massimo 2 rand5. Per ottenere il numero Rand7 uso il valore accumulato mod 7.

Per evitare che l'accumulatore trabocchi, e poiché l'accumulatore è mod 7, prendo il mod 7 dell'accumulatore:

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

La funzione rand7 () segue:

(Lascio che l'intervallo di rand5 sia 0-4 e anche rand7 sia 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;
}

Modifica: aggiunti risultati per 100 milioni di prove.

Le funzioni "reali" del 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

My rand7

La media sembra ok e anche le distribuzioni di numeri sembrano ok.

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


Probabilmente dovresti guardare la correlazione sequenziale. Penso che se prendi coppie successive (ogni numero "casuale" associato al suo predecessore), potresti trovare cose sorprendenti. Non hai spiegato PERCHÉ dovrebbe mantenere uniforme la distribuzione, in ogni caso. Un programma di lavoro normalmente dovrebbe iniziare con una spiegazione del perché funziona.
Ian,

La correlazione sequenziale si applicherebbe a molte di queste soluzioni?
philcolbourn,

La correlazione sequenziale si applicherebbe a molte di queste soluzioni? È passato un po 'di tempo da quando ho provato a farlo e ho pensato di averlo spiegato. Guardandolo ora, sembra che sto accumulando bit casuali in un pool da Rand5, assicurandomi che siano stati accumulati abbastanza prima di ritirare abbastanza per creare un numero Rand7 e assicurandomi di non traboccare il mio accumulatore.
philcolbourn,

4

Esistono algoritmi eleganti citati sopra, ma ecco un modo per affrontarlo, anche se potrebbe essere una rotonda. Sto assumendo valori generati da 0.

R2 = generatore di numeri casuali che fornisce valori inferiori a 2 (spazio campione = {0, 1})
R8 = generatore di numeri casuali che fornisce valori inferiori a 8 (spazio campione = {0, 1, 2, 3, 4, 5, 6, 7 })

Per generare R8 da R2, eseguirai R2 tre volte e utilizzerai il risultato combinato di tutte e 3 le esecuzioni come numero binario con 3 cifre. Ecco l'intervallo di valori quando R2 viene eseguito tre volte:

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

Ora per generare R7 da R8, eseguiamo nuovamente R7 se restituisce 7:

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

La soluzione rotonda è quella di generare R2 da R5 (proprio come abbiamo generato R7 da R8), quindi R8 da R2 e quindi R7 da R8.


come molti altri, questo approccio potrebbe richiedere un tempo arbitrariamente lungo per ogni chiamata R7, dal momento che potresti ottenere una lunga serie di sette da R8.
Alex North-Keys,

4

Ecco una soluzione che si adatta interamente ai numeri interi e si trova a circa il 4% dell'ottimale (ovvero utilizza 1,26 numeri casuali in {0..4} per ognuno in {0..6}). Il codice è in Scala, ma la matematica dovrebbe essere ragionevolmente chiara in qualsiasi lingua: approfitti del fatto che 7 ^ 9 + 7 ^ 8 è molto vicino a 5 ^ 11. Quindi scegli un numero di 11 cifre nella base 5 e poi lo interpreti come un numero di 9 cifre nella base 7 se è nell'intervallo (fornendo 9 numeri di base 7) o come un numero di 8 cifre se si trova sopra il numero di 9 cifre, ecc. .:

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)
  }
}

Se si incolla un test nell'interprete (REPL effettivamente), si ottiene:

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 distribuzione è piacevole e piatta (entro circa 10k di 1/7 di 10 ^ 8 in ciascun cestino, come previsto da una distribuzione approssimativamente gaussiana).


3

Utilizzando un totale parziale , è possibile entrambi

  • mantenere una distribuzione equa; e
  • non è necessario sacrificare alcun elemento nella sequenza casuale.

Entrambi questi problemi sono un problema con le rand(5)+rand(5)...soluzioni di tipo semplice . Il seguente codice Python mostra come implementarlo (la maggior parte di questo sta provando la distribuzione).

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)

E questo output mostra i risultati:

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%)

Un semplicistico rand(5)+rand(5), ignorando quei casi in cui questo restituisce più di 6 ha una variazione tipica del 18%, 100 volte quella del metodo mostrato sopra:

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%)

E, su consiglio di Nixuz, ho ripulito lo script in modo da poter estrarre e usare le rand7...cose:

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, lasciami riformulare questo. Dato che una determinata x è stata prodotta ad un certo punto della sequenza, solo 5 di 7 numeri possono essere prodotti per il numero successivo nella sequenza. Un vero RNG avrebbe tutti i campioni indipendenti l'uno dall'altro, ma in questo caso chiaramente non lo sono.
Adam Rosenfield,

3
È vero che la domanda originale non specifica se le funzioni di input e output producono campioni indipendenti e distribuiti in modo identico (iid), ma penso che sia ragionevole aspettarsi che se l'input rand5 () è iid, l'output rand7 () dovrebbe anche essere iid. Se non pensi che sia ragionevole, divertiti a usare il tuo RNG non iid.
Adam Rosenfield,

1
Allora, qual è la parola dei matematici all'università?
Adam Rosenfield,

1
Questa soluzione è chiaramente rotta. È ovvio che devi chiamare rand5 (in media) più di una volta per chiamata a rand7 e questa soluzione no. Pertanto i risultati non possono essere casuali con una definizione sana di casuale.
Chris Suter,

1
@Pax Ad ogni iterazione della tua funzione, può restituire solo uno dei cinque valori diversi (sebbene nell'intervallo 0-6). La prima iterazione può restituire solo un numero nell'intervallo 0-4. Quindi, dovrebbe essere chiaro che mentre la tua funzione può avere una distribuzione uniforme, i campioni non sono indipendenti, cioè sono correlati che non è qualcosa che desideri in un generatore di numeri casuali.
Chris Suter,

3

Questa risposta è più un esperimento per ottenere la massima entropia dalla funzione Rand5. È quindi un po 'poco chiaro e quasi sicuramente molto più lento di altre implementazioni.

Supponendo la distribuzione uniforme da 0-4 e la distribuzione uniforme risultante da 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;
  }
}

Il numero di bit aggiunti al buffer per chiamata a Rand5 è attualmente 4/5 * 2, quindi 1.6. Se viene incluso il valore di probabilità 1/5 che aumenta di 0,05, quindi 1,65, ma vedere il commento nel codice in cui ho dovuto disabilitare questo.

Bit consumati per chiamata a Rand7 = 3 + 1/8 * (3 + 1/8 * (3 + 1/8 * (...
Questo è 3 + 3/8 + 3/64 + 3/512 ... quindi circa 3,42

Estraendo le informazioni dai sette richiedo 1/8 * 1/7 bit per chiamata, quindi circa 0,018

Ciò fornisce un consumo netto di 3,4 bit per chiamata, il che significa che il rapporto è 2,125 chiamate a Rand5 per ogni Rand7. L'ottimale dovrebbe essere 2.1.

Immagino che questo approccio sia significativamente più lento di molti altri qui a meno che il costo della chiamata a Rand5 non sia estremamente costoso (diciamo che chiamare una fonte esterna di entropia).


La tua soluzione appare corretta, a parte alcuni semplici errori: "if (count> 1)" dovrebbe essere "if (count <= 1)" e "i ++" che si verifica poco dopo dovrebbe trovarsi all'interno delle parentesi graffe che la precedono. Non sono sicuro se BitsSet () sia corretto, ma è in qualche modo irrilevante.
Adam Rosenfield,

Nel complesso, tuttavia, la tua funzione è molto difficile da capire. Fa un uso leggermente migliore dell'entropia di quanto non potrebbe altrimenti, a costo di più complicazioni. Non c'è nemmeno motivo di riempire inizialmente il buffer con 35 bit casuali alla prima chiamata, quando 3 sarebbero sufficienti.
Adam Rosenfield,

Ho corretto <= grazie, in realtà i ++ dovrebbe essere lì. Dovrebbe succedere sullo zero e sul caso 1 (aggiungendo rispettivamente 1 o uno zero al buffer). Questo non è assolutamente quello che consiglierei di usare, è orribilmente complicato. Ero solo interessato a quanto potessi avvicinarmi ai limiti teorici dell'entropia inerenti al problema ... Grazie per il feedback. Ironia della sorte, il riempimento del buffer alla prima chiamata è stato quello di rendere più semplice la scrittura :)
ShuggyCoUk

Ho rielaborato questo per essere più facile da capire (a scapito della velocità) ma anche reso corretto. Non è ancora ottimale, per qualche motivo i 1/5 bit causano problemi anche se sono uniformi nel conteggio.
ShuggyCoUk,

3

in 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);
}

esegue un ciclo per produrre un numero casuale compreso tra 16 e 127, si divide per sedici per creare un float tra 1 e 7,9375, quindi arrotonda per ottenere un int tra 1 e 7. Se non sbaglio, c'è una possibilità 16/112 di ottenere uno dei 7 risultati.


anche se probabilmente c'è una risposta più semplice simile a questa senza l'uso di un ciclo condizionale, e modulo invece di floor. non riesco proprio a sgretolare i numeri in questo momento.
dqhendricks,

3
extern int r5();

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

problema: restituisce in modo non uniforme nell'intervallo 0-7, non 0-6. In effetti, puoi avere 7 = 111bconp(7) = 8 / 125
bernard paulus il

3

Penso di avere quattro risposte, due che danno soluzioni esatte come quella di @Adam Rosenfield ma senza il problema del loop infinito, e altre due con una soluzione quasi perfetta ma un'implementazione più veloce della prima.

La migliore soluzione esatta richiede 7 chiamate a rand5, ma consente di procedere per capire.

Metodo 1 - Esatto

Il punto di forza della risposta di Adam è che fornisce una distribuzione uniforme perfetta, e c'è un'altissima probabilità (21/25) che saranno necessarie solo due chiamate a rand5 (). Tuttavia, il caso peggiore è loop infinito.

La prima soluzione di seguito fornisce anche una distribuzione uniforme perfetta, ma richiede un totale di 42 chiamate rand5. Nessun loop infinito.

Ecco un'implementazione 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

Per le persone che non hanno familiarità con R, ecco una versione semplificata:

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 distribuzione di rand5sarà preservata. Se facciamo la matematica, ognuna delle 7 iterazioni del loop ha 5 ^ 6 possibili combinazioni, quindi il numero totale di possibili combinazioni sono (7 * 5^6) %% 7 = 0. Quindi possiamo dividere i numeri casuali generati in gruppi uguali di 7. Vedi il metodo due per ulteriori discussioni su questo.

Ecco tutte le possibili combinazioni:

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 

Penso che sia semplice dimostrare che il metodo di Adam verrà eseguito molto più velocemente. La probabilità che ci siano 42 o più chiamate a rand5nella soluzione di Adam è molto piccola ( (4/25)^21 ~ 10^(-17)).

Metodo 2 - Non Esatto

Ora il secondo metodo, che è quasi uniforme, ma richiede 6 chiamate a rand5:

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

Ecco una versione semplificata:

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

Questa è essenzialmente un'iterazione del metodo 1. Se generiamo tutte le possibili combinazioni, ecco i conteggi risultanti:

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 numero apparirà ancora una volta nelle 5^6 = 15625prove.

Ora, nel Metodo 1, aggiungendo da 1 a 6, spostiamo il numero 2233 in ciascuno dei punti successivi. Pertanto, il numero totale di combinazioni corrisponderà. Questo funziona perché 5 ^ 6 %% 7 = 1, e quindi facciamo 7 variazioni appropriate, quindi (7 * 5 ^ 6 %% 7 = 0).

Metodo 3 - Esatto

Se l'argomento del metodo 1 e 2 è compreso, il metodo 3 segue e richiede solo 7 chiamate rand5. A questo punto, ritengo che questo sia il numero minimo di chiamate necessarie per una soluzione esatta.

Ecco un'implementazione R:

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

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

Per le persone che non hanno familiarità con R, ecco una versione semplificata:

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

La distribuzione di rand5sarà preservata. Se facciamo la matematica, ognuna delle 7 iterazioni del ciclo ha 5 possibili esiti, quindi il numero totale di possibili combinazioni lo sono (7 * 5) %% 7 = 0. Quindi possiamo dividere i numeri casuali generati in gruppi uguali di 7. Vedi il metodo uno e due per ulteriori discussioni su questo.

Ecco tutte le possibili combinazioni:

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 

Penso che sia semplice dimostrare che il metodo di Adam sarà ancora più veloce. La probabilità che ci siano 7 o più chiamate a rand5nella soluzione di Adam è ancora piccola ( (4/25)^3 ~ 0.004).

Metodo 4 - Non esatto

Questa è una variante minore del secondo metodo. È quasi uniforme, ma richiede 7 chiamate a rand5, che è un'ulteriore al metodo 2:

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

Ecco una versione semplificata:

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

Se generiamo tutte le possibili combinazioni, ecco i conteggi risultanti:

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

Due numeri appariranno una volta in meno nelle 5^7 = 78125prove. Per molti scopi, posso conviverci.


1
Non ho familiarità con R, ma a meno che non fraintenda come funzionano, il metodo 1 non è esatto. Ha (5 ^ 6) ^ 7 = 5 ^ 42 possibili esiti, non (5 ^ 6) * 7; 5 ^ 42 non è divisibile per 7. Allo stesso modo il metodo 3 non è esatto. Ha 5 ^ 7 possibili esiti, non 5 * 7. (Anche l'ultima iterazione del ciclo nel metodo 3 i=7non ha alcun effetto, poiché l'aggiunta 7*rand5()a rnon modifica il valore della rmod 7.)
Adam Rosenfield

2

La funzione di cui hai bisogno è rand1_7 () , ho scritto rand1_5 () in modo che tu possa testarlo e tracciarlo.

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.