Вычислите точный результат сложного броска двух D30 - PullRequest
3 голосов
/ 19 ноября 2008

Хорошо, это беспокоило меня уже несколько лет. Если вы засосали статистику и высшую математику в школе, отвернитесь, сейчас . Слишком поздно.

Хорошо. Сделай глубокий вдох. Вот правила. Возьмите две тридцатигранные кости (да, они существуют ) и бросьте их одновременно.

  • Добавьте два числа
  • Если оба кубика показывают <= 5 или> = 26, бросьте снова и добавьте результат к тому, что у вас есть
  • Если один <= 5, а другой> = 26, бросить еще раз и вычесть результат из чего у вас есть
  • Повторяйте до тех пор, пока любой из них не станет> 5 и <26! ​​</li>

Если вы напишите какой-нибудь код (см. Ниже), бросите эти кубики несколько миллионов раз и посчитаете, как часто вы получаете каждое число в качестве конечного результата, вы получите кривую, которая довольно плоская слева от 1, около 45 ° градусов между 1 и 60 и флетом выше 60. Шанс бросить 30,5 или лучше больше 50%, лучше 18 - 80%, а лучше 0 - 97%.

Теперь вопрос: можно ли написать программу для вычисления точного значения f (x), т. Е. Вероятности выброса определенного значения?

Справочная информация. Для нашей ролевой игры "Джунгли звезд" мы искали способ контролировать случайные события. Приведенные выше правила гарантируют гораздо более стабильный результат для того, что вы пытаетесь:)

Для гиков, код на Python:

import random
import sys

def OW60 ():
    """Do an open throw with a "60" sided dice"""
    val = 0
    sign = 1

    while 1:
        r1 = random.randint (1, 30)
        r2 = random.randint (1, 30)

        #print r1,r2
        val = val + sign * (r1 + r2)
        islow = 0
        ishigh = 0
        if r1 <= 5:
            islow += 1
        elif r1 >= 26:
            ishigh += 1
        if r2 <= 5:
            islow += 1
        elif r2 >= 26:
            ishigh += 1

        if islow == 2 or ishigh == 2:
            sign = 1
        elif islow == 1 and ishigh == 1:
            sign = -1
        else:
            break

        #print sign

    #print val
    return val

result = [0] * 2000
N = 100000
for i in range(N):
    r = OW60()
    x = r+1000
    if x < 0:
        print "Too low:",r
    if i % 1000 == 0:
        sys.stderr.write('%d\n' % i)
    result[x] += 1

i = 0
while result[i] == 0:
    i += 1

j = len(result) - 1
while result[j] == 0:
    j -= 1

pSum = 0
# Lower Probability: The probability to throw this or less
# Higher Probability: The probability to throw this or higher
print "Result;Absolut Count;Probability;Lower Probability;Rel. Lower Probability;Higher Probability;Rel. Higher Probability;"
while i <= j:
    pSum += result[i]
    print '%d;%d;%.10f;%d;%.10f;%d;%.10f' % (i-1000, result[i], (float(result[i])/N), pSum, (float(pSum)/N), N-pSum, (float(N-pSum)/N))
    i += 1

Ответы [ 4 ]

6 голосов
/ 20 ноября 2008

Мне пришлось сначала переписать твой код, прежде чем я смог его понять:

def OW60(sign=1):
    r1 = random.randint (1, 30)
    r2 = random.randint (1, 30)
    val = sign * (r1 + r2)

    islow  = (r1<=5)  + (r2<=5)
    ishigh = (r1>=26) + (r2>=26)

    if islow == 2 or ishigh == 2:
        return val + OW60(1)
    elif islow == 1 and ishigh == 1:
        return val + OW60(-1)
    else:
        return val

Может быть, вы найдете это менее читабельным; Я не знаю. (Проверьте, соответствует ли это тому, что вы имели в виду.) Кроме того, относительно того, как вы используете «результат» в своем коде - знаете ли вы о dict s в Python?

В любом случае, вопросы стиля программирования в стороне: предположим, что F (x) - это CDF из OW60 (1), т.е.

F(x) = the probability that OW60(1) returns a value ≤ x.

Точно так же пусть

G(x) = the probability that OW60(-1) returns a value ≤ x.

Затем вы можете вычислить F (x) из определения, суммируя по всем (30 и 30 раз) возможным значениям результата первого броска. Например, если первый бросок равен (2,3), то вы снова бросите, поэтому этот термин добавляет (1/30) (1/30) (5 + F (x-5)) в выражение для F ( Икс). Таким образом, вы получите какое-то непристойно длинное выражение, например

F(x) = (1/900)(2+F(x-2) + 3+F(x-3) + ... + 59+F(x-59) + 60+F(x-60))

