Fondamentalmente, quello che stai chiedendo è un generatore di eventi "semi-casuale" che genera eventi con le seguenti proprietà:
La frequenza media alla quale si verifica ciascun evento è specificata in anticipo.
È meno probabile che si verifichi lo stesso evento due volte di seguito rispetto a quanto accadrebbe a caso.
Gli eventi non sono completamente prevedibili.
Un modo per farlo è innanzitutto implementare un generatore di eventi non casuale che soddisfi gli obiettivi 1 e 2, quindi aggiungere un po 'di casualità per soddisfare l'obiettivo 3.
Per il generatore di eventi non casuale, possiamo usare un semplice algoritmo di dithering . In particolare, p 1 , p 2 , ..., p n siano le probabilità relative degli eventi da 1 a n e s s = p 1 + p 2 + ... + p n sia la somma dei pesi. Possiamo quindi generare una sequenza non casuale di eventi equidistribuita al massimo utilizzando il seguente algoritmo:
Inizialmente, lascia e 1 = e 2 = ... = e n = 0.
Per generare un evento, incremento ciascuna e io da p I , e l'uscita l'evento k per il quale e k è il più grande (rompere i legami in qualsiasi modo si desidera).
Decremento e k da s , e ripetere dal punto 2.
Ad esempio, dati tre eventi A, B e C, con p A = 5, p B = 4 e p C = 1, questo algoritmo genera qualcosa come la seguente sequenza di output:
A B A B C A B A B A A B A B C A B A B A A B A B C A B A B A
Nota come questa sequenza di 30 eventi contenga esattamente 15 As, 12 Bs e 3 Cs. Non si distribuisce in modo abbastanza ottimale - ci sono alcune occorrenze di due come di seguito, che avrebbero potuto essere evitate - ma si avvicina.
Ora, per aggiungere casualità a questa sequenza, hai diverse opzioni (non necessariamente reciprocamente esclusive):
Puoi seguire il consiglio di Philipp e mantenere un "mazzo" di N prossimi eventi, per un numero N di dimensioni adeguate . Ogni volta che devi generare un evento, scegli un evento casuale dal mazzo, e poi lo sostituisci con il prossimo evento generato dall'algoritmo di dithering sopra.
Applicando questo esempio all'esempio sopra, con N = 3, si produce ad esempio:
A B A B C A B B A B A B C A A A A B B A B A C A B A B A B A
mentre N = 10 produce l'aspetto più casuale:
A A B A C A A B B B A A A A A A C B A B A A B A C A C B B B
Nota come gli eventi comuni A e B finiscono con molte più corse a causa del mescolamento, mentre i rari eventi C sono ancora abbastanza ben distanziati.
Puoi iniettare un po 'di casualità direttamente nell'algoritmo di dithering. Ad esempio, invece di incrementare e i da p i nel passaggio 2, si potrebbe incrementarlo da p i × casuale (0, 2), dove casuale ( un , b ) è un uniformemente distribuito numero casuale tra una e b ; questo produrrebbe un output come il seguente:
A B B C A B A A B A A B A B A A B A A A B C A B A B A C A B
oppure potresti incrementare e i di p i + random (- c , c ), che produrrebbe (per c = 0.1 × s ):
B A A B C A B A B A B A B A C A B A B A B A A B C A B A B A
oppure, per c = 0,5 × s :
B A B A B A C A B A B A A C B C A A B C B A B B A B A B C A
Si noti come lo schema additivo abbia un effetto randomizzante molto più forte per gli eventi rari C rispetto agli eventi comuni A e B, rispetto a quello moltiplicativo; questo potrebbe o non potrebbe essere desiderabile. Naturalmente, potresti anche utilizzare una combinazione di questi schemi o qualsiasi altra regolazione degli incrementi, purché preservi la proprietà che l' incremento medio di e i sia uguale a p i .
In alternativa, è possibile perturbare l' output dell'algoritmo di dithering sostituendo a volte l'evento k scelto con uno casuale (scelto in base ai pesi grezzi p i ). Fintanto che usi lo stesso k nel passaggio 3 dell'output nel passaggio 2, il processo di dithering tenderà comunque a uniformare le fluttuazioni casuali.
Ad esempio, ecco alcuni esempi di output, con una probabilità del 10% di ogni evento scelto casualmente:
B A C A B A B A C B A A B B A B A B A B C B A B A B C A B A
ed ecco un esempio con una probabilità del 50% che ogni output sia casuale:
C B A B A C A B B B A A B A A A A A B B A C C A B B A B B C
Potresti anche considerare di inserire un mix di eventi puramente casuali e sottoposti a dithering in un mazzo / pool di miscelazione, come descritto sopra, o forse randomizzare l'algoritmo di dithering scegliendo k in modo casuale, come pesato dagli e i (trattando i pesi negativi come zero).
Ps. Ecco alcune sequenze di eventi completamente casuali, con le stesse tariffe medie, per il confronto:
A C A A C A B B A A A A B B C B A B B A B A B A A A A A A A
B C B A B C B A A B C A B A B C B A B A A A A B B B B B B B
C A A B A A B B C B B B A B A B A A B A A B A B A C A A B A
Tangente: dato che nei commenti si è discusso sulla necessità o meno, per soluzioni basate sul mazzo, di svuotare il mazzo prima che venga riempito, ho deciso di fare un confronto grafico di diverse strategie di riempimento del mazzo:
Trama di diverse strategie per generare lanci di monete semi-casuali (con un rapporto 50:50 tra testa e croce in media). L'asse orizzontale è il numero di lanci, l'asse verticale è la distanza cumulativa dal rapporto previsto, misurata come (teste - code) / 2 = teste - lanci / 2.
Le linee rosse e verdi sulla trama mostrano due algoritmi non basati sul mazzo per il confronto:
- Linea rossa, dithering deterministico : i risultati con numero pari sono sempre capi, i risultati con numero dispari sono sempre code.
- Linea verde, lanci casuali indipendenti : ogni risultato viene scelto indipendentemente a caso, con una probabilità del 50% di teste e una probabilità del 50% di code.
Le altre tre linee (blu, viola e ciano) mostrano i risultati di tre strategie basate sul mazzo, ognuna implementata usando un mazzo di 40 carte, che inizialmente è riempito con 20 carte "testa" e 20 carte "croce":
- Linea blu, riempi quando è vuota : le carte vengono pescate casualmente fino a quando il mazzo è vuoto, quindi il mazzo viene riempito con 20 carte "testa" e 20 carte "croce".
- Linea viola, riempita a metà vuota : le carte vengono pescate in modo casuale fino a quando sul mazzo rimangono 20 carte; quindi il mazzo viene riempito con 10 carte "testa" e 10 carte "croce".
- Linea ciano, riempimento continuo : le carte vengono pescate a caso; i sorteggi pari vengono immediatamente sostituiti con una carta "teste" e i sorteggi dispari con una carta "croce".
Naturalmente, la trama sopra è solo una singola realizzazione di un processo casuale, ma è ragionevolmente rappresentativo. In particolare, puoi vedere che tutti i processi basati sul mazzo hanno una propensione limitata e rimangono abbastanza vicini alla linea rossa (deterministica), mentre la linea verde puramente casuale alla fine si allontana.
(In effetti, la deviazione delle linee blu, viola e ciano lontano da zero è strettamente limitata dalle dimensioni del mazzo: la linea blu non può mai spostarsi più di 10 passi da zero, la linea viola può ottenere solo 15 passi da zero e la linea ciano può spostarsi al massimo a 20 passi da zero. Naturalmente, in pratica, qualsiasi linea che effettivamente raggiunge il suo limite è estremamente improbabile, poiché c'è una forte tendenza per loro a tornare più vicino allo zero se vagano troppo lontano off.)
A prima vista, non vi è alcuna evidente differenza tra le diverse strategie basate sul mazzo (sebbene, in media, la linea blu rimanga un po 'più vicina alla linea rossa e la linea ciano rimanga un po' più lontana), ma un'ispezione più ravvicinata della linea blu rivela uno schema deterministico distinto: ogni 40 disegni (contrassegnati dalle linee verticali grigie tratteggiate), la linea blu incontra esattamente la linea rossa a zero. Le linee viola e ciano non sono così strettamente vincolate e possono stare lontane da zero in qualsiasi punto.
Per tutte le strategie basate sul mazzo, l'importante caratteristica che mantiene limitata la loro variazione è il fatto che, mentre le carte vengono pescate casualmente dal mazzo, il mazzo viene riempito in modo deterministico. Se le carte usate per riempire il mazzo fossero esse stesse scelte casualmente, tutte le strategie basate sul mazzo diventerebbero indistinguibili dalla pura scelta casuale (linea verde).