更有效的模拟2掷骰子的Python

2024-05-17 05:45:10 发布

您现在位置:Python中文网/ 问答频道 /正文

我写了一个程序,记录了2个公平骰子需要掷多少次,以匹配我们期望的每个结果的概率。在

我认为这是可行的,但我想知道是否有一种更适合资源的方式来解决这个问题。在

 import random

 expected = [0.0, 0.0, 0.028, 0.056, 0.083, 
             0.111, 0.139, 0.167, 0.139, 0.111,
             0.083, 0.056, 0.028]

 results = [0.0] * 13  # store our empirical results here

 emp_percent = [0.0] * 13  # results / by count

 count = 0.0  # how many times have we rolled the dice? 

 while True:
     r = random.randrange(1,7) + random.randrange(1,7)  # roll our die
     count += 1 
     results[r] += 1
     emp_percent = results[:]

     for i in range(len(emp_percent)):
         emp_percent[i] /= count
         emp_percent[i] = round(emp_percent[i], 3)

     if emp_percent == expected:
         break

print(count)
print(emp_percent)

Tags: 程序公平count记录ourrandom资源骰子
2条回答

这里有几个问题。在

首先,不能保证这一点会永远终止,也不太可能在合理的时间内终止。忽略浮点运算问题,只有当您的数字分布正确时,才会终止。但是law of large numbers不能保证这种情况会发生。大数定律是这样的:

  1. 你最初的结果(随机的)几乎肯定是有偏见的。在
  2. 最终,尚未进行的试验将大大超过你最初的试验,而在随后的试验中缺乏偏见将超过你最初的偏见。在

请注意,初始偏差是从不平衡。相反,它与其他结果相形见绌。这意味着偏差趋于零,但这不能保证偏差在有限的试验中消失。事实上,它特别预测,逐渐减少的偏见将继续无限期存在。所以这个算法永远不会终止是完全有可能的,因为总有那么一点点偏差仍然存在,统计上不重要,但仍然很大。在

这已经够糟糕的了,但是你也在使用浮点运算,浮点运算违反了很多传统的数学规则,因为计算机一直在进行中间舍入,以确保数字继续放入内存中,即使它们是重复的(以2为基数)或非理性的。事实上,你将经验百分位数四舍五入到小数点后三位并不能解决这个问题,因为并不是所有的终止小数(以10为基数)都是以二进制值结尾的(以2为基数),所以你可能仍然会发现经验值和预期值之间的不匹配。与其这样做:

if emp_percent == expected:
    break

…您可以尝试以下操作(仅在Python 3.5+中):

^{pr2}$

这样可以同时解决这两个问题。默认情况下,^{}要求值彼此之间的小数点后9位以内,因此它插入了必要的give,以便该算法能够实际工作。请注意,对于涉及零的比较,它确实需要特殊处理,因此您可能需要针对您的用例调整此代码,如下所示:

is_close = functools.partial(math.is_close, abs_tol=1e-9)
if all(map(is_close, emp_percent, expected)):
    break

math.is_close()也消除了取整你的经验的需要,因为它可以为你做这样的近似:

is_close = functools.partial(math.is_close, rel_tol=1e-3, abs_tol=1e-5)
if all(map(is_close, emp_percent, expected)):
    break

如果您真的不想使用这些近似值,那么您将不得不放弃浮点,而只使用^{}。它们被彼此分开时会产生精确的结果。但是,由于上面讨论的原因,您的算法不太可能很快终止(或者根本不可能终止)。在

与其尝试匹配浮点数,不如尝试为每个可能的和匹配期望值。这就相当于你试图做的事情,因为(观察数)/(试验次数)=(理论概率)当且仅当观察数等于预期数。当转鼓数是36的倍数时,这些值始终是整数。因此,如果转鼓的数量不是36的倍数,那么你的观察结果就不可能与期望值完全相等。在

要得到期望值,请注意,出现在各种和的确切概率中的分子(1,2,3,4,5,6,5,4,3,2,1分别为和2,3,…,12)中的分子是如果掷骰子36次,则是总和的期望值。如果骰子掷36i次,则将这些分子乘以i,得到期望的和值。下面的代码模拟将一对公平骰子重复掷36次,累计总计数,然后与预期计数进行比较。如果有一个完美匹配,则返回获得匹配所需的试验次数(其中一次试验为36轮)。如果max_trials没有发生这种情况,则给出一个表示最终计数与最终期望值之间差异的向量:

import random

def roll36(counts):
    for i in range(36):
        r1 = random.randint(1,6)
        r2 = random.randint(1,6)
        counts[r1+r2 - 2] += 1

def match_expected(max_trials):
    counts = [0]*11
    numerators = [1,2,3,4,5,6,5,4,3,2,1]
    for i in range(1, max_trials+1):
        roll36(counts)
        expected = [i*j for j in numerators]
        if counts == expected:
            return i
    #else:
    return [c-e for c,e in zip(counts,expected)]

以下是一些典型的输出:

^{pr2}$

在一对公平骰子的3600万次模拟掷骰子中,不仅没有观察到准确的预期值,而且在最终状态下,观察值与期望值之间的差异变得相当大(在绝对值上,相对差异接近于零,正如大数定律预测的那样)。这种方法不太可能产生完美的匹配。一个可行的变化(同时仍然关注期望值)是迭代,直到观测值通过卡方拟合优度检验,与理论分布进行比较。在这种情况下,就没有理由再关注36的倍数了。在

相关问题 更多 >