散列函数(如SHA1)的随机性


Randomness of hashing functions such as SHA1

我正在尝试基于用户ID生成随机数的偶数分布。也就是说,我希望每个用户的随机数在用户请求随机数时保持不变(但用户不需要存储该数字)。对于给定的大型用户ID数组$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中的每个值都是唯一的,但列表中的一个条目总是比其他条目获得更多的活动。有没有更好的方法来生成一个字符串的哈希值,从而给出近似均匀的分布?显然,夏不能胜任这份工作。我也尝试过MD5和一个简单的crc32,结果都一样!?

我疯了吗?事实上,唯一的解释是我没有验证$arr中的每个条目都是唯一的吗?

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

您可以使用sha1作为基于用户id的简单随机数生成器。

在十六进制中,分布接近完美:

//  $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

mt_rand()应该在请求的范围内具有非常均匀的分布。创建用户时,使用mt_rand()为该用户创建一个随机种子,然后始终使用该用户的种子创建mt_srand()

要获得从0到99的均匀分布,例如,只需mt_rand(0,$range-1)。使用sha1、md5或其他哈希算法并不能真正给你带来比直接随机更均匀的分布。

如果你发布的结果让你得出结论,认为你没有得到合适的分布,这将是有帮助的,但这可能是这里发生的三件事之一:

  1. 你只是看到了太小的样本,和/或你错过了对数据的解释。正如其他人所评论的那样,均匀分布没有完全均匀的输出是完全合理的。

  2. 如果使用mt_rand而不是rand,效果会更好。

  3. (就我个人而言,我认为这是最有可能的)你过度优化了你的种子生成,丢失了数据/鸽子洞/以其他方式损害了你生成随机数的能力。阅读你的代码,我认为你在做以下事情:

    1. 生成未知值的统一随机哈希
    2. 将散列拆分为长散列,并按位对其进行异或运算
    3. 设置rand的种子,并根据该种子生成一个随机数

    但是你为什么要做第二步呢?你认为你从中得到了什么好处?试着迈出这一步,只需使用从哈希中提取的第一个值作为种子,看看这是否不会给你带来更好的结果。随机性的好经验法则-不要试图智胜实施算法的人,这是不可能做到的:)

虽然这里的所有答案都很好,但我会提供对我来说正确的答案,那就是我确实疯了。显然,uniq命令并没有像我预期的那样工作(数据需要首先排序)。因此,解释确实是$arr中的值不是唯一的。