【问题标题】:Generate Random Unique Code生成随机唯一代码
【发布时间】:2011-08-19 02:26:29
【问题描述】:

我需要生成一个九位数字代码(最好是随机的),该代码对于给定的日期是唯一的(同一天不能再次生成相同的数字)。我正在考虑使用 HHMMSSmmm(小时、分钟、秒和毫秒)来生成唯一代码,但并不是真正随机的。这个代码生成方法可以同时被多个方法访问,所以我必须锁定这个方法。但这是否会确保数字是唯一的,因为数字生成可能需要不到一毫秒的时间并且两个线程会获得相同的数字?

有没有更好的方法来生成一个随机的唯一数字代码,该代码在给定日期是唯一的?位数可以是6到9位。

编辑:要生成的随机数的数量取决于交易的数量。最初这个数字可能会较低,但在一段时间内它可能会变得非常高(每秒多个事务)。因此,我不想将数字与已用列表进行比较,因为这可能会出现性能问题。

需要随机性,因为此号码将由用户在手机上输入。这个号码是连接在线交易和电话交易的唯一方法,所以我不希望用户错误地输入不同的号码。

随机数生成需要在 ASP.NET MVC 应用程序中进行。

【问题讨论】:

  • @achinth 他们必须是数字吗?
  • 常规 System.Guid 不行吗?
  • 您忽略了一个关键信息:您每天要创建多少个这些独特的 9 位随机数?因为如果您每天要创建两个或三个,这比你创造两千万要容易得多。如果你每天创造一万亿,那么问题就无法解决;没有那么多可用的九位数​​字。 (巧合的是,我经常把你的问题当作面试问题来问;令人震惊的是,有多少候选人从没想过要问他们正在处理多少个数字。)
  • 您能否描述一下为什么“随机性”很重要?某些东西“随机”有很多不同的原因,每个原因都对随机生成器提出了不同的要求。例如,密码随机性与伪随机性非常不同。出于安全原因,您真的需要不可预测性吗?
  • @Joan:我不再经常使用这个问题,所以我可能会在某个时候写博客。我有一小部分我喜欢使用的问题,具体取决于职位的级别。

标签: c# .net algorithm random


【解决方案1】:

如果你从一个 6 位数的随机数开始,然后继续添加随机但足够小的数字,你也许可以做到这一点。如果需要,您可以使用文件系统作为锁定存储...但我认为您应该使用数据库进行生产!

这是我所说的示例:

此示例是一个控制台应用程序,它使用文件来控制并发性,并存储上次使用的数字和生成日期。

如果你多次运行它,你会看到会发生什么。两者都有自己的唯一编号。

它不会像您要求的那样存储所有生成的数字!

此示例每天可以生成大约 999000 个随机数,范围为 6 位和 9 位(含)。这大约是每秒 11 个数字。

using System;
using System.IO;
namespace _5893408
{
    class Program
    {
        static void Main(string[] args)
        {
            Random rand = new Random();
            var futureTime = DateTime.Now.AddSeconds(60);
            while (DateTime.Now < futureTime)
                Console.WriteLine(GetNextNumber(rand));
        }

        public static int GetNextNumber(Random rand)
        {
            var now = DateTime.Now;
            string filePath = @"C:\num.txt";
            FileStream fileStream = null;
            while (fileStream == null)
            {
                try { fileStream = File.Open(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); }
                catch { }
            }
            using (fileStream)
            {
                DateTime date;
                int prevNum;
                if (fileStream.Length == 0)
                {
                    date = now;
                    prevNum = rand.Next(100000, 999999);
                }
                else
                {
                    var reader = new StreamReader(fileStream);
                    {
                        date = DateTime.Parse(reader.ReadLine());
                        prevNum = int.Parse(reader.ReadLine());
                    }
                    if (date.DayOfYear != now.DayOfYear)
                        prevNum = rand.Next(100000, 999999);
                }
                int nextNum = prevNum + rand.Next(10, 1000);
                fileStream.Seek(0, SeekOrigin.Begin);
                using (var writer = new StreamWriter(fileStream))
                {
                    writer.WriteLine(now);
                    writer.WriteLine(nextNum);
                }
                return nextNum;
            }
        }
    }
}

我认为这符合您的要求...我错了吗?

如果我是,请告诉我,我会尽力提供更多帮助。

【讨论】:

  • 在安全回调映射的情况下,这不是让它有点可预测。
  • @YetAnotherUser:是的……有点!但是使用某种整数数学可能会降低它的可预测性......就像密码学中使用的数学,做大素数和然后模块。
【解决方案2】:

它是否只需要在流程中是唯一的?

有什么理由不只保留一个每次自动递增的计数器,然后在日期翻转时将其重置?

