【问题标题】:Algorithm to generate random number of size X生成大小为 X 的随机数的算法
【发布时间】:2011-11-02 14:11:17
【问题描述】:

在我的移动应用程序中,我必须为用户提供一个随机的唯一 X 字母数字代码,以便用户可以回复该字母数字代码来执行某些任务。

将使用此应用程序的用户数量约为 100 万人,消息流量约为 10 万条消息/天。

我只能使用 26 个大写字母、26 个小写字母和 10 个数字。如果随机数大小为 5,那么我可以生成 916132832 个唯一组合。组合用尽后,我想再次回收这个数字生成。

我正在寻找一种算法方法。有什么算法可以解决这个问题吗?

【问题讨论】:

  • This 相关问题可能会有所帮助。

标签: java algorithm math random


【解决方案1】:

如果你接受回收随机数,为什么要等待组合耗尽再回收?

  • 这使得数字在到达组合集的末尾时越来越随机
  • 这会迫使您维护一些数据库,以了解哪些号码已被使用,哪些尚未使用。

我只会生成随机数,而不关心它们是否已经被使用过。

如果您真的想按照您的要求保留它,您可以这样做:

  • 生成所有组合并将它们放入数据库表中
  • 将此表的大小存储在某个变量中
  • 生成一个介于 1 和表大小之间的随机数 R
  • 获取存储在表第 R 行的组合
  • 从表中删除第 R 行,并减小 size 变量
  • 当表为空(且size变量为0)时,重新开始

您可以通过将使用过的数字从一个表移动到另一个表来改进它,并在第一个表为空时使用第二个表而不是第一个表。

如果你有足够的内存,你也可以在内存中这样做。

【讨论】:

  • 您不认为从 9 亿条记录中提取会非常耗时。加上提取必须同步,因为每次请求新号码时您都会删除一行。加上这仅适用于 5 位数字。如果使用 6 位,数据库会变得更大。
  • 是的,这会很耗时。但是,如果 OP 不改变他的要求,我看不到任何其他方式。我的回答是建议避免这样做,并从一开始就回收数字。
  • 简单地生成一个随机数并没有帮助,因为在一段时间后可能会发生碰撞,尽管这种可能性很小,导致系统失明。
  • 正如我在回答中所说,OP 会回收随机数。一旦可能的值集用完,他就会这样做。因此,已经有可能两个用户共享相同的号码。从一开始就回收它们只会使这更有可能发生。
【解决方案2】:

您的代码可以是唯一的,也可以是算法生成的。

我了解您认为算法会将序数映射为代码,每个数字

否则你必须记住所有使用代码,这在技术上是无法实现的。

