programing

PHP에서 가중치로 무작위 결과를 생성합니까?

sourcetip 2021. 1. 16. 11:18
반응형

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, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 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]; 
}

/**
 * @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를 사용하여 배열하십시오. 그리고 나는 임의의 값이 있습니다.).


평범하고 공정합니다. 복사 / 붙여 넣기 만하면됩니다.

/**
 * 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사용할 수 있습니다 . 배열 키가 될 수없는 항목으로 작업 할 수 있도록 쌍 (항목, 가중치) 목록을 허용합니다. 함수를 사용 하여 필요한 형식으로 변환 할 수 있습니다 .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>

가중 정렬을 쉽게 수행하기 위해 방금 클래스를 출시했습니다 .

BradAllain의 답변에 언급 된 동일한 알고리즘을 기반으로하며 속도에 최적화되어 있고 균일 한 배포를 위해 단위 테스트를 거치며 모든 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();

요소 중 하나를 얻을 확률은 관련 가중치에 따라 다릅니다. 유일한 제한은 가중치가 정수 여야한다는 것입니다.


이 페이지의 많은 답변은 배열 팽창, 과도한 반복, 라이브러리 또는 읽기 어려운 프로세스를 사용하는 것 같습니다. 물론 모든 사람들은 자신의 아기가 가장 귀엽다고 생각하지만 솔직히 내 접근 방식은 가늘고 간단하며 읽기 / 수정하기 쉽다고 생각합니다.

Per the OP, I will create an array of values (declared as keys) from 1 to 10, with 3, 4, and 5 having double the weight of the other values (declared as values).

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

If you are only going to make one random selection and/or your array is relatively small* (do your own benchmarking to be sure), this is probably your best bet:

$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;
    }
}

This approach involves no array modification and probably won't need to iterate the entire array (but may).


On the other hand, if you are going to make more than one random selection on the array and/or your array is sufficiently large* (do your own benchmarking to be sure), restructuring the array may be better.

The cost in memory for generating a new array will be increasingly justified as:

  1. array size increases and
  2. number of random selections increases.

The new array requires the replacement of "weight" with a "limit" for each value by adding the previous element's weight to the current element's weight.

Then flip the array so that the limits are the array keys and the values are the array values. The logic is: the selected value will have the lowest limit that is >= $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);

Creates this array:

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

Now to generate the random $pick and select the value:

// $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

This approach is brilliant because isset() is very fast and the maximum number of isset() calls in the while loop can only be as many as the largest weight (not to be confused with limit) in the array. For this case, maximum iterations = 2!

THIS APPROACH NEVER NEEDS TO ITERATE THE ENTIRE ARRAY


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;

}

I ugh modified this from an answer here Picking random element by user defined weights

After I wrote this I saw someone else had an even more elegant answer. He he he he.

ReferenceURL : https://stackoverflow.com/questions/445235/generating-random-results-by-weight-in-php

반응형