【讨论】:

  • 好主意,但我希望数字有一些随机性。乔恩有可能吗?
  • @achinth:有多种方法可以包含随机性 - 最简单的实现方式是记住您今天创建的所有值,生成一个随机数并检查您是否已经退还了。这根本无法很好地扩展(实际上很可怕),但如果您不需要生成很多数字,它会起作用。请注意,使用时间远非随机...
  • @achinth 我认为您应该在您的问题中添加所有必要的信息。如果您正在寻找一种生成唯一编号的方法,这里有一个。现在,如果你想要随机数......你应该将它添加到问题中。也许你可以告诉我们场景是什么......
  • @jon 号码生成需要很快,所以如果可能的话我不想检查现有号码。
  • 另一个假设过程中并保留已生成数字列表的问题是,如果这是一个必须在回收利用中存活的 asp.net 应用程序。
【解决方案3】:

使用 Guid.NewGuid() 得到类似的东西:

0f8fad5b-d9cb-469f-a165-70867728950e

然后,去掉“-”,将字母转换为对应的 ASCII。 (其中 a=97)

然后转换为数字。

【讨论】:

  • 我需要 6 到 9 位数字之间的数字,如果可能的话,我不想检查现有数字的唯一性。
  • 这是个坏主意。 GUID 大于 9 位十进制数字,只有当您查看 所有 位时,才能保证 GUID 是唯一的。 GUID 中任何给定的位子集都不能保证是唯一的,正如飞机的任何单个部分都是飞行的部分一样。
  • 只取 GUID 的前 6-9 位显然不能保证唯一性,因此需要检查已分配的 id。 OP 希望尽可能避免这种情况,以便他可以在其他建议的答案中进行选择。
【解决方案4】:

我愿意

  1. 在每天开始时生成一组唯一的随机数,或者更确切地说,在每天之前的足够时间

  2. 每次需要时从堆栈中依次取出一个数字

