【问题标题】:Algorithm for incrementing a String in a non-obvious manner以非显而易见的方式递增字符串的算法
【发布时间】:2011-09-29 15:41:33
【问题描述】:

我想创建外观随机的 5 或 6 个字符的字母数字字符串,例如:

Vg78KY

Creating (pseudo-)random Strings 已得到解答,但我想知道是否有一种算法可以以不明显的方式 incrementing String。上述字符串的简单增量可能会产生:

Vg78KZ

但我不希望下一个字符串是可猜测的,我希望它看起来完全不同。当然,连续的增量不应产生先前的结果,因为每个增量都应该是唯一的。

任何关于如何实现这一目标的想法都非常感谢!

谢谢

【问题讨论】:

  • “以非显而易见的方式增加字符串”与生成伪随机字符串有何不同?
  • @matt b - 我承认这是一个很好的区别。我希望避免检查生成的字符串是否存在,虽然这不太可能,所以你必须做这个检查。另外,我认为这是一个有趣的问题,可能会超出我的基本需求:)
  • 简单,当你这样做时不要那么明显。
  • @Richard - 所以唯一的区别是“递增”不会返回已经生成的字符串?
  • @matt b - 是的,它是完全等价的。但是我/曾经假设 GUID 必须超过 5 或 6 个字符。

标签: java string algorithm random


【解决方案1】:

你所说的增量究竟是什么意思?如果您只想要一些原始值的结果,您可以使用哈希码(可能是加密哈希)。然后只需使用您想要使用的字符(例如 Base64 或类似的东西)对其进行编码,并将其剪切为您想要的字符数。

但是,这是一种单向操作。这意味着您可以轻松获得一个值的后继,但不能轻易获得前任。