【讨论】:

    【解决方案3】:

    最好的方法是使用称为Format Preserving Encryption 或FPE 的加密技巧。您的问题与 FPE 的this application 非常相似。您的情况似乎最好通过使用Feistel network method 生成您的 FPE 来解决。在您的具体情况下,916132832 大约为 229.7,因此您应该使用 30 位宽的 Feistel 网络以及自行车行走。

    您选择一个随机密钥 AES 密钥 K 并维护该 K 以及一个计数器 C。C 从 0 开始,每次您分发代码时递增 1。该代码是C的FPE加密。当C达到916132832时,您已经使用了所有代码。您现在可以选择另一个 AES 密钥 K',设置 C=0,然后重新开始。根据您的应用程序,您可能需要保存所有未确认的 (K, C) 对。您可能希望对这些未确认的配对设置一个过期日期,以减少您的存储需求。

    【讨论】:

    • @Sannisetty Pavan:我真的没有更多信息。 Voltage Security 在销售支持 FPE 的产品时在其网站上有一些信息。
    【解决方案4】:

    使用 5 个字符,您可以安全使用 900 天,然后必须重置。

    几周前,我为另一个 StackOverflow 用户编写了一些代码。它是一个随机生成器,只生成新的数字。

    import java.util.BitSet;
    import java.util.Random;
    
    /**
     * Random number generator without repeat in the given range at construction time.
     * 
     * @author martijn
     */
    public class NoRepeatRandom {
    
        private Random random;
        private BitSet used;
        private int max;
    
        /**
         * Creates new instance of NoRepeatRandom, with the range <code>[0-max[</code>.
         * @param max the maximum for the range
         * @param seed the seed for the underlying {@link java.util.Random}
         */
        public NoRepeatRandom(int max, long seed)
        {
            this.max = max;
            this.used = new BitSet(max);
            this.random = new Random(seed);
        }
    
        /**
         * Creates new instance of NoRepeatRandom, with the range <code>[0-max[</code>.<br />
         * <code>System.currentTimeMillis()</code> is used as seed for the underlying {@link java.util.Random}
         * @param max the maximum for the range
         */
        public NoRepeatRandom(int max)
        {
            this(max, System.currentTimeMillis());
        }
    
        /**
         * Gives the next random number
         * @return a new random number. When finished it returns -1.
         */
        public int next()
        {
            if (isFinished())
            {
                return -1;
            }
            while (true)
            {
                int r = random.nextInt(max);
                if (!used.get(r))
                {
                    used.set(r);
                    return r;
                }
            }
        }
    
        /**
         * Tells if the random generator has finished. Which means that all number in the range
         * [0-max[ are used.
         * @return true if all numbers are used, otherwise false.
         */
        public boolean isFinished()
        {
            return max == used.cardinality();
        }
    
        /**
         * Sets all the numbers in the range [0-max[ to unused. Which means that all the numbers
         * can be reused.
         */
        public void reset()
        {
            used.clear();
        }
    
        /**
         * 
         * @return the maximum.
         */
        public int getMax()
        {
            return max;
        }
    }
    

    然后创建它的一个实例:

    NoRepeatRandom nrr = new NoRepeatRandom(916132832);
    

    要生成新代码,请使用:

    int codeInt = nrr.next();
    if (codeInt == -1)
    {
        // All the codes are used, need to reset the random generator!
    }
    String code = toCode(codeInt);
    

    剩下的唯一部分就是设计toCode(int)方法:

    public static final String charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw013456789";
    
    public static String toCode(int i)
    {
        String code = "";
    
        return code;
    }
    

    【讨论】:

    • 您的生成器将花费越来越多的时间来返回一个值。在每次迭代中,它会生成一个随机数,看看它是否被使用,如果没有,则使用另一个随机数重试。如果您有 100 万个潜在值,但只有 1 个未使用,您是否意识到它必须尝试的随机数数量?
    • @JB:啊,说得好。但我找不到更好的方法来制作唯一随机数字。
    【解决方案5】:

    我能想到的最佳解决方案是保持每天更新的私钥。使用密钥与手机号码组合生成一个 5 位代码并将此代码存储在数据库中。更新私钥时使代码无效并清除数据库。通过这种方式,您无需等待组合完成,而是决定现有代码何时失效。这种方法使您可以灵活地将代码大小从 5 增加到任何其他大小,并且您只存储已经使用的值。

    【讨论】:

      【解决方案6】:

      真的有可能用尽所有代码吗?你说只有100万用户。如果我理解正确,这意味着您只需生成 100 万个代码。如果是这种情况,那么可能的代码数量(例如 5 个字符)远大于您需要的数量,解决方案很简单:继续为新用户生成随机代码,直到找到一个没拍。您可以在哈希表中存储和查找使用过的代码。

      【讨论】:

        【解决方案7】:

        如果您不需要强大的安全性,那么一个简单的方法是

        1. 保持一个计数器 x0T-1T=62**n 一起运行,其中 n 是您要使用的字符数。
        2. 对于每个代码,您只需递增x,然后使用(x * P) % T 的编码,其中P 是一个与T 互质的大数,% 是取模运算符。

        作为PT 互质,可以保证映射(x * P) % T 是双射,因此所有代码都将在第一个代码被重用之前使用。

        因为有k,所以k*P = 1 (mod T),因此对于每个y,数字x = (k * y) % Ty的倒数,因为x*P = (k*y) * P = y * (k*P) = y * 1 = y (mod T)所以变换x -&gt; (x * P) % T)是满射的,因此也是单射的,因为这个空间是有限的。

        您也可以尝试使用一些更复杂的双射函数,例如将T 限制为 2 的幂并使用位改组,但是如果你真的需要安全性,最好每次只使用一个随机代码可能会检查它最近没有使用队列和位表或哈希表,具体取决于两者中的哪一个更小。

        【讨论】:

          【解决方案8】:

          您有 62 个字符(A-Z、a-z、0-9)。一个 5 个字符的代码实际上是一个 5 位的 base 62 数字。生成适当范围内的随机数,并将其转换为 base 62。

          为避免重复,请取足够大的数字范围并将范围随机排列。所有保证都是唯一的,并且没有任何特定的顺序。

          【讨论】:

            【解决方案9】:

            听起来您需要一个线性同余生成器。 LCG 是 X(n+1) = (a*X(n)+c) mod m 形式的简单递归函数。 LCG 可以满足您的需求并达到循环中的每个值。循环中的每个值都将映射到您指定的 5 位代码。

            公平警告,根据您的描述,我假设您并不真正需要随机因素,只是保证每个值对于循环都是唯一的。这个算法一点也不安全。任何人都可以从您发送的最后一个代码进入循环。如果您确实需要一些随机性,那么您会遇到一些麻烦。引用约翰·冯·诺依曼的话。 “任何考虑产生随机数字的算术方法的人当然都处于罪恶状态。”

            【讨论】:

              【解决方案10】:

              首先,你为什么不用UUID

              但如果您想自己生成数字,请尝试以下操作:

              预生成 10-20 百万个组合并将它们保存在内存中。 当您想要下一个 id 时,从它们中获取一个随机组合并将其从集合中移除。

              当集合变空时,使用原始组合重置集合(您可以保留原始集合的第二个副本以快速重置)。

              【讨论】:

              • 这在组合用尽时不会回收,过一段时间效率极低。
              • @Tim N - 然后他就可以预生成1000万个组合,每次只得到一个随机组合,取完就去掉。
              【解决方案11】:

              如果你正在寻找一些非常简单的东西,试试这个:

              Date date = new Date();
              String random = String.valueOf(date.getTime()).substring(6);
              

              这些数字在您不久的将来永远不会重复!

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2010-09-24
                • 2017-07-31
                • 1970-01-01
                • 2012-01-05
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2020-04-09
                相关资源
                最近更新 更多