生成随机数的算法

时间:2008-11-26 01:44:13

标签: php mysql algorithm random pseudocode

我希望生成一个随机数并将其发布到数据库中的表中,以用于特定的user_id。问题是,相同的数字不能使用两次。有一百万种方法可以做到这一点,但我希望非常热衷于算法的人能够在一个优雅的解决方案中巧妙地解决问题,因为满足以下条件:

1)对数据库进行的查询量最少。 2)在内存中进行数据结构爬行的次数最少。

基本上这个想法是做以下的

1)创建0到9999999之间的随机数 2)检查数据库以查看号码是否存在     OR
2)查询所有数字的数据库
3)查看返回的结果是否与来自db的任何内容匹配 4)如果匹配,重复步骤1,否则问题解决。

感谢。

17 个答案:

答案 0 :(得分:18)

没有你的算法不可扩展。我之前做的是串行发送数字(每次+1),然后通过XOR操作传递它们来混杂位,从而给我一个看似随机的数字。当然,它们并不是随机的,但它们对用户来说是如此。


[编辑] 其他信息

这个算法的逻辑是这样的,你使用一个已知的序列 生成唯一的数字,然后你确定地操纵它们, 所以他们不再看起来连续了。一般的解决方案是使用 某种形式的加密,在我的例子中是一个XOR触发器,因为 它尽可能快,并且它满足了数字的保证 绝不会碰撞。

但是,如果您想要更多,您可以使用其他形式的加密 随机查看数字,超速(说你不需要生成很多 一次ids)。现在是选择加密算法的重点 是“数字绝不会碰撞的保证”。并且一种证明加密算法是否能够实现的方法 这个保证是检查原始数字和结果 加密具有相同的位数,并且算法是 可逆(双射)。

[感谢 Adam Liss & CesarB 用于解决问题的方法]

答案 1 :(得分:17)

为什么不使用GUID?大多数语言应该有内置的方法来执行此操作。它保证是唯一的(具有非常合理的界限)。

答案 2 :(得分:6)

想要一个过顶的解决方案吗?

我认为随机性并不是加密质量,而是足以阻止用user_id来猜测用户的寿命。

在开发过程中,以字符串形式生成所有1000万个数字的列表。

可选地,执行一些简单的转换,例如向中间添加常量字符串。 (这是为了防止结果过于可预测。)

将它们传递给生成Perfect Hash functions的工具,例如gperf

生成的代码可用于在运行时将用户的id快速编码为唯一的哈希值,保证不会与任何其他哈希值冲突。

答案 3 :(得分:3)

尝试使用mysql中的语句 SELECT CAST(RAND()* 1000000 AS INT)

答案 4 :(得分:2)

假设:

  • 唯一性需要随机性,而不是安全性
  • 您的user_id是32位
  • 您的9999999限制只是一个例子

你可以做一些简单的事情,因为随机数为64位整数,高32位包含时间戳(在行插入时),低32位包含user_id。 即使对于具有相同用户的多行,这也是唯一的,前提是您在时间戳上使用适当的分辨率,具体取决于您为同一用户添加新行的频率。 与随机列上的唯一约束结合并捕获逻辑中的任何此类错误,然后重试。

答案 5 :(得分:1)

我的经验只是在PHP中使用RNG。我发现使用一定数量的数字(我使用的是int,所以我的最大值为4G)。我进行了一些测试,发现平均而言,在500,000次迭代中,我得到了120个单个重复项。在运行循环多次之后,我从来没有一式三份。我的“解决方案”就是插入并检查它是否失败,然后生成一个新的ID然后再去。

我的建议是做同样的事情,看看你的碰撞率是多少&c;看看它是否适合你的情况。

这不是最优的,所以如果有人有建议我也在寻找:)

编辑:我被限制为5位数ID([a-zA-z0-9] {5,5}),id越长(组合越多,碰撞次数越少)。例如,电子邮件的md5几乎不会发生冲突。

答案 6 :(得分:1)

有几种方法可以用这种方法构建一个数字为0000000到9999999的数组,然后在这个数组中选择这些数字的随机选择 并将拾取的数字值与最高值Max交换 然后将max减小1并选择此阵列的另一个随机成员直到新的最大值

每次将Max减少一个

例如(基本):(右边是应该在实际程序中删除的注释) Rndfunc是对你正在使用的随机数生成器函数的调用

dim array(0 to 9999999) as integer
for x% = 1 to 9999999
array(x%)=x%
next x%
maxPlus = 10000000
max =9999999
pickedrandom =int(Rndfunc*maxPlus)  picks a random indext of the array based on    
                                   how many numbers are left
maxplus = maxplus-1
swap array(pickedrandom) , array(max) swap this array value to the current end of the
                                     array 
max = max -1                   decrement the pointer of the max array value so it 
                              points to the next lowest place..

然后对你想要选择的每个数字继续这样做,但你需要选择使用非常大的数组

另一种方法如下:生成一个数字并将其存储到一个可以动态增长的数组中 然后选择一个新数字并将其与数组中第一个元素到最后一个元素的中间值进行比较,在这种情况下它将是第一个选中的数字 如果它匹配选择另一个随机数,根据大小对数组进行排序,如果没有匹配,那么根据天气它大于或小于你与它进行比较的数字,在列表中向上或向下上半部分距离的一半,每次它不匹配,并且大于或小于你比较它。