【讨论】:

    【解决方案2】:

    使您的字符串成为哈希运算的结果。例如,使用您的随机字符串作为输入:

    String input1 = "Vg78KY";
    String output1 = String.valueOf(input1.hashCode());
    
    String input2 = "Vg78KZ";
    String output2 = String.valueOf(input2.hashCode());
    

    output1 和 output2 将完全不同。

    【讨论】:

    • 是的,当然,很好的解决方案。但是这么短的字符串不会有碰撞风险吗?
    • 我认为这不是@richard-h 想要的。他想要简短的字母数字散列,在递增时不会重复且不可猜测。
    • hashCode 返回一个 int,所以你的代码是错误的,不会返回任何字符,只是数字。
    【解决方案3】:

    避免查找表需要的简单方法是:

    • 正常递增整数
    • 以非显而易见的方式排列位(固定排列可能很好,但如果您想要更复杂的东西,您可以使用类似George Marsaglia's XORShift algorithm 的东西,它会产生一个伪随机整数序列,仅在很长的循环后重复)
    • 转换为Base64 encoded strings

    【讨论】:

    • +1,如果 Xorshift 参考资料给你 +10,谢谢!
    • 我在 wiki 上添加了前 50 个数字 :)
    • 您甚至可以为您的编码使用一个不寻常的基数,例如基数 59... 或 42。没有什么说您必须使用标准基本编码之一。
    • 请注意,链接的 XORShift 算法适用于 128 位状态,并且只有 32 位输出。不能保证它不会通过以远低于其完整周期的频率输出相同的 32 位 int 来给您带来冲突。
    【解决方案4】:

    如果你想让它递增,这意味着你有一些转换函数 F() 可以从一个空间转换到另一个空间。

    所以你可能有一个来自 {Z} -> {SomeString}

    的函数

    所以你需要做的,只是将 F() (F-1) 的反面应用到字符串,获取原始数字,增加它,然后再次生成它。

    在伪代码中:

    int i = 1;
    String s = Transform(i);
    int num = UnTransform(s);
    num++;
    String next = Transform(num);
    

    【讨论】:

      【解决方案5】:

      一种可能的解决方案是预先生成所有可能字符串的整个字典,然后使用SecureRandom 指向该字典的索引。如果某个特定元素已经被“保留”,您只需转到下一个可用的元素(此操作也可以预先生成)。

      此解决方案的明显缺点是不确定性。但这不是 OP 要求的。而且我什至不确定在这种情况下是否可以使用确定性。

      【讨论】:

      • 假设字符串集由长度为 5 和 6 的所有字母数字(包括大写和小写)字符串组成,每个字符大小为一个字节,这将占用近 54 GB 的空间.最低限度。但是,只有大写字母和数字,它会将其缩小到 2 GB 以上。
      【解决方案6】:

      如果我们假设必须有一个从“序列号”到“随机字符串”的 1:1 映射,那么截断散列方法将不起作用,因为不能保证截断散列不会受到碰撞。

      我会这样做:

      • 依次取下一个整数。
      • 与固定数字异或。
      • 置换位。
      • 使用 Base64、Base36 或其他方式对数字进行编码。

      请注意,如果攻击者能够访问足够多的序列号,则可以轻松分析这一点。

      【讨论】:

      • 感谢您的建议。它不必是防弹的,相当硬(甚至相当硬)就可以了。
      • @RichardH:硬度将取决于“置换位”步骤。对于 N 位的输入整数,您最好的选择可能是找到一个具有 N 位内部状态和周期为 2**N-1 的良好 PRNG。使用输入整数作为 PRNG 的内部状态,多次运行算法,然后使用新的内部状态作为输出,编码为您的字符串。
      【解决方案7】:
      import java.util.UUID;
      public class RandomStringUUID {
      
          public static void main(String[] args) {
      
              UUID uuid = UUID.randomUUID();
              String randomUUIDString = uuid.toString();
      
              System.out.println("Random UUID String = " + randomUUIDString);
              System.out.println("UUID version       = " + uuid.version());
              System.out.println("UUID variant       = " + uuid.variant());
      
          }
      }
      

      如果您想生成碰撞安全字符串,只需使用 UUID

      【讨论】:

      • UUID 的长度不超过 5 或 6 个字符吗?
      • 是的,但如果您想获得唯一的字符串,即使在不同的机器上同时生成它们,它也是开箱即用的解决方案。
      【解决方案8】:

      这个呢:

      1. 将数字转换为二进制格式;
      2. 通过固定的手动映射更改数字顺序(最后一位到第 6 位等);
      3. 将数字转换回哈希

      【讨论】:

      • 如果知道足够的后续数字哈希,仍然可以轻松猜出这种方法和其他方法。最好的办法是依靠带盐的散列并检查唯一性,如果散列小到有匹配的风险。
      【解决方案9】:

      惰性方法:保留一个哈希表或集合来存储所有现有的字符串,每次生成一个随机字符串时,检查它是否在集合中。如果是这样,生成一个新的,直到你得到一个不在集合中。

      不过,从长远来看,这可能会占用大量内存和处理器。

      【讨论】:

        【解决方案10】:

        您可以尝试将以下 Python 转换为您选择的语言...

        >>> import string, random, itertools
        >>> digits = list(string.ascii_lowercase + string.ascii_uppercase + string.digits + '_')
        >>> ''.join(digits)
        'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'
        >>> digit_count = 4
        >>> alldigits = [digits[:] for i in range(digit_count)]
        >>> for d in alldigits: random.shuffle(d)
        
        >>> numbergen = (''.join(d) for d in itertools.product(*alldigits))
        >>> numbergen.__next__()
        'B1xx'
        >>> numbergen.__next__()
        'B1x1'
        >>> numbergen.__next__()
        'B1xQ'
        >>> numbergen.__next__()
        'B1x7'
        

        【讨论】:

          【解决方案11】:

          既然你希望字符串是字母数字的,那么它就很简单了。创建一个大小为 62 的字符数组。这是 26 个小写字母、26 个大写字母和 0-9 的 10 个数字。填充数组后,循环 N 次,其中 N 是字符串的长度,每次选择一个随机索引。所以它应该看起来像这样:

             char [] arrayOfCharacters = new char[62];
             int index = 0;
             for(char a = 'a';a<= 'z';a++)
             {
                     arrayOfCharacters[index++] = a;
             }//copy this loop for the upper case characters and 0-9
             String randomString = "";
             for(int x = 0;x < N; x++)
             {
                     randomString += arrayOfCharacters[(int)(Math.random() * arrayOfCharacters.length)];
             }
          

          【讨论】:

            【解决方案12】:

            另一种简单的方法是:

            $hash_key = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
            $hash_table = array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J');
            
            $init = 10000000;
            $hash_value = str_replace($hash_key, $hash_table, strval($init));
            
            //$hash_value = 'BAAAAAAA'
            
            //For next value:
            $init++;
            $hash_value = str_replace($hash_key, $hash_table, strval($init));
            //$hash_value = 'BAAAAAAB'
            
            //If you want to increment only the string without knowing the digits:
            $prev_hash = $hash_value;
            $prev_init = intval(str_replace($hash_table, $hash_key, $prev_hash));
            //$prev_init = 10000001
            
            $next_init = $prev_init + 1;
            $next_hash = str_replace($hash_key, $hash_table, strval($next_init));
            //$next_hash = 'BAAAAAAC'
            

            希望这会有所帮助。 :-)

            【讨论】:

            • 抱歉,没有注意到 Java 标签。我相信您可以在 Java 中为该算法找到类似的替代方案。 :-)
            【解决方案13】:

            这是我的代码.. 它完全按照您的要求使用 UUID 生成字符串,然后从中执行 (-)。

            import java.util.*;
            class KeyGen {
                public static void main(String[] args) {
                    String uuid = UUID.randomUUID().toString();
                    String str = uuid.replaceAll("[-]", "").toUpperCase();
                    String s = "";
                    Scanner scan = new Scanner(System.in);
                    String[] array = str.split("");
                    Random rnd = new Random();
                    int N = rnd.nextInt(str.length());
                    System.out.println("How many keys you want?");
                    int keys = scan.nextInt();
                    String[] rndstr = new String[keys];
                    System.out.println("How many letters for the first key?");
                    int count = scan.nextInt();
                    for (int t = 0; t < keys; t++)
                    {
                        s="";
                        count++; 
                        for(int i=0; i < count; i++)
                            {
                                uuid = UUID.randomUUID().toString();
                                str = uuid.replaceAll("[-]", "").toUpperCase();
                                int len = str.length();
                                N= rnd.nextInt(len) + 1;
                                s = s + array[N]; 
                            }
                        rndstr[t] = s;
                    }
                    for (int j=0; j < rndstr.length; j++)
                    {
                        System.out.println(rndstr[j]);
                    } 
            }
            } 
            

            简单输出:

            How many keys you want?
            4
            How many letters for the first key?
            6
            
            Here are your keys:
            5F2934A
            C8A456A6
            B06E49240
            FE3AE40CCE
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-12-23
              • 2014-12-29
              • 1970-01-01
              相关资源
              最近更新 更多