在PHP中通过权重生成随机结果?

我知道如何在PHP中生成一个随机数,但可以说我想要一个1-10之间的随机数,但是我想要更多的3,4,5和8,9,10。 这怎么可能? 我会张贴我已经尝试过,但老实说,我甚至不知道从哪里开始。

基于@ 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; } } } 

对于一个有效的随机数字来说,这个数字的一​​端往往倾向于:

  • 选择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)); 

有一个相当不错的教程给你 。

基本上:

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

天真的黑客攻击是建立一个列表或数组

1,2,3,4,4,4,4,4,4,5,5,5,5,5,6,6,7,7,8,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,27,28,29,23,23,23,23,24,25,26,27,28,29,23,23,23,23,23,23,24,25,26,28,29,23,23,23,23,23,23,24,23,23,24,23,23,24,23,23,23,24,23,23,24,23,23,24,23,23,24,23,23,24,23,23,24,23,23,24,23,23,24,25,23,23,24,23,23,24,23,23,23,24,25 9,9,10,10

然后从中随机选择。

本教程使用多个剪切和粘贴解决方案在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]; } 

平原和公平。 只需复制/粘贴并测试它。

 /** * 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 />'; } 

编辑:在最后添加缺少的括号。

您可以使用非标准PHP库中的 weightedChoice 。 它接受成对的列表(物品,重量),以便能够使用不能成为数组键的物品。 您可以使用pairs函数将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倍。

由于我使用了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> 
 /** * @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。 我得到数组$ weight x的$ key。 之后,使用array_rand数组。 我有随机的价值​​;)。

我刚刚发布了一个类,轻松地进行加权排序 。

它基于Brad和Allain的答案中提到的相同的算法,并且针对速度进行了优化,单元测试用于均匀分布,并支持任何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(); 

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

函数getBucketFromWeights($ values){$ 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()调用的最大数量只能是数组中最大的权重(不要与limit混淆)。 对于这种情况,最大迭代次数= 2!

这种方法从来不需要重新排列整个阵列