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

时间:2009-01-15 00:44:38

标签: php random

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

12 个答案:

答案 0 :(得分:91)

基于@ Allain的answer / link,我用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;
      }
    }
  }

答案 1 :(得分:28)

对于一个有效的随机数,一直向着标度的一端倾斜:

  • 选择0..1
  • 之间的连续随机数
  • 升力γ,偏置它。 1是未加权的,lower表示更高的数字,反之亦然
  • 缩放到所需范围并舍入为整数

例如。在PHP(未经测试):

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

答案 2 :(得分:22)

pretty good tutorial for you

基本上:

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

答案 3 :(得分:11)

天真的黑客攻击是建立一个像

这样的列表或数组

1,2,3,3,3,3,3,4,4,4,4,4,5,5,5,5,5,6,6,7,7,8,8 ,8,9,9,10,10

然后从中随机选择。

答案 4 :(得分:6)

This tutorial引导您完成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]; 
}

答案 5 :(得分:2)

平淡而公平。 只需复制/粘贴并测试即可。

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

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

答案 6 :(得分:2)

您可以使用weightedChoice中的Non-standard PHP library。它接受一对对象列表(项目,权重),以便可以使用不能是数组键的项目。您可以使用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倍。

答案 7 :(得分:2)

/**
 * @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到数组。我有随机值;)。

答案 8 :(得分:1)

由于我使用了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>

答案 9 :(得分:1)

我刚刚轻松发布了class to perform weighted sorting

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

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

答案 10 :(得分:1)

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

根据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. 随机选择的数量增加。
  3. 新数组需要通过将前一个元素的权重添加到当前元素的权重来替换每个值的“权限”“权重”。

    然后翻转数组,使限制为数组键,值为数组值。 逻辑是:所选值的最低限制是&gt; = $ 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!

    这种方法无需彻底改变整个阵列

答案 11 :(得分:0)

function 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;

}

我在这里通过答案修改了这个Picking random element by user defined weights

在我写完之后,我看到其他人的答案更加优雅。他是他。