确定掷骰子中出现的数字的频率

Posted

技术标签:

【中文标题】确定掷骰子中出现的数字的频率【英文标题】:Determine Frequency of numbers showing up in dice rolls 【发布时间】:2010-10-04 08:12:07 【问题描述】:

对于一个游戏,我试图确定某个 # 出现在给定的骰子数时出现的频率。我知道……这个问题似乎很奇怪。让我试着用实数来解释。

因此,对于 1 个骰子,每个数字的频率将相同。 1-6 会出现相同的次数。

现在对于 2 个骰子,情况会有所不同。我想 5,6,7 将是最常滚动的,而频谱两端的数字将显示较少或根本不显示(在 1 的情况下)。我想知道如何计算这个列表并以正确的顺序显示它们,从最频繁到不太频繁。

有什么想法吗?


@duffymo - 虽然有某种算法来提出它会非常好。似乎上述方式将需要大量的手工挑选和放置数字。如果我的骰子数是动态的,最多可以说 10,那么我认为手工操作将是低效且麻烦的。 :)

【问题讨论】:

你把数字加起来了吗?为什么你会得出 5,6 和 7 会出现更多的结论? @JoshFinnie - 可能是我错误地假设 5,6,7 会更频繁 - 但我基于这样一个事实,即获得 5 的掷骰可能是 2+3 & 4+1 而 3 只能与 2+1 一起出现;六是 3+3、4+2、5+1;等 【参考方案1】:

两个骰子有 6*6 = 36 种组合。

2 = 1+1 只能出现一次,所以它的出现频率是 1/36。 3 = 1+2 或 2+1,所以它的频率是 2/36 = 1/18。 4 = 1+3、2+2 或 3+1,所以它的频率是 3/36 = 1/12。

您可以在十二点之前完成剩下的工作。

任何双陆棋玩家都知道这些。

【讨论】:

【参考方案2】:

不需要真正的“算法”或模拟 - 它是基于 De Moivre 推导出的公式的简单计算:

http://www.mathpages.com/home/kmath093.htm

而且它不是“钟形曲线”或正态分布。

【讨论】:

不是钟形曲线?你确定?我的意思是,它的形状绝对像一个钟形,尽管我承认我的统计数据很弱,以至于我可以确信它不是“正态”分布,也可能不是经典的“钟形曲线”。 (顺便说一句,很棒的链接......如果你能说服我这不是真正的钟形曲线,我会投赞成票。) 这是一个离散分布,所以它不可能是一个真正的正态分布。然而,像大多数分布一样,它是渐近正态的 - 对于大量骰子,正态分布是更好的近似值(并且计算更容易)。 没关系...这就是我的想法,我在帖子中以这种方式对冲:它近似于正态分布。这就是我对这个答案的问题......这意味着它们是两种不同的野兽,而不仅仅是将迭代延伸到无限的问题。 骰子的数量越大,越接近正态分布。出于这个原因(组合学中的阶乘),对于大量骰子,可以更快地使用正态分布。【参考方案3】:

递归方式的草稿:

public static IEnumerable<KeyValuePair<int, int>> GetFrequenciesByOutcome(int nDice, int nSides)

    int maxOutcome = (nDice * nSides);
    Dictionary<int, int> outcomeCounts = new Dictionary<int, int>();
    for(int i = 0; i <= maxOutcome; i++)
        outcomeCounts[i] = 0;

    foreach(int possibleOutcome in GetAllOutcomes(0, nDice, nSides))
        outcomeCounts[possibleOutcome] = outcomeCounts[possibleOutcome] + 1;

    return outcomeCounts.Where(kvp => kvp.Value > 0);


private static IEnumerable<int> GetAllOutcomes(int currentTotal, int nDice, int nSides)

    if (nDice == 0) yield return currentTotal;
    else
    
        for (int i = 1; i <= nSides; i++)
            foreach(int outcome in GetAllOutcomes(currentTotal + i, nDice - 1, nSides))
                yield return outcome;
    

除非我弄错了,否则这应该会吐出像 [key, frequency] 组织的 KeyValuePairs。

编辑:仅供参考,运行此命令后,它显示 GetFrequenciesByOutcome(2, 6) 的频率为:

2:1

3:2

4:3

5:4

6:5

7:6

8:5

9:4

10:3

11:2

12:1

【讨论】:

