使用固定概率生成随机数

5
我在论坛中看到很多关于这个问题的讨论,但是所有的答案都太过具体化而无法套用。我找到的离我需求最近的是:Probability Random Number Generator by Alon Gubkin
不同之处在于,Alon 要求多出一个面(即六),而我希望将六个面的概率分配,使它们相加等于100%。例如,第一面的概率为40%,第二面只有10%,第三面则为25%,……等等。
我该如何做到这一点?

创建一个包含40个1,10个2和25个3的数组,然后随机选择一个元素。 - zerkms
可能是 https://dev59.com/2nRB5IYBdhLWcg3w-8Ho 的重复问题。 - user2314737
5个回答

15

单个概率检查可以使用线性概率轻松完成:

function checkWithProbability($probability=0.1, $length=10000)
{
   $test = mt_rand(1, $length);
   return $test<=$probability*$length;
}
例如,这将产生:
for($i=0; $i<10; $i++)
{
   var_dump(checkWithProbability(1/3));
}

类似这样:

布尔(false)
布尔(true)
布尔(false)
布尔(false)
布尔(false)
布尔(false)
布尔(false)
布尔(false)
布尔(true)
布尔(false)

您可以使用这个原则来以所需的概率检查您的边缘:

function checkWithSet(array $set, $length=10000)
{
   $left = 0;
   foreach($set as $num=>$right)
   {
      $set[$num] = $left + $right*$length;
      $left = $set[$num];
   }
   $test = mt_rand(1, $length);
   $left = 1;
   foreach($set as $num=>$right)
   {
      if($test>=$left && $test<=$right)
      {
         return $num;
      }
      $left = $right;
   }
   return null;//debug, no event realized
}

这个想法是利用几何概率 - 即将某条线段分为相应长度的几段,然后检查我们的随机数属于哪一部分。

                 0.75  0.9
                  |    |
                  V    V
*--------*--*-----*-*--*--* <-- (长度)
^        ^  ^       ^     ^
|        |  |       |     |
0      0.4 0.5     0.8    1

示例:

$set = [
  1 => 0.4,
  2 => 0.1,
  3 => 0.25,
  4 => 0.05,
  5 => 0.1,
  6 => 0.1
];
for($i=0; $i<10; $i++)
{
   var_dump(checkWithSet($set));
}

结果如下:

int(1)
int(2)
int(2)
int(6)
int(3)
int(1)
int(1)
int(6)
int(1)
int(1)

您可以增加$length - 理论上,这将提高随机检查的“质量”,但这并不是太容易的事情 - 因为mt_rand()使用伪随机生成器Mersenne Twister(在理想情况下,这不是真正的线性概率)


那真的很棒,我的朋友。这就是我一直在寻找的。非常非常感谢^^。 - Ali Albahrani

2
一个相对简单的方法是创建一个长度为100的数组,将你的“面”数字写入其中,然后进行洗牌并获取第一个元素。
因此,根据你的例子,在这个数组中有40个1,10个2,25个3。
以下是一些简单的代码示例(未经过测试):
$probabilities = array(
    1 => 40,
    2 => 10,
    3 => 25,
    4 => 5,
    5 => 10,
    6 => 10
);

$random = array();
foreach($probabilities as $key => $value) {
    for($i = 0; $i < $value; $i++) {
        $random[] = $key;
    }
}

shuffle($random);
echo $random[0];

1
在您的情况下,您可以生成1到100之间的随机数,然后:
if random in 1:40 -> face 1
elseif random in 41:50 -> face 2
and so on. 

当然,真正的代码会更加复杂,以获取实际范围而不是硬编码的if语句。

0

我尝试对 Alma 的代码进行了一些修改。
主要目标是使代码更短、更简单。
在这个例子中,您将输入概率作为整数,而不是小数,因此添加一个7.5%的概率将强制您将所有内容乘以10。

// face 1 = 40%, face 2 = 10% etc...
$probabilities = [40, 10, 25, 25];
$results = ['face 1', 'face 2', 'face 3', 'face 4'];
echo checkWithSet($probabilities, $results);

function checkWithSet($probabilities, $results)
{
   $total = array_sum($probabilities);
   $random_num = mt_rand(1, $total);
   $counter = 0;
   foreach($probabilities as $index=>$value)
   {
      $counter += $value
      if($counter > $random_num)
      {
         return $results[$index];
      }
   }
}

0
我可以想到一个非常简单的解决方案。这个方案不会改变随机数生成器的生成模式,但会解释结果以适应您上述的问题。我会要求随机数生成器生成0到9之间的数字。然后进行以下映射,根据我感兴趣的概率将生成的数字范围分配给我感兴趣的值:
If result <= 3, face=1
else if result <=5, face =2
else is result <=25 face =3
//and so on

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