每次将它减半,直到间隙大小为1,然后检查一次并停止,因为没有匹配,然后将数字添加到列表中,列表按升序重新洗牌,依此类推等等直到你完成选择随机数...希望这有帮助...

答案 7 :(得分:1)

正确的PRNG(伪随机数发生器)算法将具有一个循环时间,在此期间它永远不会处于相同的状态。如果您将PRNG的整个状态暴露在从其中检索到的数字中,您将获得一个在发生器周期内保证唯一的数字。

执行此操作的简单PRNG称为'Linear Congruential'PRNG,它迭代公式:

X(i) = AX(i-1)|M

使用正确的一对因子,您可以从具有32位累加器的简单PRNG获得2 ^ 30(大约10亿)的周期。请注意,您需要一个64位长的临时变量来保存计算的中间“AX”部分。大多数(如果不是全部)C编译器都支持这种数据类型。您还应该能够在大多数SQL方言上使用数字数据类型。

使用正确的A和M值,我们可以得到具有良好统计和几何属性的随机数生成器。 Fishman和Moore写了一篇关于此的着名论文。

对于M = 2 ^ 31 - 1,我们可以使用下面的A值来获得一个长周期很长的PRNG(2 ^ 30 IIRC)。

A的良好价值观:

742,938,285  
950,706,376  
1,226,874,159  
62,089,911  
1,343,714,438   

请注意,此类型的生成器(根据定义)不具有加密安全性。如果您知道从中生成的最后一个数字,您可以预测它接下来会做什么。不幸的是,我认为您无法同时获得加密安全性和保证不可重复性。对于PRNG是加密安全的(例如Blum Blum Shub),它不能在生成的数字中暴露足够的状态以允许预测序列中的下一个数字。因此,内部状态比生成的数字宽,并且(为了具有良好的安全性),周期将比可以生成的可能值的数量长。这意味着曝光的数字在此期间不会是唯一的。

出于类似的原因,长期生成器也是如此,例如Mersenne Twister.

答案 8 :(得分:1)

如果您真的希望从0到9 999 999获得“随机”数字,那么解决方案是进行一次“随机化”,然后将结果存储到磁盘中。

获得你想要的结果并不难,但我认为它更像是“用数字制作一个长列表”,而不是“得到一个随机数”。

$array = range(0, 9999999);
$numbers = shuffle($array);

您还需要一个指向$ numbers中当前位置的指针(将其存储在数据库中);从0开始并在每次需要新号码时递增。 (或者你可以使用array_shift()或array_pop(),如果你不喜欢使用指针。)

答案 9 :(得分:1)

我之前写过an article about this。它采用与Robert Gould的答案相同的方法,但另外还展示了如何使用xor折叠将分组密码缩短到合适的长度,然后如何在不是2的幂的范围内生成排列,同时仍然保留独特性。

答案 10 :(得分:1)

我喜欢Oddthinking的想法,但不是选择世界上最强大的哈希函数,你可以简单地说:

  • 生成前1000万个数字的MD5(表示为字符串,+一些盐)
  • 检查重复离线,即在投入生产之前(我猜不会有任何重复)
  • 将副本存储在某个地方的数组中
  • 当您的应用程序启动时,加载数组
  • 如果要插入ID,请选择下一个数字,计算其MD5,检查它是否在数组中,以及是否不将其用作数据库中的ID。否则,请选择下一个号码

MD5很快,检查字符串是否属于数组会避免使用SELECT。

答案 11 :(得分:1)

设计一个长期不重复的伪随机数发生器很容易;例如this one,用于你想要的同样的东西。

顺便说一下,为什么不按顺序发出用户ID?

答案 12 :(得分:1)

问题在于,如果要生成随机数,则很有可能无限制地生成重复数据。

但是:

<?php
//Lets assume we already have a connection to the db
$sql = "SELECT randField FROM tableName";
$result = mysql_query($sql);
$array = array();
while($row = mysql_fetch_assoc($result))
 {
   $array[] = $row['randField'];
 }
while(True)
 {
   $rand = rand(0, 999999);
   if(!in_array($rand))
     {
       //This number is not in the db so use it!
       break;
     }
 }
?>

虽然这也会做你想要的,但这是一个坏主意,因为这不会长时间扩展,最终你的数组会变大,生成一个尚未生成的随机需要很长时间在您的数据库中。

答案 13 :(得分:1)

我想你会发现你真的不想这样做。随着数据库中的数字增加,您可能会在“确保未采用此数字”循环中花费太多时间。

就我个人而言,我已经幸运地选择了哈希作为替代方案,但为了找到更好的解决方案,我真的需要知道你为什么要这样做。

答案 14 :(得分:0)

PHP已经有了一个函数uniqid。它生成一个标准的uuid,如果你必须从其他地方访问数据,这是很好的。不要重新发明轮子。

答案 15 :(得分:0)

我可能没有抓住你的观点,但是auto_increments呢?

答案 16 :(得分:0)

如果你想确保随机数不重复,你需要一个非重复的随机数发生器(如here所述)。

基本思路是以下公式seed * seed & p将为任何输入生成非重复随机数x such that 2x < p,而p - x * x % p会产生所有其他随机数以及非重复,但仅在p = 3 mod 4时。所以你基本上只需要一个单一的原始数字,尽可能接近9999999。这样可以将工作量减少到单个读取字段,但缺点是生成的ID过大或生成的ID太少。

这种算法不能很好地进行排列,因此我建议将其与XOR或加法或其他方法相结合,以更改精确值,而不会破坏种子与其生成值之间的1对1关系。