我想知道为什么这被否决了,因为没有其他人提供算法。 #1 是一种计算,而不是一种算法。有人建议手动完成计算,然后对表格进行硬编码(哎哟)——我宁愿有一个像这样的真正算法,也不愿手动计算数字并对其进行硬编码。 该算法非常简单,就像冒泡排序一样......并且存在类似的效率问题。这是迄今为止提出的唯一算法,但对于大量数据,它的强度将远远超过所需。最好只编写其背后的数学代码。 (其他一些帖子有数学链接)。 没错,数学更好。该算法只是对每个可能的掷骰子的简单模拟,正如您可以想象的那样,它可能是很多掷骰子。【参考方案4】:

将先前滚动的频率数组加起来,通过移动位置将“边号”乘以,然后您将得到每个数字出现的频率数组。

1, 1, 1, 1, 1, 1  # 6 sides, 1 roll

1, 1, 1, 1, 1, 1
   1, 1, 1, 1, 1, 1
      1, 1, 1, 1, 1, 1
         1, 1, 1, 1, 1, 1
            1, 1, 1, 1, 1, 1
+              1, 1, 1, 1, 1, 1
_______________________________
1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1  # 6 sides, 2 rolls

1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1
   1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1
      1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1
         1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1
            1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1
+              1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1
______________________________________________
1, 3, 6,10,15,21,25,27,27,25,21,15,10, 6, 3, 1  # 6 sides, 3 rolls

这比蛮力模拟要快得多,因为简单的方程是最好的。 这是我的python3实现。

def dice_frequency(sides:int, rolls:int) -> list:
    if rolls == 1:
        return [1]*sides
    prev = dice_frequency(sides, rolls-1)
    return [sum(prev[i-j] for j in range(sides) if 0 <= i-j < len(prev))
            for i in range(rolls*(sides-1)+1)]

例如,

dice_frequency(6,1) == [1, 1, 1, 1, 1, 1]
dice_frequency(6,2) == [1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1]
dice_frequency(6,3) == [1, 3, 6, 10, 15, 21, 25, 27, 27, 25, 21, 15, 10, 6, 3, 1]

请注意,您应该使用“目标数字 - 滚动计数”作为列表的索引来获取每个数字的频率。如果你想得到概率,使用'side number'^'roll count'作为分母。

sides = 6
rolls = 3
freq = dice_frequency(sides,rolls)
freq_sum = sides**rolls
for target in range(rolls,rolls*sides+1):
    index = target-rolls
    if 0 <= index < len(freq):
        print("%2d : %2d, %f" % (target, freq[index], freq[index]/freq_sum))
    else:
        print("%2d : %2d, %f" % (target, 0, 0.0))

此代码生成

 3 :  1, 0.004630
 4 :  3, 0.013889
 5 :  6, 0.027778
 6 : 10, 0.046296
 7 : 15, 0.069444
 8 : 21, 0.097222
 9 : 25, 0.115741
10 : 27, 0.125000
11 : 27, 0.125000
12 : 25, 0.115741
13 : 21, 0.097222
14 : 15, 0.069444
15 : 10, 0.046296
16 :  6, 0.027778
17 :  3, 0.013889
18 :  1, 0.004630

【讨论】:

【参考方案5】:

网上有很多关于骰子概率的资料。这是一个帮助我解决 Project Euler 问题的链接:

http://gwydir.demon.co.uk/jo/probability/calcdice.htm

【讨论】:

【参考方案6】:

简洁的事实...

你知道帕斯卡三角形是 N 个 2 面骰子之和的概率分布吗?

   1 1    - 1 die, 1 chance at 1, 1 chance at 2
  1 2 1   - 2 dice, 1 chance at 2, 2 chances at 3, 1 chance at 4
 1 3 3 1  - 3 dice, 1 chance at 3, 3 chances at 4, 3 chances at 5, 1 chance at 6 
1 4 6 4 1 - etc.

【讨论】:

这很好!现在我正在考虑如何将这个迭代的思维过程(如何在帕斯卡三角形中创建一行又一行)推广到 M 面骰子。【参考方案7】:

使用动态函数创建的 javascript 实现:

<script>
var f;
function prob(dice, value)
 
var f_s = 'f = function(dice, value) var occur = 0; var a = [];';
for (x = 0; x < dice; x++)
 
f_s += 'for (a[' + x + '] = 1; a[' + x + '] <= 6; a[' + x + ']++) ';
 
