8

我正在寻找生成一个随机数并将其发布到数据库中特定 user_id 的表中。问题是,同一个号码不能使用两次。有一百万种方法可以做到这一点,但我希望非常热衷于算法的人有一种巧妙的方法来解决问题,因为它满足以下标准:

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

本质上,这个想法是执行以下操作

1)创建一个从 0 到 9999999 的随机数
2)检查数据库以查看该数字是否存在

2)查询数据库中的所有数字
3)查看返回的结果是否与来自 d​​b 的任何
内容匹配 4)如果匹配,请重复第1步,如果没有,问题就解决了。

谢谢。

4

17 回答 17

18

不,您的算法不可扩展。我之前所做的是连续发出数字(每次+1),然后通过异或运算将它们传递给混杂位,从而给我一个看似随机的数字。当然,它们并不是真正随机的,但在用户眼中它们看起来是随机的。


[编辑]附加信息

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

但是,您可以使用其他形式的加密,如果您想要更随机的数字,而不是速度(假设您不需要一次生成多个 id)。现在选择加密算法的重点是“保证数字永远不会发生冲突”。证明加密算法是否可以满足此保证的一种方法是检查原始数字和加密结果是否具有相同的位数,并且该算法是可逆的(双射)。

[感谢Adam LissCesarB扩展解决方案]

于 2008-11-26T01:51:56.003 回答
17

为什么不直接使用 GUID?大多数语言应该有一个内置的方法来做到这一点。它保证是唯一的(具有非常合理的界限)。

于 2008-11-26T02:19:10.037 回答
6

想要一个顶级的解决方案?

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

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

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

将它们传递到生成Perfect Hash 函数的工具中,例如gperf

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

于 2008-11-26T02:16:51.883 回答
3

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

于 2008-11-26T07:51:45.093 回答
2

假设:

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

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

于 2008-11-26T02:00:34.317 回答
1

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

就个人而言,我很幸运可以使用哈希作为替代方案,但要提出更好的解决方案,我真的需要知道你为什么要这样做。

于 2008-11-26T01:51:16.740 回答
1

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

我的建议是做同样的事情,看看你的碰撞率是多少,看看你的情况是否可以接受。

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

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

于 2008-11-26T01:51:41.810 回答
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;
     }
 }
?>

虽然这也可以满足您的需求,但这是一个坏主意,因为它不会长时间扩展,最终您的数组会变大,并且需要很长时间才能生成一个尚未在您的数据库中的随机数.

于 2008-11-26T01:55:06.497 回答
1

设计一个长周期不重复的伪随机数发生器很容易;例如这个,它被用于你想要的同样的事情。

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

于 2008-11-26T02:02:54.983 回答
1

我喜欢 Oddthinking 的想法,但与其选择世界上最强的散列函数,你可以简单地:

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

MD5 速度很快,检查字符串是否属于数组可以避免 SELECT。

于 2008-11-26T02:41:37.073 回答
1

其实我之前写过一篇关于这个的文章。它采用与 Robert Gould 的回答相同的方法,但还展示了如何使用 xor 折叠将分组密码缩短到合适的长度,然后如何在不是 2 的幂的范围内生成排列,同时仍然保留唯一性属性。

于 2008-11-26T10:13:25.713 回答
1

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

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

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

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

于 2008-11-27T22:41:22.237 回答
1

适当的 PRNG(伪随机数生成器)算法将有一个循环时间,在此期间它永远不会处于相同状态。如果您在从中检索到的数字中公开 PRNG 的整个状态,您将获得一个保证在生成器期间唯一的数字。

执行此操作的简单 PRNG 称为“线性同余” 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 的值来获得一个具有很长周期(2^30 IIRC)的 PRNG。

A的良好价值:

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

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

出于类似的原因,长周期发电机如Mersenne Twister 也是如此。

于 2008-11-27T22:59:11.843 回答
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 的间隙大小,然后检查一次并停止,因为没有匹配项,然后将数字添加到列表中,列表按升序重新排列,依此类推,直到你完成选择随机数...希望这会有所帮助..

于 2012-01-27T13:05:27.543 回答
0

PHP 已经为此提供了一个函数uniqid。如果您必须从其他地方访问数据,它会生成一个标准的 uuid。不要重新发明轮子。

于 2008-11-26T02:06:39.750 回答
0

如果要确保随机数不重复,则需要一个不重复的随机数生成器(如此所述)。

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

该算法的置换效果不是很好,因此我建议将其与 XOR 或加法或其他一些方法结合使用来更改确切值,而不会破坏种子与其生成值之间的一对一关系。

于 2015-10-04T22:49:06.910 回答
0

对于随机生成:(在 PHP 中)

代码:

它完美地创建了随机数。

<?PHP
 /*set the bigger range if there is huge demand*/
$n=range(111111,999999);

shuffle($n); //shuffle those

for ($x=0; $x< 1; $x++) //selects unique random number.
{
echo $n[$x].' ';
}
echo "\n"

?>

结果:

第一次:

一

第二次:

二

第三次:

三

于 2021-11-05T18:11:26.520 回答