Basicamente, o que você está pedindo é um gerador de eventos "semi-aleatório" que gere eventos com as seguintes propriedades:
A taxa média na qual cada evento ocorre é especificada antecipadamente.
É menos provável que o mesmo evento ocorra duas vezes seguidas do que aleatoriamente.
Os eventos não são totalmente previsíveis.
Uma maneira de fazer isso é primeiro implementar um gerador de eventos não aleatórios que satisfaça os objetivos 1 e 2 e, em seguida, adicionar alguma aleatoriedade para satisfazer o objetivo 3.
Para o gerador de eventos não aleatórios, podemos usar um algoritmo de pontilhamento simples . Especificamente, sejam p 1 , p 2 , ..., p n as probabilidades relativas dos eventos 1 a n e s = p 1 + p 2 + ... + p n seja a soma dos pesos. Em seguida, podemos gerar uma sequência de eventos máxima não-aleatória equidistribuída usando o seguinte algoritmo:
Inicialmente, deixe e 1 = e 2 = ... = e n = 0.
Para gerar um evento, incrementar cada e i por p i , ea saída do evento k para o qual e k é maior (quebrando os laços da maneira que quiser).
Decremento e k por s e repetição do passo 2.
Por exemplo, dados três eventos A, B e C, com p A = 5, p B = 4 ep C = 1, esse algoritmo gera algo como a seguinte sequência de saídas:
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
Observe como essa sequência de 30 eventos contém exatamente 15 As, 12 Bs e 3 Cs. Não é bastante otimizada distribui - há algumas ocorrências de dois Como em uma fileira, o que poderia ter sido evitado - mas chega perto.
Agora, para adicionar aleatoriedade a essa sequência, você tem várias opções (não necessariamente mutuamente exclusivas):
Você pode seguir o conselho de Philipp e manter um "deck" de N eventos futuros, para um número N de tamanho apropriado . Toda vez que você precisa gerar um evento, escolhe um evento aleatório no baralho e o substitui pela próxima saída de evento pelo algoritmo de pontilhamento acima.
A aplicação disso no exemplo acima, com N = 3, produz, por exemplo:
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
enquanto N = 10 produz a aparência mais aleatória:
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
Observe como os eventos comuns A e B terminam com muito mais execuções devido ao embaralhamento, enquanto os eventos C raros ainda são razoavelmente bem espaçados.
Você pode injetar alguma aleatoriedade diretamente no algoritmo de pontilhamento. Por exemplo, em vez de incrementar e i por p i na etapa 2, você pode incrementá-lo por p i × aleatório (0, 2), onde aleatório ( a , b ) é um número aleatório distribuído uniformemente entre a e b ; isso produziria uma saída como a seguinte:
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
ou você pode incrementar e i por p i + aleatório (- c , c ), o que produziria (para 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
ou, para 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
Observe como o esquema aditivo tem um efeito aleatório muito mais forte para os eventos raros C do que para os eventos comuns A e B, em comparação com o evento multiplicativo; isso pode ou não ser desejável. Obviamente, você também pode usar alguma combinação desses esquemas ou qualquer outro ajuste nos incrementos, desde que preserve a propriedade de que o incremento médio de e i é igual a p i .
Como alternativa, você pode perturbar a saída do algoritmo de pontilhamento, substituindo algumas vezes o evento escolhido k por um aleatório (escolhido de acordo com os pesos brutos p i ). Desde que você também use o mesmo k na etapa 3 e emita na etapa 2, o processo de pontilhamento ainda tenderá a uniformizar as flutuações aleatórias.
Por exemplo, aqui está um exemplo de saída, com 10% de chance de cada evento ser escolhido aleatoriamente:
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
e aqui está um exemplo com 50% de chance de cada saída ser aleatória:
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
Também poderia considerar que alimenta uma mistura de eventos puramente aleatórios e pontilhadas numa plataforma / piscina de mistura, como descrito acima, ou talvez randomizar o algoritmo pontilhado escolhendo k aleatoriamente, como pesadas pelo e i s (tratamento de pesos negativos como zero).
Ps. Aqui estão algumas sequências de eventos completamente aleatórias, com as mesmas taxas médias, para comparação:
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: Como houve um debate nos comentários sobre se é necessário, para soluções baseadas em decks, permitir que o deck esvazie antes de ser reabastecido, decidi fazer uma comparação gráfica de várias estratégias de preenchimento de decks:
Traçar várias estratégias para gerar lançamentos de moedas semi-aleatórios (com proporção de 50:50 de cara para coroa), em média. Eixo horizontal é o número de inversões, o eixo vertical é a distância cumulativa da razão esperada, medida como (cara - coroa) / 2 = cara - coroa / 2.
As linhas vermelha e verde no gráfico mostram dois algoritmos não baseados em baralho para comparação:
- Linha vermelha, pontilhamento determinístico : resultados pares são sempre cabeças, resultados pares são sempre caudas.
- Linha verde, inversões aleatórias independentes : cada resultado é escolhido de forma independente e aleatória, com 50% de chance de cara e 50% de chance de coroa.
As outras três linhas (azul, roxo e ciano) mostram os resultados de três estratégias baseadas no baralho, cada uma implementada usando um baralho de 40 cartas, que é inicialmente preenchido com 20 cartas "chefes" e 20 cartas "caudas":
- Linha azul, preenchida quando vazia : as cartas são sorteadas aleatoriamente até que o baralho esteja vazio e, em seguida, o baralho é reabastecido com 20 cartas "cara" e 20 cartas "caudas".
- Linha roxa, preencha quando estiver meio vazio : as cartas são sorteadas aleatoriamente até o baralho ter 20 cartas sobrando; então o baralho é complementado com 10 cartas "cara" e 10 cartas "coroa".
- Linha ciana, preenchimento contínuo : as cartas são sorteadas aleatoriamente; os draws com números pares são imediatamente substituídos por uma carta "heads" e os draws com números ímpares com uma carta "tails".
Obviamente, o gráfico acima é apenas uma realização de um processo aleatório, mas é razoavelmente representativo. Em particular, você pode ver que todos os processos baseados em baralho têm um viés limitado e permanecem razoavelmente perto da linha vermelha (determinística), enquanto a linha verde puramente aleatória acaba se afastando.
(De fato, o desvio das linhas azul, roxa e ciana do zero é estritamente limitado pelo tamanho do baralho: a linha azul nunca pode se afastar mais de 10 passos do zero, a linha roxa pode apenas 15 passos do zero e a linha ciana pode se afastar no máximo 20 passos do zero.Claro, na prática, qualquer uma das linhas que realmente atingem seu limite é extremamente improvável, pois há uma forte tendência para que elas retornem mais perto de zero se vagarem muito longe fora.)
À primeira vista, não há diferença óbvia entre as diferentes estratégias baseadas no baralho (embora, em média, a linha azul fique um pouco mais próxima da linha vermelha e a linha ciana fique um pouco mais distante), mas uma inspeção mais detalhada da linha azul revela um padrão determinístico distinto: a cada 40 desenhos (marcados pelas linhas verticais cinza pontilhadas), a linha azul encontra exatamente a linha vermelha no zero. As linhas roxa e ciana não são tão estritamente restritas e podem ficar longe de zero a qualquer momento.
Para todas as estratégias baseadas no baralho, o recurso importante que mantém a variação limitada é o fato de que, embora as cartas sejam retiradas aleatoriamente do baralho, o baralho é reabastecido deterministicamente. Se as cartas usadas para recarregar o baralho fossem escolhidas aleatoriamente, todas as estratégias baseadas no baralho se tornariam indistinguíveis da escolha aleatória pura (linha verde).