使用 php 计算事件以不同赔率发生一次或多次的几率


Using php to calculate odds for events to happen one or more times with different odds

我有一组不同的事件,它们都有自己的几率。有没有办法计算它们组合的赔率,以便我得到 0、1 2 等发生的赔率。

数学很简单,但计算的数量增长很快,所以我希望有一个函数可以帮助我。

具有三个因素的示例:

Event | Yes | No
A     | 3%  | 97%
B     | 4%  | 96%
C     | 5%  | 95%

0 发生 : $A[否] * $ B[否] * $c[否]

1 发生 : $A[是] * $ B[否] * $c[否] + $A[否] * $ B[是] * $c[否] + $A[否] *

$ B[否] * $c[是]

2 发生 = $A[是] * $ B[是] * $c[否] + $A[是] * $ B[否] * $c[是] + $A[否] * $ B[是]

* $c[是]

3 发生 : $A[是] * $ B[是] * $c[是]

这很容易用 php 编写,我的问题是如果我添加更多事件,这将如何扩展。只要再添加一个,计算的数量就会翻倍,很快代码就会变得非常长。

那么有没有更简单的方法呢?我将不胜感激任何提示或想法。

这是相当缓慢的实现。

让我们考虑一个包含 5 个事件的案例。

0 事件发生的几率是:

$no[0]  * $no[1]  * $no[2]  * $no[3]  * $no[4]

1 个事件发生的几率是:

$no[0]  * $no[1]  * $no[2]  * $no[3]  * $yes[4] +
$no[0]  * $no[1]  * $no[2]  * $yes[3] * $no[4]  +
$no[0]  * $no[1]  * $yes[2] * $no[3]  * $no[4]  +
...

您进行所有乘法的地方,其中正好有 1 个"是"选择。

发生 2 个事件的几率是:

$no[0]  * $no[1]  * $no[2]  * $yes[3] * $yes[4] +
$no[0]  * $no[1]  * $yes[2] * $no[3]  * $yes[4] +
$no[0]  * $no[1]  * $yes[2] * $yes[3] * $no[4]  +
...

您进行所有乘法的地方,其中正好有 2 个"是"选择。

这可以概括:为了计算N个事件发生的几率,您需要经历所有乘法,其中正好有N个"是"选项。

现在,当您需要计算从 0 到 5 个事件发生的所有赔率时,您需要检查是/否选项的所有可能组合,并将每个乘法添加到 $odds[$yesCount] 中。

$no[0]  * $no[1]  * $no[2]  * $no[3]  * $no[4]   ; added to $odds[0]
$no[0]  * $no[1]  * $no[2]  * $no[3]  * $yes[4]  ; added to $odds[1]
$no[0]  * $no[1]  * $no[2]  * $yes[3] * $no[4]   ; added to $odds[1]
$no[0]  * $no[1]  * $no[2]  * $yes[3] * $yes[4]  ; added to $odds[2]
$no[0]  * $no[1]  * $yes[2] * $no[3]  * $no[4]   ; added to $odds[1]
...
$yes[0] * $yes[1] * $yes[2] * $yes[3] * $yes[4]  ; added to $odds[5]

这里总共有2**5 = 32种不同的乘法,或者通常2**$eventCount

如果我们为每个情况分配一个从 02**$eventCount-1 的数字,然后使用该数字的位来选择乘法中是否包含每个事件的"是"或"否"选择,最后将每个乘法结果添加到$odds[$yesCount],则很容易遍历所有这些情况:

// number of events
$eventCount = 5;
// odds of each event happening
$yes = [ 0.10, 0.50, 0.32, 0.66, 0.99 ];
// odds of each event not happening
$no = [];
for ($eventNumber = 0; $eventNumber < $eventCount; $eventNumber++) {
  $no[$eventNumber] = 1 - $yes[$eventNumber];
}
// initialize combined $odds to zero
$odds = [];
for ($n = 0; $n <= $eventCount; $n++) {
  $odds[$n] = 0;
}
// calculate combined odds
for ($case = 0; $case < 2 ** $eventCount; $case++) {
  $multiplication = 1;
  $yesCount = 0;
  for ($eventNumber = 0; $eventNumber < $eventCount; $eventNumber++) {
    if ($case & (1 << $eventNumber)) {
      $yesCount++;
      $multiplication *= $yes[$eventNumber];
    } else {
      $multiplication *= $no[$eventNumber];
    }
  }
  $odds[$yesCount] += $multiplication;
}
// show combined odds
for ($n = 0; $n <= $eventCount; $n++) {
  echo "Odds of " . $n . " events happening is " . $odds[$n] . "<br>'n";
}

