我正在尝试根据用户ID生成随机数的均匀分布。也就是说,我希望每个用户的随机数在用户请求随机数时保持不变(但用户不需要存储该数字)。对于给定的大型userID $arr
,我当前用于计算分布的算法(在PHP中)是:
$range = 100;
$results = array_fill(0, $range, 0);
foreach ($arr as $userID) {
$hash = sha1($userID,TRUE);
$data = unpack('L*', $hash);
$seed = 0;
foreach ($data as $integer) {
$seed ^= $integer;
}
srand($seed);
++$results[rand(0, $range-1)];
}
人们希望这会产生近似均匀的分布。但事实并非如此!我已经检查过以确保$arr
中的每个值都是唯一的,但列表中的一个条目总是比其他条目获得更多的活动。有没有更好的方法来生成一个字符串的散列,它会产生近似均匀的分布?显然SHA不能胜任这项工作。我也试过MD5和一个简单的crc32,都有相同的结果!?
$arr
中的每个条目都是唯一的吗?
答案 0 :(得分:4)
sha1哈希数分布非常均匀。执行此操作后:
<?php
$n = '';
$salt = 'this is the salt';
for ($i=0; $i<100000; $i++) {
$n .= implode('', unpack('L*', sha1($i . $salt)));
}
$count = count_chars($n, 1);
$sum = array_sum($count);
foreach ($count as $k => $v) {
echo chr($k)." => ".($v/$sum)."\n";
}
?>
你得到这个结果。每个数字的概率:
0 => 0.083696057956298
1 => 0.12138983759522
2 => 0.094558704004335
3 => 0.07301783188663
4 => 0.092124978934097
5 => 0.088623772577848
6 => 0.11390989553446
7 => 0.092570936094051
8 => 0.12348330833868
9 => 0.11662467707838
您可以根据用户的ID使用sha1作为简单的随机数生成器。
在十六进制中,分布接近完美:
// $n .= sha1($i . $salt, false);
0 => 0.06245515
1 => 0.06245665
2 => 0.06258855
3 => 0.0624244
4 => 0.06247255
5 => 0.0625422
6 => 0.0625246
7 => 0.0624716
8 => 0.06257355
9 => 0.0625005
a => 0.0625068
b => 0.0625086
c => 0.0624463
d => 0.06250535
e => 0.06250895
f => 0.06251425
答案 1 :(得分:1)
mt_rand()
应在所请求的范围内分布均匀。创建用户后,使用mt_rand()
为该用户创建随机种子,然后始终mt_srand()
使用该用户的种子。
要获得从0到99的均匀分布,仅作为示例,只需mt_rand(0,$range-1)
。使用sha1,md5或其他一些散列算法做一些技巧,实际上不会比直接随机分配更均匀。
答案 2 :(得分:0)
如果您发布的结果会让您得出结论认为您没有获得适当的分发,那会很有帮助,但这可能是以下三种情况之一:
您只是看着太小的样本,和/或您错过了解释您的数据。正如其他人所评论的那样,均匀分布完全没有完全统一的输出是完全合理的。
如果您使用mt_rand
代替rand
,则效果会更好。
(就个人而言,我认为这很有可能)你过度优化种子生成,丢失数据/鸽子/否则会损害你生成随机数的能力。阅读你的代码,我认为你正在做以下事情:
rand
的种子,并从该种子生成随机数但你为什么要做第2步呢?你认为从中得到了什么好处?尝试采取这一步骤,只需使用从哈希中提取的第一个值作为种子,并查看这是否不会给您带来更好的结果。具有随机性的良好经验法则 - 不要试图超越实施算法的人,不能做到:)
答案 3 :(得分:0)
虽然这里的所有答案都很好,但我会提供对我来说正确的答案,那就是我确实疯了。实际上,uniq
命令实际上并不像我预期的那样工作(数据需要先排序)。所以解释的确是$arr
中的值不是唯一的。