3v4l.org

run code in 300+ PHP versions simultaneously
<?php /** * 与えられたアイテムの累積重みを計算します。 * * @param array<int|float> $items アイテムとその重みを格納した配列。キーにアイテム、値に重み * @return array{0: array<int|float>, 1: int|float} 累積重みの配列と総重み。累積重みの配列はキーにアイテム、値に累積重み */ function prepare_cumulative_weights(array $items): array { // 累積重みの配列を初期化 $cumulative_weights = []; // 重みの総和を初期化 $total = 0.0; // 各アイテムに対して累積重みを計算 foreach ($items as $item => $weight) { $total += $weight; $cumulative_weights[$item] = $total; } // 累積重みの配列と総重みを返す return [$cumulative_weights, $total]; } /** * 累積重みを基にランダムにアイテムを選択します。 * * @param array<int|float> $cumulative_weights 累積重みの配列 * @param float|int $total 総重み * @param string $algo アルゴリズム名。binary_search, linear_search * @return float|int|string 選択されたアイテムのキー */ function weighted_random_pick(array $cumulative_weights, float|int $total, string $algo = 'binary_search'): float|int|string { // ランダムな数を生成 $num = mt_rand() / mt_getrandmax() * $total; // 生成された数に基づいてアイテムを探索して選択 return $algo($cumulative_weights, $num); } // 何度も参照する値のキャッシュ置き場 class WeightRandomCacheStorage { public static array|null $arrayValues; public static array|null $arrayKeys; public static function clear(): void { self::$arrayValues = null; self::$arrayKeys = null; } } /** * 二分探索を行い、指定した値以上の最初の要素を見つけます。 * * @param array<float|int> $array 検索対象の配列。昇順で累積重みが値に入っている値 * @param float|int|string $value 検索する値 * @return float|int|string 指定した値以上の最初の要素 */ function binary_search(array $array, float|int|string $value): float|int|string { WeightRandomCacheStorage::$arrayValues ??= array_values($array); WeightRandomCacheStorage::$arrayKeys ??= array_keys($array); $left = 0; $right = count(WeightRandomCacheStorage::$arrayValues) - 1; // 二分探索 while ($left <= $right) { $middle = floor(($left + $right) / 2); if (WeightRandomCacheStorage::$arrayValues[$middle] == $value) { return $middle; } if (WeightRandomCacheStorage::$arrayValues[$middle] < $value) { $left = $middle + 1; } else { $right = $middle - 1; } } // $valueを超える最初の要素の値を返す return WeightRandomCacheStorage::$arrayKeys[$left]; } /** * 線形探索を行い、指定した値以上の最初の要素を見つけます。 * * @param array<float|int> $array 検索対象の配列。昇順で累積重みが値に入っている値 * @param float|int|string $value 検索する値 * @return float|int|string 指定した値以上の最初の要素 */ function linear_search(array $array, float|int|string $value): float|int|string { global $arrayV, $arrayK; $arrayV ??= array_values($array); $arrayK ??= array_keys($array); // 線形探索 for ($i = 0; $i < count($arrayV); ++$i) { if ($arrayV[$i] >= $value) { return $arrayK[$i]; // $value以上の最初の要素のキーを返す } } return $arrayK[$i]; // 指定された値以上の要素が見つからない場合 } /** * 与えられた回数だけアイテムをランダムに選択します。 * * @param array<int|float> $items アイテムとその重みを格納した配列。キーにアイテム、値に重み * @param int $count ランダムに選択する回数 * @param string|null $algo アルゴリズム名。binary_search, linear_search * @return array<int> 選択された各アイテムの数を格納した配列。キーにアイテム、値に選択された回数 */ function some_weighted_random_pick(array $items, int $count, string|null $algo = null): array { if(!isset($algo)){ // 要素が少ないなら線形探索、多いなら二分探索 $algo = count($items) < 50 ? 'linear_search' : 'binary_search'; } // 累積重みと総重みを計算 list($cumulative_weights, $total) = prepare_cumulative_weights($items); // 結果を格納する配列を初期化 $results = array_combine(array_keys($items), array_fill(0, count($items), 0)); // 指定された回数だけランダムにアイテムを選択 for ($i = 0; $i < $count; ++$i) { $result = weighted_random_pick($cumulative_weights, $total, $algo); ++$results[$result]; } WeightRandomCacheStorage::clear(); // 選択結果を返す return $results; } /** * 一度だけアイテムをランダムに選択します。 * * @param array<int|float> $items アイテムとその重みを格納した配列。キーにアイテム、値に重み * @param string $algo アルゴリズム名。binary_search, linear_search * @return float|int|string 選択されたアイテムのキー */ function single_weighted_random_pick(array $items, string $algo = 'binary_search'): float|int|string { // 累積重みと総重みを計算 list($cumulative_weights, $total) = prepare_cumulative_weights($items); // ランダムにアイテムを選択 $ret = weighted_random_pick($cumulative_weights, $total, $algo); WeightRandomCacheStorage::clear(); return $ret; } /** 使用例 **/ // ランダム対象 $items = []; // どれが選ばれたのか集計する配列 $results = []; for ($i = 0; $i < 10; ++$i) { $items["item$i"] = [ 'name' => "item$i", 'weight' => random_int(1, 1e5), ]; $results[$i] = 0; } // アイテムをシャッフル shuffle($items); // 実行するランダム選択回数 $num_trials = 1e5; foreach([null, 'linear_search', 'binary_search',] as $algo) { // 処理開始時間を記録 & ランダムなアイテムの選択を繰り返す $start = microtime(true); $results = any_item_some_weighted_random_pick($items, $num_trials, static fn($item)=>$item['weight'], $algo); $end = microtime(true); // 処理時間を表示 $algo ??= 'auto_algo_select'; echo $algo . ' Time: ' . ($end - $start) . "\n"; // 選択結果を表示 foreach($results as $item => $count) { echo "key:\t$item\tweight:\t{$items[$item]['weight']}\trate:\t" . ($count / $num_trials) . "\n"; } } function any_item_some_weighted_random_pick(array $items, int $count, callable|null $weightGetter = null, string|null $algo = null) { $_items = []; if(isset($weightGetter)) { foreach($items as $key => $item) { $_items[$key] = $weightGetter($item); } } else { $_items = $items; } return some_weighted_random_pick($_items, $count, $algo); }
Output for git.master_jit
auto_algo_select Time: 0.026458024978638 key: 0 weight: 3791 rate: 0.0082 key: 1 weight: 26242 rate: 0.05639 key: 2 weight: 65438 rate: 0.14224 key: 3 weight: 54542 rate: 0.118 key: 4 weight: 39605 rate: 0.08528 key: 5 weight: 41077 rate: 0.08934 key: 6 weight: 92286 rate: 0.20006 key: 7 weight: 66524 rate: 0.14484 key: 8 weight: 62223 rate: 0.13314 key: 9 weight: 10640 rate: 0.02251 linear_search Time: 0.026773929595947 key: 0 weight: 3791 rate: 0.0086 key: 1 weight: 26242 rate: 0.05619 key: 2 weight: 65438 rate: 0.14195 key: 3 weight: 54542 rate: 0.118 key: 4 weight: 39605 rate: 0.08541 key: 5 weight: 41077 rate: 0.08885 key: 6 weight: 92286 rate: 0.19977 key: 7 weight: 66524 rate: 0.14457 key: 8 weight: 62223 rate: 0.13273 key: 9 weight: 10640 rate: 0.02393 binary_search Time: 0.057395935058594 key: 0 weight: 3791 rate: 0.00802 key: 1 weight: 26242 rate: 0.05754 key: 2 weight: 65438 rate: 0.14067 key: 3 weight: 54542 rate: 0.11724 key: 4 weight: 39605 rate: 0.08655 key: 5 weight: 41077 rate: 0.08873 key: 6 weight: 92286 rate: 0.20009 key: 7 weight: 66524 rate: 0.1431 key: 8 weight: 62223 rate: 0.13481 key: 9 weight: 10640 rate: 0.02325
Output for git.master
auto_algo_select Time: 0.025741100311279 key: 0 weight: 35686 rate: 0.06331 key: 1 weight: 85284 rate: 0.15156 key: 2 weight: 48677 rate: 0.08635 key: 3 weight: 81046 rate: 0.14529 key: 4 weight: 19310 rate: 0.03412 key: 5 weight: 92576 rate: 0.16596 key: 6 weight: 25724 rate: 0.04543 key: 7 weight: 65398 rate: 0.11666 key: 8 weight: 69220 rate: 0.12367 key: 9 weight: 38243 rate: 0.06765 linear_search Time: 0.027000904083252 key: 0 weight: 35686 rate: 0.06417 key: 1 weight: 85284 rate: 0.15272 key: 2 weight: 48677 rate: 0.0872 key: 3 weight: 81046 rate: 0.14338 key: 4 weight: 19310 rate: 0.03355 key: 5 weight: 92576 rate: 0.16465 key: 6 weight: 25724 rate: 0.04589 key: 7 weight: 65398 rate: 0.11559 key: 8 weight: 69220 rate: 0.12381 key: 9 weight: 38243 rate: 0.06904 binary_search Time: 0.055678844451904 key: 0 weight: 35686 rate: 0.06393 key: 1 weight: 85284 rate: 0.15006 key: 2 weight: 48677 rate: 0.08726 key: 3 weight: 81046 rate: 0.1446 key: 4 weight: 19310 rate: 0.03418 key: 5 weight: 92576 rate: 0.16623 key: 6 weight: 25724 rate: 0.04677 key: 7 weight: 65398 rate: 0.1166 key: 8 weight: 69220 rate: 0.12245 key: 9 weight: 38243 rate: 0.06792

This tab shows result from various feature-branches currently under review by the php developers. Contact me to have additional branches featured.

Active branches

Archived branches

Once feature-branches are merged or declined, they are no longer available. Their functionality (when merged) can be viewed from the main output page


preferences:
27.05 ms | 410 KiB | 5 Q