Вычислить точный результат сложного броска двух D30

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

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

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

Если вы напишете какой-то код (см. Ниже), бросите эти кубики несколько миллионов раз и посчитаете, как часто вы получаете каждое число в качестве окончательного результата, вы получите довольно плоскую кривую слева от 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

Кривая, которую вы описываете, представляет собой кумулятивную вероятность. Кривая плотности вероятности для этих данных интересна. Он имеет два четких пика (один на 26 и один на 36) с впадиной в середине (31). Уже одно это заставляет меня думать, что ваш ответ будет трудно определить!

e.James 20.11.2008 01:41

Знаю :) Я уже просил у профессоров статистику, и они не могли придумать ответа ...

Aaron Digulla 20.11.2008 10:54

это должны были быть «два профессора»

Aaron Digulla 20.11.2008 10:58
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
3
533
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

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

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

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

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

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

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

Sparr 20.11.2008 00:12

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

Aaron Digulla 20.11.2008 10:56

Эта попытка не так уж и плоха. Я могу подтвердить эти «дочерние» кривые колокола (вы можете легко увидеть их, импортировав вывод в Excel и отрендерив соответствующие столбцы). Теперь не хватает только правильной функции лаймов для рекурсивных вероятностей.

Aaron Digulla 20.11.2008 11:02

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

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

Нам нужно было что-то со следующими свойствами: - Возвращать значение от 0 до 100 (очень приблизительно; мы получили от 1 до 60, и это было «достаточно хорошо») - Простое выполнение много раз в игре - Значения ниже 0 должны быть действительно редкими

Aaron Digulla 20.11.2008 10:57
Ответ принят как подходящий

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

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

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

В любом случае, оставим вопросы стиля программирования: предположим, что 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] × [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), так что это тоже выходит.)

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

re: dict -> Поленился заполнить пробелы между клавишами в dict. Если вы запустите мой код, вы увидите, что некоторые результаты имеют счетчик 0. Итак, массив был намного проще.

Aaron Digulla 21.11.2008 15:09

Мне нужен генератор случайных чисел, который люди могут использовать во время ролевой игры. Генераторы работают достаточно хорошо; нам просто интересно, каковы его свойства.

Aaron Digulla 21.11.2008 15:10

... во время ролевой игры без компьютер :)

Aaron Digulla 21.11.2008 15:19

Я прочитал это несколько раз и не могу понять вас. Как получить "(1/30) (1/30) (5 + F (x-5))" ?? Почему вы прибавляете ценность броска к «F (x-5)»?

Aaron Digulla 24.11.2008 11:57

Вероятность события = сумма_ (по каждому способу возникновения этого события) {Вероятность такого события}. Итак, здесь F (x) = вероятность того, что «конечный результат» будет ≤x = sum_ (по всем возможным результатам первого броска) {(вероятность этого броска) * (что должно произойти в остальных бросках) }.

ShreevatsaR 24.11.2008 18:29

То есть, если в первом броске вы получите (2,3) (вероятность 1/900), то вы бросите снова - и то, что вы собираетесь делать с этого момента, является в точности исходной задачей (см. рекурсивный код / ​​определение), и чтобы получить ≤x для исходного набора бросков, вы должны получить ≤x-5 в наборе бросков «с этого момента».

ShreevatsaR 24.11.2008 18:33

Ааа ... все проясняется. Вы говорите: чтобы получить окончательный результат F (5) (где 5 - это не первый бросок, а конечный результат), я беру p (5) (одиночный бросок), а затем мне нужно добавить F (0), потому что следующий roll должен дать 0 (иначе окончательный результат не будет 5). Правильный?

Aaron Digulla 26.11.2008 11:15

Я чувствую, что это много вопросов, но не могли бы вы написать сценарий Python, который это реализует? Или помогите написать?

Aaron Digulla 26.11.2008 11:19

Я провел базовую статистику по выборке из 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

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

Другие вопросы по теме