【问题标题】:Algorithm for generating a random number生成随机数的算法
【发布时间】:2010-09-24 02:06:20
【问题描述】:

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

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

基本上这个想法是做以下事情

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

2) 查询数据库中的所有号码
3) 查看返回的结果是否与来自 d​​b
的结果匹配 4)如果匹配,重复步骤1,如果不匹配,问题解决。

谢谢。

【问题讨论】:

  • 我不得不 -1 因为这个问题背后的逻辑是有缺陷的。
  • 能否补充一下为什么您不想使用简单的自动增量字段?
  • 我不知道这是随机的还是唯一且非连续的。我无法弄清楚 user_id 与任何事情有什么关系。而且我不知道 9,999,999 与任何事情有什么关系。
  • 有两件事你应该'永远'不做:尝试生成你自己的随机号码或'发明'你自己的加密。 100 次中有 99 次是有缺陷的。

标签: php mysql algorithm random pseudocode


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

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

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

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

    【讨论】:

      【解决方案3】:

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


      [编辑]附加信息

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

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

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

      【讨论】:

      • 噢……聪明!使用已知序列生成唯一数字并确定性地操作它们。将 XOR 替换为“加密”以获得更好的随机性。
      • 是的,加密有效,实际上 XOR 是一种“廉价”加密形式,保证永远不会发生冲突。使我的解决方案成为更通用解决方案的特例,但您需要证明另一种加密是否可以提供相同的保证,然后才能安全使用。
      • 容易证明:如果原始数和加密结果的位数相同,那么算法要可逆,它必须是双射(否则会发生冲突)的方向)。因此,它只需要是可逆的并且具有相同的位数。
      • 我们走了 :) 感谢您提到一种证明方法。如果可以的话,我会支持评论!
      • @Robert Gould:为什么不把它合并到你的答案中呢?即使对于没有 Javascript 的人来说,它也会变得更加有用。
      【解决方案4】:

      问题是,如果您生成随机数,很可能会无限地产生重复。

      然而:

      <?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;
           }
       }
      ?>
      

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

      【讨论】:

        【解决方案5】:

        假设:

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

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

        【讨论】:

          【解决方案6】:

          设计一个长周期不重复的伪随机数发生器很容易;例如this one,它正用于您想要的同一件事。

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

          【讨论】:

            【解决方案7】:

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

            【讨论】:

            • uniqid 根据当前时间返回一个合理的随机字符串,而不是 UUID。
            【解决方案8】:

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

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

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

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

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

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

            【讨论】:

              【解决方案9】:

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

              【讨论】:

              • GUID 是“全球唯一 ID”而不是“全球随机 ID”
              • @andora: true;取决于OP想要什么。似乎他想要一些看似随机的东西,GUID 会这样做
              • @andora 虽然首字母缩写词 GUID 中的首字母都不代表“随机”,但事实是 GUID 随机的。
              • @rjmunro:如果你稍微检查一下,你会发现它们根本不是很随机,它们可能看起来是这样,但并不是设计为随机的,它们被设计为独一无二的。
              【解决方案10】:

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

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

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

              【讨论】:

              • 补充这个想法,如果你碰巧找到一两个重复的,用不同的盐重复这个过程,直到你没有。这样,您就可以完全避免运行时检查。
              【解决方案11】:

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

              【讨论】:

                【解决方案12】:

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

                【讨论】:

                • 您可能更改了服务器 url 映射,因此您文章的正确链接现在是 blog.notdot.net/2007/9/…。你说的那个坏了。 +1 为 TEA 密码
                • 谢谢,已修复链接。显然,我在迁移博客时遗漏了一些重定向。
                【解决方案13】:

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

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

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

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

                【讨论】:

                  【解决方案14】:

                  适当的 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 的值来获得一个较长周期 (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. 等长周期生成器也是如此

                  【讨论】:

                    【解决方案15】:

                    有几种方法可以解决这个问题,其中一种方法是构建一个包含 0000000 到 9999999 的数字的数组,然后在这个数组中随机选择这些数字 并将选取的数字值与最大值 Max 交换 然后将 max 减 1 并选择该数组的另一个随机成员直到新的最大值

                    每次Max减一

                    例如(基本):(右侧是在实际程序中应删除的 cmets) 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 的间隙大小,然后检查一次并停止,因为没有匹配项,然后将数字添加到列表中,列表按升序重新排列,依此类推直到您选择完随机数...希望这会有所帮助..

                    【讨论】:

                      【解决方案16】:

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

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

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

                      【讨论】:

                        【解决方案17】:

                        对于随机生成:(在 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"
                        
                        ?>

                        结果:

                        第一次:

                        第二次:

                        第三次:

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 2012-01-05
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2015-01-05
                          • 1970-01-01
                          相关资源
                          最近更新 更多