, которая представляет собой сумму более 900 слагаемых, по одному на каждую пару (a, b) в [30] & times; [30]. Пары (a, b) с обоими ≤ 5 или обоими ≥26 имеют член a + b + F (xab), пары с одним ≤5 и одним ≥26 имеют член a + b + G (xab), и у остальных есть такой термин, как (a + b), потому что вы больше не бросаете.

Точно так же у вас есть

G(x) = (1/900)(-2+F(x-2) + (-3)+F(x-3) + ... + (-59)+F(x-59) + (-60)+F(x-60))

Конечно, вы можете собирать коэффициенты; единственные члены F встречаются от F (x-60) до F (x-52) и от F (x-10) до F (x-2) (для a, b≥26 или для обоих ≤5), и единственные члены G, которые встречаются, - от G (x-35) до G (x-27) (для одного из a, b≥26 и другого ≤5), поэтому существует меньше членов, чем 30 членов. В любом случае, определяя вектор V (x) как

V(x) = [F(x-60) G(x-60) ... F(x-2) G(x-2) F(x-1) G(x-1) F(x) G(x)]

(скажем), у вас есть (из этих выражений для F и G) отношение вида

V(x) = A*V(x-1) + B

для соответствующей матрицы A и соответствующего вектора B (который вы можете вычислить), поэтому начиная с начальных значений вида V (x) = [0 0] для x, достаточно малого, вы можете найти F (x) и G (x) для x в диапазоне, который вы хотите произвольно закрыть точность. (И ваша f (x), вероятность броска x, это просто F (x) -F (x-1), так что это тоже получается.)

Возможно, есть лучший способ. Все сказано и сделано, однако, почему ты это делаешь? Какой бы тип распределения вы ни выбрали, существуют хорошие и простые распределения вероятностей с соответствующими параметрами, которые имеют хорошие свойства (например, небольшая дисперсия, односторонние ошибки и т. Д.). Нет причин создавать собственную специальную процедуру для генерации случайных чисел.

2 голосов
/ 08 августа 2010

Я сделал некоторую базовую статистику по выборке из 20 миллионов бросков. Вот результаты:

Median: 17 (+18, -?) # This result is meaningless
Arithmetic Mean: 31.0 (±0.1)
Standard Deviation: 21 (+1, -2)
Root Mean Square: 35.4 (±0.7)
Mode: 36 (seemingly accurate)

Ошибки были определены экспериментально. Среднее арифметическое и режим действительно точны, и изменение параметров даже довольно агрессивно, кажется, не оказывает на них большого влияния. Я полагаю, поведение медианы уже объяснено.

Примечание: не принимайте эти числа для правильного математического описания функции. Используйте их, чтобы быстро получить представление о том, как выглядит дистрибутив. Для чего-то еще, они не достаточно точны (даже если они могут быть точными.

Возможно, это кому-нибудь пригодится.

Редактировать 2:

graph

На основе только 991 значения. Я мог бы втиснуть в нее больше ценностей, но они исказили бы результат. Этот образец довольно типичный.

Редактировать 1:

вот приведенные выше значения только для одного шестидесятистороннего штампа, для сравнения:

Median: 30.5
Arithmetic Mean: 30.5
Standard Deviation: 7.68114574787
Root Mean Square: 35.0737318611

Обратите внимание, что эти значения рассчитаны, а не экспериментальны.

1 голос
/ 20 ноября 2008

Сложная неограниченная вероятность ... нетривиальна. Я собирался решать проблему так же, как Джеймс Керран, но затем из вашего исходного кода я увидел, что может быть третий набор бросков, четвертый и так далее. Проблема разрешима, но далеко за пределами большинства симуляторов прокатки.

Есть ли какая-то особая причина, по которой вам нужен случайный диапазон от -Inf до + Inf с такой сложной кривой около 1-60? Почему кривая колокола 2D30 неприемлема? Если вы объясните свои требования, вероятно, кто-то может предложить более простой и более ограниченный алгоритм.

0 голосов
/ 19 ноября 2008

Ну, посмотрим. У второго броска (который иногда добавляется или вычитается к первому броску) есть хорошая легко предсказуемая кривая колокола около 31. Первый бросок, конечно, является проблемой.

Для первого броска у нас есть 900 возможных комбинаций.

  • 50 комбинаций приводят к добавлению второго броска.
  • 25 комбинаций приводят к вычитанию второго броска.
  • Оставляя 825 комбинаций, которые соответствуют кривой колокола второго броска.

Набор вычитания (предварительное вычитание) сформирует кривую колокольчика в диапазоне (27..35). Нижняя половина набора добавлений будет формировать кривую колокольчика в диапазоне (2..10), а верхняя половина сформирует кривую колокольчика в диапазоне (52 ... 60)

Моя вероятность немного ржавая, поэтому я не могу определить точные значения для вас, но должно быть ясно, что они приводят к предсказуемым значениям.

...