По сути, вы запрашиваете «полуслучайный» генератор событий, который генерирует события со следующими свойствами:
Средняя скорость, с которой происходит каждое событие, указывается заранее.
Одно и то же событие реже встречается дважды подряд, чем случайное.
События не полностью предсказуемы.
Один из способов сделать это состоит в том, чтобы сначала реализовать генератор неслучайных событий, который удовлетворяет целям 1 и 2, а затем добавить некоторую случайность для достижения цели 3.
Для генератора неслучайных событий мы можем использовать простой алгоритм дизеринга . В частности, пусть p 1 , p 2 , ..., p n - относительные вероятности событий с 1 по n , и пусть s = p 1 + p 2 + ... + p n - сумма весов. Затем мы можем сгенерировать неслучайную максимально равнораспределенную последовательность событий, используя следующий алгоритм:
Сначала пусть e 1 = e 2 = ... = e n = 0.
Чтобы сгенерировать событие, увеличьте каждое значение e i на p i и выведите событие k, для которого e k является наибольшим (разрывая связи любым удобным для вас способом).
Уменьшите e k на s и повторите с шага 2.
Например, для трех событий A, B и C с p A = 5, p B = 4 и p C = 1 этот алгоритм генерирует что-то вроде следующей последовательности выходных данных:
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
Обратите внимание, что эта последовательность из 30 событий содержит ровно 15 As, 12 B и 3 C. Это не совсем оптимально распределяет - есть несколько вхождений по два в ряд, которых можно было бы избежать - но это близко.
Теперь, чтобы добавить случайность к этой последовательности, у вас есть несколько (не обязательно взаимоисключающих) опций:
Вы можете последовать совету Филиппа и сохранить «колоду» из N предстоящих событий для некоторого числа N соответствующего размера . Каждый раз, когда вам нужно сгенерировать событие, вы выбираете случайное событие из колоды, а затем заменяете его следующим выводом события с помощью алгоритма дизеринга, описанного выше.
Применение этого к приведенному выше примеру с N = 3 приводит, например, к следующему:
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
тогда как N = 10 дает более случайный вид:
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
Обратите внимание, что из-за тасования общие события A и B заканчиваются намного большим количеством прогонов, тогда как редкие события C все еще довольно хорошо разнесены.
Вы можете ввести некоторую случайность непосредственно в алгоритм сглаживания. Например, вместо увеличения e i на p i на шаге 2 вы можете увеличить его на p i × random (0, 2), где random ( a , b ) - это равномерно распределенное случайное число между a и b ; это дало бы результат как следующее:
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
или вы можете увеличить e i на p i + random (- c , c ), что приведет к (для 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
или для с = 0,5 × с :
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
Обратите внимание, что аддитивная схема имеет гораздо более сильный рандомизирующий эффект для редких событий C, чем для общих событий A и B, по сравнению с мультипликативной; это может или не может быть желательным. Конечно, вы также можете использовать некоторую комбинацию этих схем или любую другую корректировку приращений, если она сохраняет свойство, при котором средний прирост e i равен p i .
В качестве альтернативы, вы можете нарушить вывод алгоритма дизеринга, иногда заменяя выбранное событие k случайным (выбранным в соответствии с необработанными весами p i ). Пока на шаге 3 вы также используете то же значение k, которое вы выводите на шаге 2, процесс сглаживания будет по-прежнему стремиться к выравниванию случайных колебаний.
Например, вот несколько примеров выходных данных с вероятностью 10% для каждого события, выбранного случайным образом:
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
и вот пример с вероятностью 50% для каждого выхода случайным образом:
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
Вы также можете рассмотреть возможность подачи смеси чисто случайных и размытых событий в пул колоды / микширования, как описано выше, или, возможно, рандомизацию алгоритма сглаживания путем случайного выбора k , взвешенного по e i s (рассматривая отрицательные веса как ноль).
Ps. Вот несколько совершенно случайных последовательностей событий с одинаковыми средними скоростями для сравнения:
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
Касательно: Поскольку в комментариях обсуждались вопросы о том, необходимо ли для решений на основе колоды, чтобы колода могла опустошаться до ее повторного заполнения, я решил провести графическое сравнение нескольких стратегий наполнения колоды:
График нескольких стратегий для генерации полуслучайных подбрасываний монет (в среднем с соотношением головы к хвосту 50:50). Горизонтальная ось - количество сальто, вертикальная ось - совокупное расстояние от ожидаемого соотношения, измеряемое как (головы - хвосты) / 2 = головы - сальто / 2.
Красные и зеленые линии на графике показывают два алгоритма не на основе колоды для сравнения:
- Красная линия, детерминированное сглаживание : четные результаты всегда являются головами, нечетные результаты - всегда хвостами.
- Зеленая линия, независимые случайные сальто : каждый исход выбирается независимо наугад с вероятностью 50% голов и вероятностью 50% хвостов.
Другие три строки (синяя, фиолетовая и голубая) показывают результаты трех основанных на колодах стратегий, каждая из которых реализована с использованием колоды из 40 карт, которая изначально заполнена 20 картами с «головами» и 20 картами «с хвостами»:
- Синяя линия, заполняется, когда пусто : карты вытягиваются случайным образом до тех пор, пока колода не опустеет, затем колода заполняется 20 картами с «головами» и 20 картами с «хвостами».
- Фиолетовая линия, заполняется, когда наполовину пуст : карты вытягиваются случайным образом, пока в колоде не осталось 20 карт; затем колода пополняется 10 картами "головы" и 10 картами "хвостов".
- Голубая линия, заполнять непрерывно : карты раздаются случайным образом; Дроу с четными номерами немедленно заменяется картой "головы", а дро с нечетными номерами - картой "хвосты".
Конечно, приведенный выше график - это всего лишь одна реализация случайного процесса, но он достаточно представительный. В частности, вы можете видеть, что все основанные на колоде процессы имеют ограниченное смещение и остаются довольно близко к красной (детерминированной) линии, тогда как чисто случайная зеленая линия в конечном итоге отклоняется.
(Фактически, отклонение синих, фиолетовых и голубых линий от нуля строго ограничено размером колоды: синяя линия никогда не может отклоняться более чем на 10 шагов от нуля, фиолетовая линия может быть удалена только на 15 шагов от нуля). и голубая линия может отклоняться не более чем на 20 шагов от нуля. Конечно, на практике любая из линий, фактически достигающих своего предела, крайне маловероятна, поскольку у них есть сильная тенденция возвращаться ближе к нулю, если они отклоняются слишком далеко. выкл.)
На первый взгляд, нет очевидной разницы между различными стратегиями на основе колоды (хотя, в среднем, синяя линия остается несколько ближе к красной линии, а голубая линия остается немного дальше), но более тщательное изучение синей линии действительно выявляет отчетливый детерминированный паттерн: каждые 40 розыгрышей (отмеченных пунктирными серыми вертикальными линиями) синяя линия точно встречает красную линию в нуле. Фиолетовые и голубые линии не так строго ограничены и могут держаться подальше от нуля в любой точке.
For all the deck-based strategies, the important feature that keeps their variation bounded is the fact that, while the cards are drawn from the deck randomly, the deck is refilled deterministically. If the cards used to refill the deck were themselves chosen randomly, all of the deck-based strategies would become indistinguishable from pure random choice (green line).