【讨论】:

    【解决方案5】:

    如果所有调用都在同一个 JVM 中,我认为您只需要创建一个静态来保存分配的最后一个数字,编写一个函数来增加数字并返回新值,然后同步它。喜欢:

    public class NumMaker
    {
      static long num=0;
      public static synchronized next()
      {
        return ++num;
      }
    }
    

    如果有多个 JVM,最简单的做法是将数字存储在数据库中并使用数据库锁定来保持数字的唯一性。

    更新

    我看到您添加了一个要求数字是随机的。如果您希望数字是随机且唯一的,我看不出有任何替代方法可以保留所有先前分配的数字的列表。您可以将它们保存在某种哈希表中,这样您就不必每次都搜索整个列表。如果您分配了很多这些,哈希表的大小可能会开始成为问题,即使您不必按顺序搜索它。

    根据您要完成的任务,您可以提出一个方案,以非顺序但严格的顺序分配数字,因此出于某些目的,它们看起来是随机的。例如,您可以增加一个相对于最大值非常大且与最大值互质的数字,然后每次下一个增量超过时,减去最大值。就像只是按比例缩小一样,假设您分配的是 2 位数字而不是 9 位数字。以 37 为增量。然后将 37、74、111 环绕分配给 11、48、85、122 环绕分配给 22,等等。

    【讨论】:

    • @Oscar:糟糕,我对 Java 的偏执已经泄露了!我的意思当然是 .NET 运行时。
    【解决方案6】:

    编辑:当我意识到每天需要有多个唯一代码但他们可以在第二天重复时,这个答案没有意义。如果您每天都在寻找一个唯一代码(无论出于何种原因),那么这个答案很有用:)

    所以如果只要求代码每天唯一,那么只需使用日期作为代码...

    也许是 YYYYmmdd,它会给你(今天的日期)20110505,明天是 20110506。

    【讨论】:

    • @AlastairPitts 这就是他所说的,但我真的不认为他想要每个 da 一个代码。否则他不会考虑在同一秒内访问两种方法:/
    • @AlastairPitts 是的,他就是这么说的;)
    • 代码一天不能重复,第二天可以重复
    • @achinth:嗯,好的。我明白你现在的意思了。我误读了您的要求:)
    【解决方案7】:

    您不能确保随机数不会重复。 (因为它们是随机的)

    如果不与已经生成的数字进行比较,您可以:

    • 随机数或
    • 唯一编号。

    您需要具有随机外观的唯一编号。如果从一天开始的毫秒数没有足够的随机外观,请尝试将其与另一个唯一数字结合。

    例如:

    • 结合毫秒数,和
    • 原子计数器(每次生成数字时递增一)

    例如,当您对它们求和时,您可以生成: 999999999-86400000 = 每天 913599999 个唯一号码。

    虽然它们不是随机的,但它们将是独一无二的 - 并且只能在 00:00 进行预测。

    这里有一些变体,例如不在 00:00 重置计数器。

    【讨论】:

      【解决方案8】:

      Shuffle Bag 的修改版怎么样。这是它的工作原理 -

      1. 在您的一天开始之前,您将 N 个满足您标准的不同数字放入洗牌袋中
      2. 在一天中,您从洗牌袋中请求一个号码。
      3. Shuffle bag 会从袋子中为您提供一个随机数并将其丢弃 - 即不会再次返回相同的数字。
      4. 在一天结束时,它会清理袋子,为第二天做好准备。

      好处

      • 确保号码不会被重复使用,无需检查现有列表
      • 数字是随机的,没有任何顺序
      • 应用简单的健全性规则来初始化 Shuffle Bag,例如不允许常见/重复序列(1111111 或 123456789)
      • 简单初始化洗牌袋 - 使用随机序列号。即从六位数开始继续添加一个小的随机数来初始化包。
      • 根据历史使用情况轻松修改包的大小。
      • 在 C# 中非常简单的线程安全实现。

      原始来源是here - 修改后的版本可能符合您的目的。

      【讨论】:

        【解决方案9】:

        在代码末尾附加一个线程id来处理并发。

        【讨论】:

        • 假设您将线程 id 附加到当前时间。现在假设 id 是在夏令时“倒退”的一小时开始时创建的。一小时后,同一个线程被要求产生一个 ID,它产生的 ID 和以前一样,违反了它是唯一的要求。这里的问题是时代不是唯一的。向它们添加更多内容并不能改善这种情况。
        • 让我们看看您的解决方案中的另一个问题。假设您有一个由两部分组成的 ID。第一部分保证在给定线程上是唯一的,第二部分是线程 ID。您生成一个 ID。然后该线程结束。几个小时过去了。创建了一个新线程并且它恰好获得了相同的 ID。那是一个不同的线程,所以它可以生成与之前的线程相同的第一部分,并且 ID 相同,所以同样,我们没有唯一性。
        • 总而言之:时间和线程句柄都不是每天唯一性的来源。 你不能用两个本身并不独特的东西来制造独特性。
        【解决方案10】:

        合适的代码生成器取决于周期内应该生成多少数字。 考虑以下模式:

        HHMMSS+NNN- 在一秒钟内为您提供 999 个随机数的空间。

        HH+NNNNNNN - 在一小时内为您提供 9999999 个随机数的空间。等等。

        如果数字生成方法调用在时间上的分布是均匀的,那么任何模式几乎都是相等的。

        无论如何,考虑到随机数长度限制,在发生冲突之前,方法调用的数量总是有限制的。例如。如果方法每秒调用 > 1000 次。

        【讨论】:

          【解决方案11】:

          我继续使用当前时间的想法,我只是添加了多线程同步,并存储使用过的随机数以将所有下一个随机数与它们进行比较以提供唯一性。

          private static DateTime DateInArray = DateTime.Today;
          private static ICollection<string> UsedTodayRandoms = new List<string>();
          
          [MethodImpl(MethodImplOptions.Synchronized)]
          public static string RandomUniqueToday()
          {
              if (! DateTime.Today.Equals(DateInArray) ) {
                  UsedTodayRandoms.Clear();
                  DateInArray = DateTime.Today;
              }
          
              string result = null;
              DateTime timeToGenerateUnique = DateTime.Now;
              do
              {
                  result = timeToGenerateUnique.ToString("HHmmssfff");
                  timeToGenerateUnique = timeToGenerateUnique.AddMilliseconds(-1);
              } while (UsedTodayRandoms.Contains(result));
              UsedTodayRandoms.Add(result);
          
              return result;
          }
          

          【讨论】:

          • 我希望避免与使用过的列表进行比较,因为这可能会导致性能问题。
          【解决方案12】:

          public double GetRandomNumber() {object operation = new object(); lock(operation) { return new Random().NextDouble(); } } 可以。

          这不取决于日期/时间,因此它比您的要求更加随机。 现在这个数字小于 1,我将留给你作为练习......

          此外,如果您想跟踪给定日期生成的所有数字 - 保留它们的列表并在生成重复时重新生成。但是我不会为你写,因为你是你这种情况的程序员......

          【讨论】:

          • 您的解决方案存在两个问题。首先,一个没有播种的新 Random() 类将使用当前系统的时间作为种子值。这意味着在大约 15 毫秒(Windows 系统时钟分辨率)内创建的两个将很可能产生完全相同的值。锁定不会阻止这一点。其次,由于您要锁定的对象是在您的方法中创建的,因此它实际上不会阻塞任何东西,因为其他线程将创建自己的新对象来锁定。
          【解决方案13】:

          根据需要多少随机性,您可能正在寻找具有适当参数的linear congruential generator。例如,按照 Wikipedia 条目中给出的关于周期长度的指南,一组可能对您有用的参数是:M=1000000000, a=21, c=3,然后使用任何初始种子 X0 sub> 在 [0..999999999] 中,并计算 Xn+1=(a*Xn+c)%M。这会生成一个周期为 M 的 Xn 序列,即该序列在开始重复之前恰好在 [0..999999999] 中生成所有数字。

          【讨论】:

            猜你喜欢
            • 2018-06-25
            • 1970-01-01
            • 2011-06-11
            • 1970-01-01
            • 2020-05-27
            • 2019-01-12
            • 2012-12-29
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多