让我们看看我们在这里实际需要什么。让我们假设,我们有一系列的机会:

$chances = [0.8, 0.3, 0.15];

我们需要的是使用以下计算获得这些结果:

// 0.119
$chance0 = (1-$array[0]) * (1-$array[1]) * (1-$array[2]);
// 0.548
$chance1 = $array[0] * (1-$array[1]) * (1-$array[2]) + (1-$array[0]) * $array[1] * (1-$array[2]) + (1-$array[0]) * (1-$array[1]) * $array[2];
// 0.297
$chance2 = $array[0] * $array[1] * (1-$array[2]) + $array[0] * (1-$array[1]) * $array[2] + (1-$array[0]) * $array[1] * $array[2];
// 0.036
$chance3 = $array[0] * $array[1] * $array[2];

我们真正需要的,是基于一个代表成功机会的整数,从数字池中获取每个非重复组合,并使其余组合"反向",即使它们(1 - chance)

为了实现这一点,我使用了这个答案并稍微修改了代码。从上述答案中,我们得到非重复的机会组合(这意味着,如果我们获得 2 次成功的机会,0.80.150.150.8 相同(。接下来,我们使用array_diff来查看剩余的内容,并假设这些是失败的机会。但由于这些机会失败,我们需要"逆转"它们,用相应的回调调用array_map。最后一件事是计算array_product以获得事件发生的机会。

最后,我们只是array_sum所有的机会。

class Combinations implements Iterator
{
    protected $c = null;
    protected $s = null;
    protected $n = 0;
    protected $k = 0;
    protected $pos = 0;
    function __construct($s, $k) {
        if(is_array($s)) {
            $this->s = array_values($s);
            $this->n = count($this->s);
        } else {
            $this->s = (string) $s;
            $this->n = strlen($this->s);
        }
        $this->k = $k;
        $this->rewind();
    }
    function key() {
        return $this->pos;
    }
    function current() {
        $r = array();
        for($i = 0; $i < $this->k; $i++)
            $r[] = $this->s[$this->c[$i]];
        return is_array($this->s) ? $r : implode('', $r);
    }
    function next() {
        if($this->_next())
            $this->pos++;
        else
            $this->pos = -1;
    }
    function rewind() {
        $this->c = range(0, $this->k);
        $this->pos = 0;
    }
    function valid() {
        return $this->pos >= 0;
    }
    protected function _next() {
        $i = $this->k - 1;
        while ($i >= 0 && $this->c[$i] == $this->n - $this->k + $i)
            $i--;
        if($i < 0)
            return false;
        $this->c[$i]++;
        while($i++ < $this->k - 1)
            $this->c[$i] = $this->c[$i - 1] + 1;
        return true;
    }
}
function getTotalChances($calculateFrom, $successCount)
{
    $totalChance = [];
    foreach(new Combinations($calculateFrom, $successCount) as $success)
    {
        $failure = array_map(function($element) {
            return (1-$element);
        }, array_diff($calculateFrom, $success));
        $chance = array_product(array_merge($success, $failure));
        $totalChance[] = $chance;
    }
    return(array_sum($totalChance));
}
$calculateFrom = [0.8, 0.3, 0.15];
var_dump(getTotalChances($calculateFrom, 2));

现在,如果您运行以下命令,您将获得所需的内容:

var_dump(getTotalChances($calculateFrom, 0)); // 0.119
var_dump(getTotalChances($calculateFrom, 1)); // 0.548
var_dump(getTotalChances($calculateFrom, 2)); // 0.297
var_dump(getTotalChances($calculateFrom, 3)); // 0.036