您想要的可以描述为“加权概率分布”或技术上的“discrete distribution”和“Categorical distribution”:
分类分布是一种离散概率分布,它描述了一个随机变量的可能结果,该随机变量可以处理 K 个可能的基本事件之一,并分别指定每个基本事件的概率。
-- 维基百科
假设您有一个random variable with uniform distribution in the range 0 to 1,您可以使用Inverse Method 构建您想要的任何发行版。
您的第一步是规范化分布。这意味着要确保曲线下方的面积等于 1(即您的体重总和不超过 100%)。对于离散分布,这意味着确保权重之和等于 1。 【相当于取值作为向量,计算同方向的单位向量】,取每个值除以值之和即可。
因此,你从这里开始:
(original)
user 1 = 3
user 2 = 1
user 3 = 6
到这里:
sum = 3 + 1 + 6 = 10
(normalized)
user 1 = 3 / 10 = 0.3
user 2 = 1 / 10 = 0.1
user 3 = 6 / 10 = 0.6
接下来,获取累积分布。也就是说,对于每个值,您不想要它的(标准化)权重,而是它的权重加上所有以前的权重。
因此,你从这里出发
(normalized)
user 1 = 0.3
user 2 = 0.1
user 3 = 0.6
到这里:
(cumulative)
user 1 = 0.3
user 2 = 0.1 + 0.3 = 0.4
user 3 = 0.6 + 0.3 + 0.1 = 1
最后,你得到你的random variable with uniform distribution in the range 0 to 1 并检查它低于哪个值:
$r = (float)rand()/(float)getrandmax();
if ($r <= 0.3) return "user 1"; // user 1 = 0.3
else if ($r <= 0.4) return "user 2"; // user 2 = 0.4
else return "user 3"; // user 3 = 1
注意:范围包括在内,因为PHP is weird。
好的,一口气,在(丑陋的)PHP中:
$p = ['user 1' => 3, 'user 2' => 1, 'user 3' => 6];
$s = array_sum($p);
$n = array_map(function($i) use ($p, $s){return $i/$s;},$p);
$a = []; $t = 0;
foreach($n as $k => $i) {$t += $i; $a[$k] = $t;}
$r = (float)rand()/(float)getrandmax();
foreach($a as $k => $i) { if ($r <= $i) return $k; }
Try online.
让我们在 MySQL 中重新实现因为原因。
首先我们需要一个带有输入的表格,例如:
SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`
然后我们将值相加
SELECT sum(chance) FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input
然后我们归一化
SELECT id, chance / sum FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input CROSS JOIN (SELECT sum(chance) as sum FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input) s
然后我们累积
SELECT id, chance / sum as sum, (@tmp := @tmp + chance / sum) as csum FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input CROSS JOIN (SELECT sum(chance) as sum FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input) s CROSS JOIN (SELECT @tmp := 0) cheat
然后我们选择
SELECT id from (
SELECT id, chance / sum as sum, (@tmp := @tmp + chance / sum) as csum FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input CROSS JOIN (SELECT sum(chance) as sum FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input) s CROSS JOIN (SELECT @tmp := 0) cheat) a
CROSS JOIN (SELECT RAND() as r) random
WHERE csum > r
LIMIT 1
Try online.