f_s += 'if (eval(a.join(\'+\')) == value) occur++;';
for (x = 0; x < dice; x++)
 
f_s += '';
 
f_s += 'return occur;';
eval(f_s);
var occ = f(dice, value);
return [occ, occ + '/' + Math.pow(6, dice), occ / Math.pow(6, dice)];
 ;

alert(prob(2, 12)); // 2 die, seeking 12
                    // returns array [1, 1/36, 0.027777777777777776]
</script>

编辑:很失望没有人指出这一点;必须用Math.pow(6, dice) 替换6 * dice。不要再犯这样的错误了...

【讨论】:

我很好奇你为什么选择这个特殊的感叹词。 :) 因为这是一种比我倾向于的更有趣的代码编写方式:) 我也喜欢这样想。只是最近才发现 JavaScript 中的动态函数创建/修改;很高兴找到它的用途。【参考方案8】:

似乎有一些关于“为什么”的谜团,虽然 duffymo 已经解释了一部分,但我正在查看另一篇文章:

5、6 和 7 的掷数应该没有理由超过 [2],因为第一次掷骰子是独立于第二次掷骰子的事件,而且它们的概率都是 1- 6 被滚动。

这有一定的吸引力。但这是不正确的......因为第一次滚动会影响机会。推理可能最容易通过一个例子来完成。

假设我想弄清楚掷出 2 或 7 的概率是否更有可能出现在两个骰子上。如果我掷出第一个骰子并得到 3,那么我现在总共掷出 7 的机会有多大?显然,六分之一。我总共滚动 2 的机会是多少? 0 比 6...因为我无法在第二个骰子上滚动以使我的总数为 2。

出于这个原因,7 很有可能被掷出……因为无论我在第一个骰子上掷出什么,我仍然可以通过在第二个骰子上掷出正确的数字来达到正确的总数。 6 和 8 的可能性同样略小,5 和 9 的可能性更大,以此类推,直到我们达到 2 和 12,同样不可能,各有 36 分之一的机会。

如果您绘制此图(总和与可能性),您会得到一个漂亮的钟形曲线(或者更准确地说,由于实验的离散性,一个近似的块状曲线)。

【讨论】:

【参考方案9】:

在互联网和***上进行了大量搜索后,我发现Dr. Math在一个工作函数中很好地解释了它(另一个答案中的链接有一个不正确的公式)。我将 Dr. Math 的公式转换为 C#,并且我的 nUnit 测试(之前因其他代码尝试而失败)都通过了。

首先我必须编写一些辅助函数:

  public static int Factorial(this int x)
  
     if (x < 0)
     
        throw new ArgumentOutOfRangeException("Factorial is undefined on negative numbers");
     
     return x <= 1 ? 1 : x * (x-1).Factorial();
  

由于选择在数学中的工作方式,我意识到如果我有一个具有下限的重载阶乘函数,我可以减少计算。该函数可以在达到下限时跳出。

  public static int Factorial(this int x, int lower)
  
     if (x < 0)
     
        throw new ArgumentOutOfRangeException("Factorial is undefined on negative numbers");
     
     if ((x <= 1) || (x <= lower))
     
        return 1;
     
     else
     
        return x * (x - 1).Factorial(lower);
     
  

  public static int Choose(this int n, int r)
  
     return (int)((double)(n.Factorial(Math.Max(n - r, r))) / ((Math.Min(n - r, r)).Factorial()));
  

有了这些,我就可以写了

  public static int WaysToGivenSum(int total, int numDice, int sides)
  
     int ways = 0;
     int upper = (total - numDice) / sides; //we stop on the largest integer less than or equal to this
     for (int r = 0; r <= upper; r++)
     
        int posNeg = Convert.ToInt32(Math.Pow(-1.0, r)); //even times through the loop are added while odd times through are subtracted
        int top = total - (r * sides) - 1;
        ways += (posNeg * numDice.Choose(r) * top.Choose(numDice - 1));
     
     return ways;
  

【讨论】:

以上是关于确定掷骰子中出现的数字的频率的主要内容,如果未能解决你的问题,请参考以下文章

新书上市 |《谁在掷骰子?》在“不确定性时代”中确定前行

从 5 次掷骰子中,生成一个范围为 [1 - 100] 的随机数

用 Python PyQT5 掷骰子

python 由用户输入掷多少次骰子,然后统计每个面出现的次数

Dice

python之pygal:掷两个不同的骰子并统计大小出现次数