Генерирование случайных результатов по весу в PHP? - PullRequest
53 голосов
/ 15 января 2009

Я знаю, как генерировать случайное число в PHP, но допустим, что я хочу случайное число в диапазоне от 1 до 10, но мне нужно больше 3,4,5, чем 8,9,10. Как это возможно? Я бы опубликовал то, что я пробовал, но, честно говоря, я даже не знаю, с чего начать.

Ответы [ 12 ]

91 голосов
/ 09 августа 2012

Исходя из ответа @ Allain / 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;
      }
    }
  }
28 голосов
/ 15 января 2009

Для эффективного случайного числа, смещенного последовательно к одному концу шкалы:

  • Выберите непрерывное случайное число от 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));
22 голосов
/ 15 января 2009

Есть довольно хороший учебник для вас .

В основном:

  1. Суммируйте веса всех чисел.
  2. Выберите случайное число меньше, чем
  3. вычитайте веса по порядку, пока результат не станет отрицательным, и верните это число, если оно есть.
11 голосов
/ 15 января 2009

Наивный взлом для этого будет построить список или массив, как

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

А затем выберите случайным образом из этого.

6 голосов
/ 19 мая 2010

Из этого туториала вы узнаете об этом на 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]; 
}
2 голосов
/ 24 июня 2016
/**
 * @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. Я получаю $ ключ для массива $ вес х. После этого используйте array_rand для массива. И у меня есть случайное значение;).

2 голосов
/ 25 декабря 2015

Вы можете использовать weightedChoice из Нестандартная библиотека 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 будут появляться в 3 раза чаще, чем 7-10.

2 голосов
/ 01 мая 2012

Добрая и справедливая. Просто скопируйте / вставьте и протестируйте его.

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

Редактировать: добавлена ​​пропущенная скобка в конце.

1 голос
/ 17 апреля 2017

Многие ответы на этой странице, кажется, используют раздувание массива, чрезмерную итерацию, библиотеку или трудный для чтения процесс. Конечно, все думают, что их собственный ребенок - самый милый, но я, честно говоря, считаю, что мой подход прост, прост и легко читается / изменяется ...

В соответствии с 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. увеличивается число случайных выборов.

Новый массив требует замены «weight» на «limit» для каждого значения путем добавления веса предыдущего элемента к весу текущего элемента.

Затем переверните массив так, чтобы ограничения были ключами массива, а значения - значениями массива. Логика такова: выбранное значение будет иметь самый низкий предел:> = $ 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() очень быстр, и максимальное количество вызовов isset() в цикле while может составлять только максимальный вес (не путать с лимитом) в массиве. Для этого случая максимальные итерации = 2!

ЭТОМУ ПОДХОДУ НИКОГДА НЕ НУЖНО ИТЕРИРОВАТЬ ВСЮ Массив

1 голос
/ 01 апреля 2016

Я только что выпустил класс , чтобы легко выполнять взвешенную сортировку .

Он основан на том же алгоритме, который указан в ответах Брэда и * Аллена , оптимизирован для скорости, проверен на единицу для равномерного распределения и поддерживает элементы любого типа PHP. 1009 *

Использовать это просто. Подтвердите это:

$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();

Вероятность получения одного из элементов зависит от его веса. Единственное ограничение - веса должны быть целыми числами.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...