web-dev-qa-db-ja.com

PHPでランダムな結果を重みで生成していますか?

PHPで乱数を生成する方法を知っていますが、1〜10の乱数が必要だが、3、4、5よりも8、9、10の乱数が必要だとしましょう。これはどうですか私が試したものを投稿しますが、正直なところ、どこから始めればよいかさえわかりません。

53
kyct

@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;
      }
    }
  }
92
Brad

効率的な乱数がスケールの一端に向かって常に偏っている場合:

  • 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));
29
bobince

あなたのためのかなり良いチュートリアル があります。

基本的に:

  1. すべての数値の重みを合計します。
  2. それよりも小さい乱数を選びます
  3. 結果が負になるまで順番に重みを減算し、負の場合はその数を返します。
22
Allain Lalonde

これに対する素朴なハックは、次のようなリストまたは配列を作成することです

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

そして、その中からランダムに選択します。

11
Iain Holder

このチュートリアル は、複数のカットアンドペーストソリューションを使用して、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]; 
}
6
Brad Parks

weightedChoice from 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倍の頻度で表示されます。

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を入力します。配列$ weight xの$ keyを取得します。その後、array_Randを使用して配列します。そして、私はランダムな値を持っています;)。

2
Lukáš Kříž

プレーンでフェア。コピー/貼り付けしてテストするだけです。

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

編集:不足しているブラケットを最後に追加しました。

2
Nick Olszanski

私は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>
1
degenerate

加重ソートを実行するクラス を簡単にリリースしました。

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

要素の1つを取得する確率は、関連する重みに依存します。唯一の制限は、重みは整数でなければならないということです。

1
Benjamin

このページの回答の多くは、配列の肥大化、過度の反復、ライブラリ、または読みにくいプロセスを使用しているようです。もちろん、誰もが自分の赤ちゃんが一番かわいいと思っていますが、正直なところ、私のアプローチは無駄がなく、シンプルで読みやすく、変更しやすいと思います...

OPごとに、1から10までの値(キーとして宣言)の配列を作成します。3、4、および5は、他の値(値として宣言)の2倍の重みを持ちます。

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

ランダムな選択を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. ランダム選択の数が増加します。

新しい配列では、前の要素の重みを現在の要素の重みに追加することにより、各値の「制限」で「重み」を置き換える必要があります。

次に、配列を反転して、制限が配列のキーになり、値が配列の値になるようにします。ロジックは次のとおりです。選択された値には、$ 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!

このアプローチでは、アレイ全体を反復する必要はありません

1
mickmackusa

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;

}

私はこれをここの答えから修正しました ユーザー定義の重みによるランダム要素の選択

これを書いた後、他の誰かがもっとエレガントな答えを持っているのを見ました。彼彼彼彼。

0
user4951