在 PHP 中按重量生成随机结果?

我知道如何在 PHP 中生成一个随机数,但让我们说,我想要一个1-10之间的随机数,但我想要更多的3,4,5的然后8,9,10的。这怎么可能?我会发布我尝试过的东西,但是说实话,我甚至不知道从哪里开始。

37235 次浏览

有一个 对你来说是个不错的指导

基本上:

  1. 把所有数字的权重加起来。
  2. 选一个小于这个数的随机数
  3. 按顺序减去权重,直到结果为负,如果为负则返回该数字。

对于一个有效的随机数,它总是向量表的一端倾斜:

  • 选择一个介于0. .1之间的连续随机数
  • 提高到 γ 的幂,使它偏移。1是未加权的,越低的数字越高,反之亦然
  • 缩放到所需的范围和整数

比如 PHP (未经测试) :

function weightedrand($min, $max, $gamma) {
$offset= $max-$min+1;
return floor($min+pow(lcg_value(), $gamma)*$offset);
}
echo(weightedrand(1, 10, 1.5));

本教程 使用 PHP,通过多个剪切和粘贴解决方案,指导您完成这个过程。注意,由于下面的注释,这个例程比您在该页面上看到的稍微修改了一些。

从职位上取得的职能:

/**
* weighted_random_simple()
* Pick a random item based on weights.
*
* @param array $values Array of elements to choose from
* @param array $weights An array of weights. Weight must be a positive number.
* @return mixed Selected element.
*/


function weighted_random_simple($values, $weights){
$count = count($values);
$i = 0;
$n = 0;
$num = mt_rand(1, array_sum($weights));
while($i < $count){
$n += $weights[$i];
if($n >= $num){
break;
}
$i++;
}
return $values[$i];
}

因为我使用了 IainMH 的解决方案,所以我不妨分享我的 PHP 代码:

<pre><?php


// Set total number of iterations
$total = 1716;


// Set array of random number
$arr = array(1, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5);
$arr2 = array(0, 0, 1, 1, 2, 2, 2, 3, 3, 4, 5);


// Print out random numbers
for ($i=0; $i<$total; $i++){


// Pick random array index
$rand = array_rand($arr);
$rand2 = array_rand($arr2);


// Print array values
print $arr[$rand] . "\t" . $arr2[$rand2] . "\r\n";


}


?></pre>

简单明了。 只要复制/粘贴并测试它。

/**
* Return weighted probability
* @param (array) prob=>item
* @return key
*/
function weightedRand($stream) {
$pos = mt_rand(1,array_sum(array_keys($stream)));
$em = 0;
foreach ($stream as $k => $v) {
$em += $k;
if ($em >= $pos)
return $v;
}


}


$item['30'] = 'I have more chances than everybody :]';
$item['10'] = 'I have good chances';
$item['1'] = 'I\'m difficult to appear...';


for ($i = 1; $i <= 10; $i++) {
echo weightedRand($item).'<br />';
}

编辑: 在末尾添加了缺少的括号。

基于@Allain 的 回答/链接,我在 PHP 中编写了这个快速函数。如果要使用非整数加权,则必须对其进行修改。

  /**
* getRandomWeightedElement()
* Utility function for getting random values with weighting.
* Pass in an associative array, such as array('A'=>5, 'B'=>45, 'C'=>50)
* An array like this means that "A" has a 5% chance of being selected, "B" 45%, and "C" 50%.
* The return value is the array key, A, B, or C in this case.  Note that the values assigned
* do not have to be percentages.  The values are simply relative to each other.  If one value
* weight was 2, and the other weight of 1, the value with the weight of 2 has about a 66%
* chance of being selected.  Also note that weights should be integers.
*
* @param array $weightedValues
*/
function getRandomWeightedElement(array $weightedValues) {
$rand = mt_rand(1, (int) array_sum($weightedValues));


foreach ($weightedValues as $key => $value) {
$rand -= $value;
if ($rand <= 0) {
return $key;
}
}
}

可以从 非标准 PHP 库使用 加权选择。它接受一个对列表(条目、权重) ,以便有可能处理不能是数组键的条目。可以使用 成对函数将 array(item => weight)转换为所需的格式。

use function \nspl\a\pairs;
use function \nspl\rnd\weightedChoice;


$weights = pairs(array(
1 => 10,
2 => 15,
3 => 15,
4 => 15,
5 => 15,
6 => 10,
7 => 5,
8 => 5,
9 => 5,
10 => 5
));


$number = weightedChoice($weights);

在这个例子中,2-5出现的频率是7-10的3倍。

我刚刚轻松释放了一个 类执行加权排序

它基于 布拉德的阿兰的答案中提到的相同算法,并且针对速度进行了优化,针对均匀分布进行了单元测试,并且支持任何 PHP 类型的元素。

使用它很简单,实例化它:

$picker = new Brick\Random\RandomPicker();

然后以加权值数组的形式添加元素(仅当元素是字符串或整数时) :

$picker->addElements([
'foo' => 25,
'bar' => 50,
'baz' => 100
]);

或者单独调用 addElement()。该方法支持任何类型的 PHP 值作为元素(字符串、数字、对象、 ...) ,这与数组方法截然不同:

$picker->addElement($object1, $weight1);
$picker->addElement($object2, $weight2);

然后得到一个随机元素:

$element = $picker->getRandomElement();

获得其中一个元素的概率取决于它的相关权重。唯一的限制是权重必须是整数。

/**
* @param array $weightedValues
* @return string
*/
function getRandomWeightedElement(array $weightedValues)
{
$array = array();


foreach ($weightedValues as $key => $weight) {
$array = array_merge(array_fill(0, $weight, $key), $array);
}


return $array[array_rand($array)];
}

getRandomWeightedElement(array('A'=>10, 'B'=>90));

这是一个非常简单的方法。如何得到随机加权元素。我填充数组变量 $key。我得到 $key 到数组 $weight x。然后,将 array _ rand 用于 array。我有随机值;)。

函数 getBucketFromWeights ($value){ $total = $currentTotal = $bucket = 0;

foreach ($values as $amount) {
$total += $amount;
}


$rand = mt_rand(0, $total-1);


foreach ($values as $amount) {
$currentTotal += $amount;


if ($rand => $currentTotal) {
$bucket++;
}
else {
break;
}
}


return $bucket;

}

我已经修改了这里的答案 用户自定义权重选取随机元素

在我写完这篇文章之后,我看到别人有一个更优雅的回答,呵呵呵呵。

这个页面上的许多答案似乎使用了数组膨胀、过度迭代、库或难以阅读的过程。当然,每个人都认为自己的宝宝是最可爱的,但我真的认为我的方法是精简,简单,容易阅读/修改..。

根据 OP,我将创建一个从1到10的值数组(声明为键) ,其中3、4和5的权重是其他值的两倍(声明为值)。

$values_and_weights=array(
1=>1,
2=>1,
3=>2,
4=>2,
5=>2,
6=>1,
7=>1,
8=>1,
9=>1,
10=>1
);

如果你只打算进行一次随机选择,并且/或者你的数组相对较小 * (确保你自己的基准测试) ,这可能是你最好的选择:

$pick=mt_rand(1,array_sum($values_and_weights));
$x=0;
foreach($values_and_weights as $val=>$wgt){
if(($x+=$wgt)>=$pick){
echo "$val";
break;
}
}

这种方法不涉及数组修改,可能不需要迭代整个数组(但可能需要)。


另一方面,如果你打算对数组进行多次随机选择,并且/或者你的数组是足够大 * (确保你自己的基准测试) ,重新构建数组可能会更好。

生成新数组的内存成本将越来越合理,因为:

  1. 数组大小增加和
  2. 随机选择的数量增加。

新数组要求为每个值用“限制”替换“权重”,方法是将前一个元素的权重添加到当前元素的权重中。

然后翻转数组,使限制是数组键,值是数组值。 其逻辑是: 选择的值的最低限制是 > = $pick。

// Declare new array using array_walk one-liner:
array_walk($values_and_weights,function($v,$k)use(&$limits_and_values,&$x){$limits_and_values[$x+=$v]=$k;});


//Alternative declaration method - 4-liner, foreach() loop:
/*$x=0;
foreach($values_and_weights as $val=>$wgt){
$limits_and_values[$x+=$wgt]=$val;
}*/
var_export($limits_and_values);

创建此数组:

array (
1 => 1,
2 => 2,
4 => 3,
6 => 4,
8 => 5,
9 => 6,
10 => 7,
11 => 8,
12 => 9,
13 => 10,
)

现在生成随机 $pick并选择值:

// $x (from walk/loop) is the same as writing: end($limits_and_values); $x=key($limits_and_values);
$pick=mt_rand(1,$x);  // pull random integer between 1 and highest limit/key
while(!isset($limits_and_values[$pick])){++$pick;}  // smallest possible loop to find key
echo $limits_and_values[$pick];  // this is your random (weighted) value

这种方法非常出色,因为 isset()非常快,while 循环中 isset()调用的最大数量只能与数组中的最大权重(不要与极限混淆)相同。对于这种情况,最大迭代 = 2!

这种方法不需要迭代整个数组

我用了这个:

mt_rand($min, mt_rand($min, $max));

它给出更低的值和更低的值,因为值越高,被 mt _ rand 中的一个删除的值就越多

这个概率在较低的值中呈线性增长,形成一个正方形的对角线(见下面的数学)

简单明了

CON : 可能太简单了,所以对于某些用例来说没有足够的权重或平衡

数学:

让 i 索引 i-nth 值从 min 到 max,

设 P (i)得到 i 值的概率,

让 N = max-min:

P(i)=(1+N-i)/sum(1,N)

因为 N 等于所有项:

P(i) is proportional to N-i

因此,事实上,在较低的值中,概率是线性增加的,形成一个正方形的对角线

变种:

你可以写下变体:

mt_rand($min, mt_rand(1, mt_rand(1, $max))); //value more given in low part


mt_rand(mt_rand($min, $max), $max); //mirrored, more upper values than lower


...

我用了布拉德的回答,并改变了一点,以适应我的情况,增加了更多的灵活性

我有一个带数组值的数组

$products = [
['id'=>1,'name'=> 'product1' , 'chance'=>2] ,
['id'=>2,'name'=> 'product2' , 'chance'=>7]
]

首先我洗牌产品阵列

shuffle($products );

然后你可以把它传递给函数

function getRandomWeightedElement(array $products) {


$chancesSum = 0;
foreach ($products as $product){
$chancesSum += (int) $product['chance'];
}


$rand = mt_rand(1, $chancesSum);
$range = 0;


foreach ($products as $product) {
$range += (int) $product['chance'];
$compare = $rand - $range;
if ($compare <= 0){
return (int) $product['id'];
}
}}