Как сделать рандом с процентами?

Всем привет!
Хочу реализовать рандом с шансом выпадения с процентами на основе

$block_array = array('One','Two','Three');
            $rand = $block_array[array_rand($block_array)];

Как возможно реализовать что бы допустим One шанс выпадения - 70%, Two - 20% Three- 10?
  • Вопрос задан
  • 3784 просмотра
Решения вопроса 1
Maksclub
@Maksclub Куратор тега PHP
maksfedorov.ru
Можно для каждого значения сделать вес, можете сделать так, чтобы сумма все х весов была 100,
но не обязательно — это могут быть произвольные значения. Главное, что доля веса от суммы всех весов — будет вероятностью данного элемента.

Такой способ делает ваши значения управляемыми буквально одним параметром веса.
И не нужно шатать код.

Обновление от 13.06.2020:
Так вышло, что мой тимлид математик и нашел интересное и эффективное решение этой задачи
<?php

$values = [
    ['value' => 'One', 'weight' => 20],
    ['value' => 'Two', 'weight' => 30],
    ['value' => 'Three', 'weight' => 50]
];

function randByWeight(array $arr) {
    $max = 0;
    $result = [];
    foreach($arr as $value) {
        $rand = pow((mt_rand() / (mt_getrandmax() + 1)), 1/$value['weight']);
        if ($rand > $max) {
            $max = $rand;
            $result = $value;
        }
    } 

    return $result;
}

// Например: 'Three' выпадет в 50% случаев, тк его вес -- половина от суммы всех весов
// Например: 'Two' выпадет в 30% случаев, тк его вес -- 30% от суммы всех весов
var_dump(randByWeight($values));

sandbox.onlinephpfunctions.com/code/39de2d54a298de...

Чем хорошо это решение — его можно использовать прямо в SQL запросе!
SELECT * FROM table ORDER BY POWER(random(), 1/weight) DESC LIMIT 1


Классическое решение:
<?php

$values = [
    ['value' => 'One', 'weight' => 20],
    ['value' => 'Two', 'weight' => 30],
    ['value' => 'Three', 'weight' => 50]
];

function randByWeight(array $items) {
    $sum = array_reduce($items, function(int $acc, array $item): int {
        return $acc += $item['weight'];
    }, 0);
    
    $rand = (mt_rand() / (mt_getrandmax() + 1)) * $sum;

    foreach($items as $item) {
        if($rand < $item['weight']) {
            return $item;
        }
        
        $rand -= $item['weight'];
    }
}

// Например: 'Three' выпадет в 50% случаев, тк его вес -- половина от суммы всех весов
// Например: 'Two' выпадет в 30% случаев, тк его вес -- 30% от суммы всех весов
var_dump(randByWeight($values));

sandbox.onlinephpfunctions.com/code/aee9dca651213e...
Ответ написан
Пригласить эксперта
Ответы на вопрос 3
@alexalexes
Возьмите интервал генерации случайных чисел (от 0 до 99) и раскидайте сгенерированные числа по эквивалентным интервалам (0...69, 70...89, 90...99) - если число попало в опред. интервал, значит ширина интервала будет вероятностью попадания в него этого числа.
$rnd_number = rand(0, 99);
switch(true)
{
  case $rnd_number < 70: 
   echo "One";
    break;
 case $rnd_number >= 70 && $rnd_number < 90:
    echo "Two";
    break;
 case $rnd_number  >= 90:
    echo "Three";
    break;
}

Чтобы в этом убедиться, повторите эксперимент, например, 1000 раз:
$counter = array('One'=> 0, 'Two' => 0, 'Three' => 0);
for($i = 0; $i < 1000; $i++)
{
  $rnd_number = rand(0, 99);
  switch(true)
  {
    case $rnd_number < 70: 
     $counter["One"]++;
      break;
   case $rnd_number >= 70 && $rnd_number < 90:
      $counter["Two"]++;
      break;
   case $rnd_number  >= 90:
      $counter["Three"]++;
      break;
  }
}
var_dump($counter);

Если хотите эксплуатировать именно функцию array_rand(), то ей нужно предоставить такой массив:
$block_array = array('One','One','One','One','One','One','One','Two','Two','Three');
$counter = array('One'=> 0, 'Two' => 0, 'Three' => 0);
for($i = 0; $i < 1000; $i++)
{
 $gen_value = array_rand($block_array);
 $counter[$gen_value]++;  
}
var_dump($counter);

Вес каждого элемента увеличивается путем его тиражирования пропорционально вероятности его выпадания.
Решение не будет отличаться от классики.
Ответ написан
Комментировать
edward_freedom
@edward_freedom
Сделай массив элементов. Туда добавляй столько элементов, сколько у него шанс выпадения. One - 70 раз, Two - 20 раз. И рандомно выбирай из массива элемент
$items_set = array();
		foreach ( $items as $item_id => $item ) {
			for ( $i = 0; $i < $item['chance']; $i ++ ) {
				$items_set[] = $item_id;
			}
		}
		$rand_id = $items_set[array_rand( $items_set )];

		$rand_item = $items[$rand_id];

		return $rand_item["id"];
Ответ написан
Комментировать
xmoonlight
@xmoonlight
https://sitecoder.blogspot.com
https://ideone.com/KfOhEh
<?php
$a=['1','2','3'];
foreach(range(1,100) as $i)
	switch ($s=random_int ( 1 , 100 )) {
		case $s<=10:echo $a[2];break;//10%
		case $s>=81:echo $a[1];break;//20%
		default: echo $a[0];//70%
	}

Out:
2121121311211212211131111111111111111111211112112211113211111112111111111311111121111112211112211212
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы