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

Posted

技术标签:

【中文标题】在 PHP 中按权重生成随机结果?【英文标题】:Generating random results by weight in PHP? 【发布时间】:2010-10-01 12:11:46 【问题描述】:

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

【问题讨论】:

【参考方案1】:

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

【讨论】:

非常感谢这个布拉德。我只是在一个项目中使用它,它比我编写的其他函数更高效和灵活。 这太完美了!谢谢! $weightedValues 是否必须按升序排列才能正常工作? @chiborg 我最初也有同样的直觉,但事实证明数组不必全部排序。我通过多次调用该函数并验证数字在统计上是否匹配,在混洗加权数组上凭经验验证了它。无论顺序如何,他们都会这样做;结果是一致的。然而,顺序极大地影响了执行速度:最大的权重首先产生最大的速度,而最小的权重首先产生最差的性能。 这是一个类似的情况,以防有人了解这里发生了什么:***.com/a/12571681/470749【参考方案2】:

对于一个始终向标尺一端倾斜的有效随机数:

选择一个介于 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));

【讨论】:

我喜欢你的回答。请看看我的问题(下面的链接)。我很想听听你关于扩展这个的信息。 ***.com/questions/4030427/… 我知道这个问题很老了,但是$gamma 在这个 sn-p 中做了什么? @Optimus:它是一个加权因子:函数的输出是它的输入到 gamma 的幂,其中输入在 0 和 1 之间。例如,对于 gamma=0.5,你得到一个正方形根曲线,它比直线更快地从 0 向上弯曲,所以你得到更高的数字。参见 wiki 获取有关伽马曲线的信息(传统上用于图像校正目的) @Optimus: 0.218 是一个非常陡峭的 gamma; wiki 页面上的那个是 1/2.2 (0.455)。 我认为,伽马范围是gamma &gt; 0。在这种情况下,如果 gamma 为 =0,则永远返回 3!这不在1-2 范围内。所以,如果你想要数字幸运总是$max,伽玛必须像0.000001,幸运总是$min,伽玛必须像9999991 是正常行为。查看示例:phpio.net/s/5xxb【参考方案3】:

有一个pretty good tutorial for you。

基本上:

    将所有数字的权重相加。 选择一个小于那个的随机数 按顺序减去权重,直到结果为负,如果为负,则返回该数字。

【讨论】:

另外,这没有先前答案的内存开销(构建另一个具有所需分布的数组并从中随机选择) 这是非天真的黑客。 :-) 我知道这个方法是缩进的,但我的问题是为什么这个公式有效?【参考方案4】:

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

【讨论】:

这个答案和从中复制它的教程有缺陷,因为mt_rand() 的最小值不应该是0,它应该是1。这意味着第一个元素的权重将比预期的更受青睐。 [sandbox.onlinephpfunctions.com/code/… of issue)请编辑您的答案并删除狡猾的教程的超链接。 @mickmackusa - 我不再使用 PHP,但会做出改变......谢谢!【参考方案5】:
/**
 * @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'=&gt;10, 'B'=&gt;90));

这是一个非常简单的方法。如何获得随机加权元素。我填充数组变量 $key。我得到 $key 到数组 $weight x。之后,使用 array_rand 进行数组。而且我有随机值;)。

【讨论】:

【参考方案6】:

简单而公平。 只需复制/粘贴并测试它。

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

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

【讨论】:

抱歉,我发现有时alg有问题。 请注意不要使用相同的键。【参考方案7】:

您可以从Non-standard PHP library 使用weightedChoice。它接受对(项目,重量)的列表,以便有可能处理不能是数组键的项目。您可以使用pairs 函数将array(item =&gt; 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 倍。

【讨论】:

【参考方案8】:

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

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

它基于Brad's 和Allain'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】:

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

根据 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;
    

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


另一方面,如果您要对数组进行多个随机选择和/或您的数组足够大*(确保自己进行基准测试),那么重组数组可能会更好。

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

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

新数组需要通过将前一个元素的权重添加到当前元素的权重来将每个值的“权重”替换为“限制”。

然后翻转数组,使限制是数组键,值是数组值。 逻辑是:所选值将具有 >= $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】:

我用过这个:

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

它给出更多的低值和更少的高值,因为值越高,被 mt_rand 之一切除的越多

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

专业版:简单明了

CON:可能太简单了,所以对于某些用例来说不够加权或平衡

数学:

让第 i 个值的第 i 个索引从 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

...

【讨论】:

【参考方案12】:

我使用了 Brad 的答案并对其进行了一些修改以适应我的情况并增加了更多灵活性

我有一个带有数组值的数组

$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'];
    

【讨论】:

【参考方案13】:

函数 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这里的答案修改了这个

在我写完这篇文章后,我看到其他人的答案更加优雅。嘿嘿嘿嘿。

【讨论】:

以上是关于在 PHP 中按权重生成随机结果?的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server - 随机随机播放结果,但为每条记录分配权重

PHP---------生成随机数,日期时间函数

PHP的函数-----生成随机数日期时间函数

php poker 生成的随机结果与预期不符

Mark一个按照权重生成随机数方法

权重随机算法的java实现