确定掷骰子中出现的数字的频率
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] 的随机数