17十二/090
使用 PHP 解决机率和权重问题
在程序设计中我们经常会碰到要解决机率或权重相关的问题,例如经常碰到的中奖问题。 有一个百货公司办了一个抽奖活动,一等奖的中奖率是3%,二等奖是5%,三等奖是10%,纪念奖是20%。 处理这个需求,一般的程序员会采用如下的逻辑:
$randKey = mt_rand(1, 100);
$prizeLevel = 0;
if ($randKey <= 3)
{
$prizeLevel = 1;
}
elseif ($randKey <= 3 + 5)
{
$prizeLevel = 2;
}
elseif ($randKey <= 3 + 5 + 10)
{
$prizeLevel = 3;
}
elseif ($randKey <= 3 + 5 + 10 + 20)
{
$prizeLevel = 4;
}
这段程序似乎有些复杂,我们可以简化一下它:
$prizeInfo = array(
0 => 62,
1 => 3,
2 => 5,
3 => 10,
4 => 20
);
$randKey = mt_rand(1, 100);
$radix = 0;
foreach ($prizeInfo as $prizeLevel => $prizeOdds)
{
$radix += $prizeOdds;
if ($randKey <= $radix)
{
break;
}
}
权重和机率类似,只是概率总值不固定,我们只想直观的用一个数字的大小来表示想给它多大的机会。 我们修改一下需求,一等奖的权重是5,二等奖是10,三等奖是20,纪念奖是50,没有奖励是100。 实际上,稍微修改一下逻辑,我们就可以处理这个问题了:
$prizeInfo = array(
0 => 100,
1 => 5,
2 => 10,
3 => 20,
4 => 50
);
$randKey = mt_rand(1, array_sum($prizeInfo));
$radix = 0;
foreach ($prizeInfo as $prizeLevel => $prizeOdds)
{
$radix += $prizeOdds;
if ($randKey <= $radix)
{
break;
}
}
但是实际上,我们在处理更复杂的需求时会发现,即使我们使用了 PHP 中号称更好用线性分布更平均的 mt_rand,统计结果看起来,也不是那么随机,或许这是源于 PHP 本身的问题。 我在设计杀死怪物时按照权重掉落物品的逻辑时,就遇到了这样的问题。 稍作处理后,结果稍微满意了些,基本上,我只是扩大了随机范围,扰乱了权重的顺序。
$prizeInfo = array(
0 => 100,
1 => 5,
2 => 10,
3 => 20,
4 => 50
);
$prizeLevels = array_keys($prizeInfo);
shuffle($prizeLevels);
$randKey = rand(1, array_sum($prizeInfo));
$radix = 0;
foreach ($prizeLevels as $prizeLevel)
{
$radix += $prizeInfo[$prizeLevel];
if ($randKey <= $radix)
{
break;
}
}
这段逻辑基本上可以应付大多数的权重和机率问题! 当然,如果你有更复杂更精确的需求,你可能需要自己设计一个特别的算法。