【问题标题】:smart way to generate unique random number生成唯一随机数的聪明方法
【发布时间】:2011-04-07 07:44:16
【问题描述】:

我想生成一个 00000001 到 99999999 范围内的唯一随机数序列。

所以第一个可能是 00001010,第二个可能是 40002928 等等。

简单的方法是生成一个随机数并将其存储在数据库中,下次再做一次,如果该数字已经存在,则检查数据库,如果存在,则生成一个新的,再次检查,等等. 但这看起来不对,如果生成的项目数量变大,我可能会重新生成一个数字可能 100 次。

有没有更聪明的方法?

编辑 一直以来我都忘了说为什么我想要这个,它可能会让事情变得更清楚,也许会得到一个替代方案,它是: 我们想为预订生成一个订单号,所以我们可以只使用 000001、000002 等。但我们不想让竞争对手知道创建了多少订单(因为它不是一个大容量市场,我们不'不希望他们知道我们是在 2 个月后订购 30 还是订购 100。所以我们希望有一个随机的订单号(但唯一)

【问题讨论】:

  • 为什么你需要一个约束集中的唯一数字?
  • 独特而随机。随着新数字的产生,这变得越来越随机。
  • @Pierre-Alain Vigeant:您是说硬币的随机性不如例如硬币那么随机。骰子,因为它可能的结果更少?
  • @Lazarus:如果你能在电子或量子噪声中找到一种模式,那么你就是那个人。
  • @0xA3,如果你消除所有出现的值,硬币只会随机一次。

标签: c# random


【解决方案1】:

您可以使用线性同余发生器 (LCG) 或线性反馈移位寄存器 (LFSR)。谷歌或维基百科了解更多信息。

两者都可以通过正确的参数在“全周期”(或“全周期”)的基础上进行操作,以便它们在单个周期内仅生成一次“伪随机数”,并生成所有数字范围内。两者都是“弱”生成器,因此对密码学没有好处,但对于明显的随机性来说可能“足够好”。您可能必须将句点限制在“十进制”最大值范围内,因为必须使用“二进制”句点。

更新:我应该补充一点,没有必要以任何方式预先计算或预先存储以前的值,您只需要保留以前的种子值(单一整数)并计算“按需”下一个序列中的编号。当然,如果需要,您可以将预先计算的数字链保存到数据库中,但这不是必需的。

【讨论】:

    【解决方案2】:

    如何创建一组所有可能的数字并简单地随机化顺序?然后,您可以从尾部选择下一个数字。

    每个数字在集合中只出现一次,当你想要一个新的数字时,它已经生成了,所以在你想要一个的时候开销很小。您可以在内存或您选择的数据库中执行此操作。您只需要一个合理的锁定策略来提取下一个可用号码。

    【讨论】:

    • 要求的范围是1到1亿。这是一个使用 32 位整数的 400 兆字节列表。祝你好运。
    • @Jeremy:我的不是特别高规格的机器可以在大约 20 秒内生成 shuffle 那些 99,999,999 个整数的数组。 (这只是使用了一些我很快拼凑起来的未优化的 LINQ。)
    • 也许使用 trie 来存储数字?这应该会减少存储所需的空间。
    【解决方案3】:

    你可以建立一个包含所有可能数字的表格,给记录一个“使用”字段。

    1. 选择所有未被“使用”的记录
    2. 在 1 和记录数之间选择一个随机数 (r)
    3. 取记录号r
    4. 从记录中获取您的“随机值”
    5. 设置“已使用”标志并更新数据库。

    这应该比选择随机数、查询数据库并重复直到找不到更有效,因为这只是在乞求最后几个值的永恒。

    【讨论】:

    • 请注意,这是一个 O(n^2) 算法,用于洗牌 n 个数字。 (识别未使用的集合是 O(n),而你这样做了 O(n) 次。)这里介绍了用于洗牌的 O(n) 和 O(n lg n) 算法;如果您要不厌其烦地保存一个包含数百万条目的数据库表,为什么不首先快速洗牌呢?
    • 这个已经把我的想法定在了最终的方向上。我做了几乎相同的事情,只是我在代码中做了随机数,所以我生成了 100000 个带有随机数的插入语句,所以在 sql 中我只选择下一个。
    【解决方案4】:

    使用伪随机数生成器。

    例如 - 线性同余随机数生成器

    (如果increment和n互质,那么代码将生成从0到n-1的所有数字):

        int seed = 1, increment = 3;
        int n = 10;
    
        int x = seed;
        for(int i = 0; i < n; i++)
        {
            x = (x + increment) % n;
            Console.WriteLine(x);
        }
    

    输出: 4 7 0 3 6 9 2 5 8 1

    Basic Random Number Generators

    Mersenne Twister

    【讨论】:

    • 将 n 增加到 15,数字 4 将出现 3 次。看来种子和 n 互质是不够的。
    • 错字,“increment”和“n”需要互质。
    • 聪明的数学,但如果我没记错的话,它们总是会以算术级数的形式出现
    • 是的,这是最简单的生成器算法
    • @jonas:有关为 LCG 选择常量的详细信息,请参阅维基百科。选择最佳常量还有其他限制条件。
    【解决方案5】:

    使用此算法可能是合适的,但它会消耗内存: http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 将 1 到 99999999 的数字放入数组中,然后随机播放。

    【讨论】:

      【解决方案6】:

      由于您的数字非常有限,因此您不能期望任何类型的随机生成都具有唯一性。

      您正在生成一个 32 位整数,而要达到唯一性,您需要一个更大的数字,大约 128 位,这是 GUID 使用的大小,保证始终是全局唯一的。

      【讨论】:

      • 请注意,如果数字足够大,只会降低冲突的概率。理论上,不能保证唯一性,但出于实际目的,您可以假设概率足够低。
      • 由于GUID算法的性质,理论上与现实无关。
      • 确实,鉴于 1. 一种智能算法,以及 2. 使用该算法的所有相关方 保证唯一性非常容易。不过,第 2 点很重要,如果有人选择使用另一种算法,那么所有的赌注都会被取消。请注意,GUID 生成有多种算法,如果您将它们混合使用,那么您的 GUID 可能毕竟不是那么 GU。
      【解决方案7】:

      如果您碰巧可以访问某个库,并且想深入了解并深入了解该问题,请查看

      The Art of Computer Programming, Volume 2: Seminumerical Algorithms

      唐纳德·E·高德纳 (Donald E. Knuth)。第 3 章都是关于随机数的。

      【讨论】:

        【解决方案8】:

        您可以将您的号码放在一组中。如果生成 N 个数后集合的大小太小,请生成更多。

        做一些试运行。您平均需要生成多少个数字?尝试找出权衡“生成太多数字”/“经常检查重复项”的最佳解决方案。这个最优值是一个数字 M,因此在生成 M 个数字后,您的集合可能会包含 N 个唯一数字。

        哦,M也可以计算:如果你需要一个额外的数字(你的集合包含N-1),那么随机数已经在集合中的机会是(N-1)/R,其中R是范围。我在这有点眼花缭乱,所以你必须自己解决这个问题(但这种东西才是让编程变得有趣的原因,不是吗?)。

        【讨论】:

        • 如果您要将所有 99,999,999 个可能的数字存储在某种集合和/或表格中,那么为什么不按顺序生成它们以开始并打乱它们呢?
        • @LukeH - 来自 OP,我猜他只想要几个数字,与整个范围相比很小。
        • @Michel,但唯一性约束是否延伸到前几个月?
        【解决方案9】:

        您可以在包含随机数的列上设置唯一约束,然后通过重新生成数字来处理任何违反约束的情况。我认为这通常也会对列进行索引,因此会更快。

        你已经用 C# 标记了这个问题,所以我猜你正在使用 C# 来生成随机数。也许考虑让数据库在存储过程中生成随机数,然后返回。

        【讨论】:

        • 这不是真的和 OP 建议的一样,并且遇到同样的问题/问题吗??
        • @andora:这是一种优化。
        • @andora:没有其他方法可以保证没有重复吗?
        • 是的,不要使用随机数作为值。使用 used 标志生成一组可能的随机数,选择所有未使用的值并从该集合中随机选择一条记录,设置 used 标志并更新该集合。 random 元素保证了一个未使用的可用值,它与 OP 和您的方法一样随机,但不会产生已经使用过的随机数的循环。
        • @Lazarus:是的!没想到。
        【解决方案10】:

        您可以尝试使用起始编号和递增编号来提供书写用户名。您从一个数字(例如 12000)开始,然后,对于创建的每个帐户,该数字都会增加增量值。

        id = startValue + (totalNumberOfAccounts * inctrementalNumber)
        

        如果incrementalNumber 是一个质数,您应该能够围绕最大帐户值循环,而不会碰到另一个值。这会产生随机 id 的错觉,但也应该很少有冲突。在发生冲突的情况下,您可以在发生冲突时添加一个数字来增加,所以上面的代码就变成了。我们要处理这种情况,因为如果我们遇到一个相同的帐户值,当我们增加时,我们会在再次增加时遇到另一个冲突。

        id = startValue + (totalNumberOfAccounts * inctrementalNumber) + totalConflicts
        

        【讨论】:

          【解决方案11】:

          我以前不得不做这样的事情(为 URL 的一部分创建一个“随机查找”的数字)。我所做的是创建一个随机生成的密钥列表。每次它需要一个新数字时,它只是从键中随机选择一个数字。对键和给定的序列号进行计数和异或运算,然后输出以键索引为前缀的异或值(以 62 为基数)(以 62 为基数)。 我还检查了输出以确保它不包含任何空话。如果它确实只需使用下一个键并进行第二次操作。 解密数字同样简单(第一个数字是要使用的密钥的索引,一个简单的 XOR 就完成了)。

          我喜欢andora's answer,如果您正在生成新数字并且如果我知道的话可能会使用它。但是,如果我要再次这样做,我会简单地使用UUIDs。大多数(如果不是每个)平台都有生成它们的方法,并且长度不是 URL 的问题。

          【讨论】:

            【解决方案12】:

            您可以尝试shuffling 一组可能的值,然后按顺序使用它们。

            【讨论】:

              【解决方案13】:

              我喜欢 Lazarus 的解决方案,但是如果您想避免为每个可能的数字有效地预先分配空间,只需将使用的数字存储在表中,但通过将所有可能的数字添加到内存中来构建一个“未使用的数字”列表一个集合,然后删除数据库中存在的每个集合。然后选择剩余的数字之一并使用它,显然将其添加到数据库中的列表中。

              但是,就像我说的,我喜欢 Lazaru 的解决方案 - 我认为这是您在大多数情况下的最佳选择。

              【讨论】:

                【解决方案14】:
                function getShuffledNumbers(count) {
                 var shuffledNumbers = new Array();
                 var choices = new Array();
                 for (var i = 0; i<count; i++) {
                  // choose a number between 1 and amount of numbers remaining
                  choices[i] = selectedNumber = Math.ceil(Math.random()*(99999999 - i));
                  // Now to figure out the number based on this selection, work backwards until
                  // you figure out which choice this number WOULD have been on the first step
                  for (var j = 0; j < i; j++) {
                   if (choices[i - 1 - j] >= selectedNumber) {
                    // This basically says "it was choice number (selectedNumber) on the last step,
                    // but if it's greater than or equal to this, it must have been choice number
                    // (selectedNumber + 1) on THIS step."
                    selectedNumber++;
                   }
                  }
                  shuffledNumbers[i] = selectedNumber;
                 }
                 return shuffledNumbers;
                }
                

                这是我能想到的最快的方法,并且只使用它需要的内存,但是如果你一直运行它,它将使用双倍的内存,因为它有两个数组,choicesshuffledNumbers .

                【讨论】:

                  【解决方案15】:

                  运行一次线性同余生成器来生成每个数字很容易产生相当微弱的结果。运行它通过一些相对于您的基础(在本例中为 100,000,000)的迭代将大大改善它。如果在报告生成器的每个输出之前,您通过一个或多个附加排列函数运行它,最终输出仍将是任意数量的无重复排列(最多 100,000,000),但如果选择了正确的函数结果可能在密码学上很强大。

                  【讨论】:

                    【解决方案16】:
                    1. 创建并存储区间 [0..N) 的两个混洗版本(SHUFFLE_1 和 SHUFFLE_2),其中 N=10'000;

                    2. 每当创建新订单时,您都可以像这样分配它的 id:

                    ORDER_FAKE_INDEX = N*SHUFFLE_1[ORDER_REAL_INDEX / N] + SHUFFLE_2[ORDER_REAL_INDEX % N]

                    【讨论】:

                      【解决方案17】:

                      我也遇到了同样的问题,但在 C# 中。我终于解决了。希望它也对你有用。

                      假设我需要介于 0 和某个 MaxValue 之间的随机数,并且有一个 Random 类型的对象说随机。

                      int n=0;
                      while(n<MaxValue)
                      {
                         int i=0;
                         i=random.Next(n,MaxValue);
                         n++;
                         Write.Console(i.ToString());
                      }
                      

                      【讨论】:

                        【解决方案18】:

                        通过休耕线,我们可以得到例如6个非重复随机数的范围,例如1 到 100。

                        var randomNumbers = Enumerable.Range(1, 100)
                            .OrderBy(n => Guid.NewGuid())
                            .Take(6)
                            .OrderBy(n => n);
                        

                        【讨论】:

                          【解决方案19】:

                          愚蠢的方法:建一个表来记录,先存储所有的数字,然后每次数字使用,并将其标记为“已使用”

                          【讨论】:

                            【解决方案20】:
                            System.Random rnd = new System.Random();
                            IEnumerable<int> numbers = Enumerable.Range(0, 99999999).OrderBy(r => rnd.Next());
                            

                            这会在您的范围内提供随机打乱的整数集合。然后,您可以按顺序遍历集合。

                            这样做的好处是您实际上并没有在内存中创建整个集合。

                            参见下面的 cmets - 当您迭代到第一个元素时,这将在内存中生成整个集合。

                            【讨论】:

                            • 您能解释一下为什么您认为这不会在内存中创建整个集合吗? (排序至少需要确定最小的排序键;这样做需要查看所有这些键。)我对人们为什么相信错误的东西很感兴趣,因为它可以帮助我们更好地设计语言和库。我向你保证,这不仅会在内存中创建整个集合,还会在 n lg n 时间内对其进行洗牌。另一方面,fischer-yates shuffle 在 O(n) 时间内工作,不需要对整个集合进行洗牌。 (尽管整个集合必须存在于内存中。)
                            • 如何在不出现内存不足异常的情况下从中获取数字? var number = numbers.First();炸弹。
                            • @Eric Lippert - 我的错误。我从 OrderBy 的描述中误解了这一点:“这个方法是通过使用延迟执行来实现的。立即返回值是一个对象,它存储了执行该操作所需的所有信息。由该方法表示的查询直到通过直接调用其 GetEnumerator 方法或在 Visual C# 中使用 foreach 或在 Visual Basic 中使用 For Each 来枚举对象。"
                            • 回答你的第一个问题:是的,工作被推迟了,但那又怎样?仅仅因为炸弹不会立即爆炸并不能安全触发。在任何人要求完成工作的那一刻,所有未排序的键、生成的值和排序的结果都将被创建,后两者必须同时全部存在于内存中。 (8亿字节的双打!)回答你的第二个问题:不。获取第一个元素只需要获取第一个元素。但是获取第一个排序的元素需要获取所有元素,以便您知道哪个先出现。
                            • 你是对的;工作被推迟到需要。而获取排序列表的第一个元素需要查看所有元素。如果您可以在不查看所有元素的情况下弄清楚如何在列表中找到最小的元素,我很乐意看到它!回答您关于文档的问题:正如您所指出的,文档并没有它可能的那么好。实现也不是完美的,但它们非常好。但总的来说,使用常识:分组、排序、最小值、最大值和连接需要查看所有内容才能知道第一个元素。
                            猜你喜欢
                            • 2014-04-30
                            • 1970-01-01
                            • 2020-05-27
                            • 1970-01-01
                            • 1970-01-01
                            • 2018-05-03
                            • 2017-09-24
                            相关资源
                            最近更新 更多