使用六面骰子计算赔率分布

4

我正在尝试计算不断变化的6面骰子投掷次数的概率分布。例如,3d6范围从3到18如下:

3:1, 4:3, 5:6, 6:10, 7:15, 8:21, 9:25, 10:27, 11:27, 12:25, 13:21, 14:15, 15:10, 16:6, 17:3, 18:1

我编写了这个PHP程序来计算它:

function distributionCalc($numberDice,$sides=6) {
for ( $i=0; $i<pow($sides,$numberDice); $i++)
    {
    $sum=0;
    for  ($j=0; $j<$numberDice; $j++)
        { $sum+=(1+(floor($i/pow($sides,$j))) % $sides); }
    $distribution[$sum]++;
    }
return $distribution;
}

内部的$j$循环利用了floormodulus函数的魔力,创建了一个基于6的计数序列,其数字数量为骰子的数量,因此$3d6$将被计算为:
111,112,113,114,115,116,121,122,123,124,125,126,131,etc.

该函数将每个数字求和,因此它会读作:3,4,5,6,7,8,4,5,6,7,8,9,5等。它会遍历所有6^3种可能结果,并在3到18之间的$分布数组中添加1以对应槽位进行计算。相当简单直接。然而,在8d6左右时,它只能工作一段时间,之后我会因为进行了数十亿次计算而出现服务器超时。

但是我不认为这是必要的,因为骰子概率遵循一个甜美的钟形曲线分布。我想知道是否有一种方法可以跳过繁琐的计算直接进入曲线本身。例如,是否可以在80d6(范围:80-480)中实现这一点?是否可以在进行6^80次计算之前预测分布情况?

我不是专业的编码人员,概率对我来说还很新颖,所以感谢您的所有帮助!

Stephen

4个回答

3

在PERL中:

#!
my( $DieType, $NumDice, $Loaded ) = @ARGV;

my $subname = "D" . $DieType . ( ( $Loaded eq "Loaded" ) ? "Loaded" : "Normal" );
my $Prob = \&$subname;

my $width = 12;
my $precision = $width - 2;

printf "%5s  %-${width}s \n", "Pip:", "Frequency:"; 
for ( my $j = $NumDice; $j <= $DieType * $NumDice ; $j++ ) {
  printf "%5d  %${width}.${precision}f \n", $j, Frequency( $DieType, $NumDice, $j );
}

sub D6Normal {
  my $retval = 1/6;
}

sub D6Loaded {
  my $retval = 1/6;

  CASE: for ($_[0]) {
    /1/    && do { $retval -= 0.02/6;   last CASE; }; 
    /2..5/ && do { $retval += 0.0025/6; last CASE; }; 
    /6/    && do { $retval += 0.01/6;   last CASE; }; 
  }
  return $retval;
}

sub D8Normal {
  my $retval = 1/8;
}

sub D10Normal {
  my $retval = 1/10;
}

sub D10Loaded {
  my $retval = 1/10;

  CASE: for ($_[0]) {
    /1..8/ && do { last CASE; }; 
    /9/    && do { $retval -= 0.01/10;  last CASE; }; 
    /10/   && do { $retval += 0.01/10;  last CASE; }; 
  }
  return $retval;
}

sub D12Normal {
  my $retval = 1/12;
}

sub D20Normal {
  my $retval = 1/20;
}

sub D32Normal {
  my $retval = 1/32;
}

sub D100Normal {
  my $retval = 1/100;
}

sub Frequency {
  my( $DieType, $NumberofDice, $PipCount ) = @_;

  if ( ( $PipCount > ($DieType * $NumberofDice) ) || ( $PipCount < $NumberofDice ) ) { 
    return 0; 
  }

  if ( ! exists $Freq{$NumberofDice}{$PipCount} ) {
    if ( $NumberofDice > 1 ) {
      for ( my $i = max( 1, $PipCount - $DieType ); $i <= min( $DieType * ($NumberofDice - 1), $PipCount - 1 ); $i++ ) {
        $Freq{$NumberofDice}{$PipCount} += &$Prob( $PipCount - $i ) * Frequency( $DieType, $NumberofDice - 1, $i );
      }
    } else {
      $Freq{$NumberofDice}{$PipCount} = &$Prob( $PipCount );
    }
  }
  return $Freq{$NumberofDice}{$PipCount}; 
}

sub max {
  my $max = shift(@_);
  foreach my $arg (@_) {
    $max = $arg if $max < $arg;
  }
  return $max;
}

sub min {
  my $min = shift(@_);
  foreach my $arg (@_) {
    $min = $arg if $min > $arg;
  }
  return $min;
}

1
一些代码解释会对阅读你的答案的人有所帮助。 - Andrew Barber

1

二项分布不能对骰子点数的总和进行建模,只能对单个结果进行建模(例如,如果我们掷40个骰子,将掷出多少个3)。 - rlbond

1

好的,让我们从掷一个骰子开始。我们知道平均值是3.5。我们还可以计算方差,

sum(p(x) * (x - M)^2),其中M是平均值,x是骰子结果,p是该骰子结果的概率。

使用这个公式,单次掷骰子的方差为35/12 = 1/6*((-2.5)^2 + (-1.5)^2 + (-0.5)^2 + 0.5^2 + 1.5^2 + 2.5^2)

事实上,对于从相同分布中独立取样的多个样本,它们的方差会相加。因此,如果您掷N个骰子,您应该得到一个新的分布,其平均值为3.5*N,方差为35*N/12。

因此,如果您生成一个平均值为3.5*N,方差为35*N/12的正态分布,假设您掷了足够多的骰子,它将是一个相当好的拟合。


0
我在想是否有一种方法可以跳过数字计算,直接进入曲线本身。例如,使用80d6(范围:80-480),是否可以这样做?能否在不进行6^80次计算的情况下进行分布投影?
是的。独立变量和的概率函数是每个变量的概率函数的卷积。
在这种情况下,卷积只是一个特殊的求和。(更一般地,卷积是一个积分。)设p和q为两个离散概率函数。卷积通常用星号表示。
(p * q)[i] = sum_{j=1}^(n_p) p[j] q[i - j + 1]

其中i的范围为1到(n_p + n_q - 1),其中n_p是p的元素数量,n_q是q的元素数量。如果(i-j+1)小于1或大于n_q,则将q [i-j + 1]归零(这样这些项就从总和中消失了)。

在这种情况下,您有p = q = [1/6,1/6,1/6,1/6,1/6,1/6],n_p = n_q = 6。3个骰子点数之和的分布是(p * p * p)。80次掷骰子点数之和的分布是(p * p * p * …(还有76个p)… * p)。

我不懂PHP,所以我用 Maxima写了一个小程序。

discrete_conv (p, q) := makelist (discrete_conv1 (p, q, i), i, 1, length (p) + length (q) - 1);
discrete_conv1 (p, q, i) := sum (p [j] * foo (q, i - j + 1), j, 1, length (p));
foo (a, i) := if 1 <= i and i <= length (a) then a [i] else 0;
r : [1/6, 1/6, 1/6, 1/6, 1/6, 1/6];
discrete_conv (r, discrete_conv (r, r));
 => [1/216,1/72,1/36,5/108,5/72,7/72,25/216,1/8,1/8,25/216,7/72,
     5/72,5/108,1/36,1/72,1/216]

如果你继续重复使用discrete_conv,你会发现数字变得越来越像正态分布。这是中心极限定理的一个例子。
我完全有可能在索引方面犯了一些错误,所以你需要检查一下。希望这能对问题有所启发。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接