明滅するプログラマの思索

WEBエンジニアとして勤務している一介の男が、日々気づいたことをまとめるブログです

確率による重みづけをしてn個のものを取り出す

X種類のフルーツ中からランダムにn種類取り出す場合、PHPなら mt_rand() などの関数を使えばすぐに実装可能です。

実装条件

ソフトウェア バージョン
PHP 5.6.30
<?php
$X = [
  'orange',
  'apple',
  'banana',
  'melon',
  'pear',
  'pineapple'
];

$n = 3; // 3種類取り出す場合
$result = [];
for ($i = 0; $i < $n; $i++) {
  $key = mt_rand(0, count($X) - 1);
  $result[] = $X[$key];
  array_splice($X, $key, 1);
}
print_r($result);

for() 構文を使って、n 回の取り出しをします。
取り出されたフルーツは array_splice() 関数を使用して $X から削除しています。

では、それぞれに出現確率を設定して取り出す場合、どのようにすると良いでしょうか。

確率による重みづけをした場合

<?php
$X = [
  ['name' => 'orange', 'percent' => 10],
  ['name' => 'apple', 'percent' => 30],
  ['name' => 'banana', 'percent' => 5],
  ['name' => 'melon', 'percent' => 15],
  ['name' => 'pear', 'percent' => 25],
  ['name' => 'pineapple', 'percent' => 15]
];

$n = 3; // 3種類取り出す場合
$result = [];
for ($i = 0; $i < $n; $i++) {
  $score = 0;  // 各フルーツの重みを加算するためのカウンター
  foreach ($X as $key => $fruits_data) {
    $score += $fruits_data['percent'];
    $X[$key]['score'] = $score;
  }
  $rand = mt_rand(0, $score);
  foreach ($X as $key => $fruits_data) {
    if ($fruits_data['score'] >= $rand) {
      $result[] = $fruits_data['name'];
      array_splice($X, $key, 1);
      break;
    }
  }
}
print_r($result);

今回のサンプルでは配列 $X に name と percent を持たせています。percent は確率(%)を表しています。
n 回のループの中で、foreach() 構文が2回出てきます。
1回目の foreach() では、各フルーツに score というパラメータを割り振っています。
score の最大値はすべての percent の合計値となります。
0 から score までの間の数値を mt_rand() で生成します。
そして2回目の foreach() で、それがどのフルーツに当たっているかを判定しています。

このロジックでは percent の合計値が必ずしも 100 である必要はありません。
ピッタリ100(%)であれば、各フルーツに設定されている percent は実際の確率と同値となります。