具有至少两个差异的唯一字符串

时间:2017-04-11 14:29:06

标签: algorithm postgresql random

我正在寻找一种方法如何生成唯一的随机(随机查找)字母数字字符串,其约束条件是每个此类字符串在非相邻位置至少有两个不同的字符。字符串的长度应该支持至少20百万个唯一值(7个字符应该足够)。

示例:

AAAAAAA <- first string
AAAAABB <- does not work (different, but adjacent)
ABAAAAA <- does not work (only one different)
AABAABA <- that works perfectly

我第一次考虑使用一些标准函数(我目前正在使用PostgreSql,但我也可以使用Oracle)。我可以使用md5()之类的东西生成随机字符串,但我不知道如何满足其他约束。一个想法是使用levenshtein检查每个新生成的字符串对所有已生成的字符串,并仅在距离大于X时接受它,但这似乎是非常强力的解决方案。此外,leventshtein仅检查替换,因此不同的两个字符仍然可以相邻。

我目前的解决方案:

--PostgreSQL 9.5    
with t as (
select 
  generate_series(1, 200) as id,
  substr('abcdefghijklmnopqrstuvwxyz0123456789', trunc(random() * 36)::integer + 1, 1) ||
  substr('abcdefghijklmnopqrstuvwxyz0123456789', trunc(random() * 36)::integer + 1, 1) ||
  substr('abcdefghijklmnopqrstuvwxyz0123456789', trunc(random() * 36)::integer + 1, 1) ||
  substr('abcdefghijklmnopqrstuvwxyz0123456789', trunc(random() * 36)::integer + 1, 1) ||
  substr('abcdefghijklmnopqrstuvwxyz0123456789', trunc(random() * 36)::integer + 1, 1) ||
  substr('abcdefghijklmnopqrstuvwxyz0123456789', trunc(random() * 36)::integer + 1, 1) ||
  substr('abcdefghijklmnopqrstuvwxyz0123456789', trunc(random() * 36)::integer + 1, 1)     
    as rnd_string
)


select distinct id, rnd_string from (

select t1.id, t1.rnd_string, levenshtein(t1.rnd_string, t2.rnd_string)
from t  t1
join t  t2 on t1.id < t2.id
where levenshtein(t1.rnd_string, t2.rnd_string) > 3
) x

order by id

使用200个ID,它只会从列表中过滤掉一个或两个字符串,但这会增加更多记录。

相关问题:

3 个答案:

答案 0 :(得分:4)

基数为36的七位数为78,364,164,096。如果你通过基数为36(或十进制为1297)的步长101运行它们,你仍然有60,419,555个不同的数字,这些数字在至少2个非连续的位置都会有所不同。

aaaaaaa
aaaabab
aaaacac
...
aaaa9a9
aaababa
aaabbbb
aaabcbc
aaabdbd
...
aaab9b9
aaacaca
aaacbcb
aaacccc
...
aaa9999
aababaa
aabacab
aabadac
aabaead
...

如果您认为模式太明显,请从该范围的中间位置开始连续使用2000万个数字,以便没有人以“aaaa”开头,或使用不同的步长,如107(请参阅下面的更新) )。

关于101 36 (1297)以外的步长:

基数为36的数字包含以下期间:

digit:    1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18
period:  36  18  12   9  36   6  36   9   4  18  36   3  36  18  12   9  36   2

digit:   19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35
period:  36   9  12  18  36   3  36  18   4   9  36   6  36   9  12  18  36

当选择形状X0Y 36 的步长时,最小数字Y的周期不应小于第三个数字X的周期;例如步长106 36 (1302)将是一个糟糕的选择,因为经过六个步骤后,你只能在两个相邻的数字中得到差异:

aaaaaaa, aaaabag, aaaacam, aaaadas, aaaaeay, aaaafa4, aaaagba  

如果您想获得至少2000万条结果,则最大步长为3.0.29 36 (3917),因此您可以选择以下选项:

1.0.1  (1297)  1.0.5  (1301)  1.0.7  (1303)  1.0.11 (1307)  1.0.13 (1309)  1.0.17 (1313)  
1.0.19 (1315)  1.0.23 (1319)  1.0.25 (1321)  1.0.29 (1325)  1.0.31 (1327)  1.0.35 (1331)  
2.0.1  (2593)  2.0.2  (2594)  2.0.5  (2597)  2.0.7  (2599)  2.0.10 (2602)  2.0.11 (2603)  
2.0.13 (2605)  2.0.14 (0606)  2.0.17 (2609)  2.0.19 (2611)  2.0.22 (2614)  2.0.23 (2615)  
2.0.25 (2617)  2.0.26 (2618)  2.0.29 (2621)  2.0.31 (2623)  2.0.34 (2626)  2.0.35 (2627)  
3.0.1  (3889)  3.0.2  (3890)  3.0.3  (3891)  3.0.5  (3893)  3.0.7  (3895)  3.0.10 (3898)  
3.0.11 (3899)  3.0.13 (3901)  3.0.14 (3902)  3.0.15 (3903)  3.0.17 (3905)  3.0.19 (3907)  
3.0.21 (3909)  3.0.22 (3910)  3.0.23 (3911)  3.0.25 (3913)  3.0.26 (3914)  3.0.29 (3917)  

另外,第二个数字可以被赋予不同于零的值,例如, 1.5.7 36 (1483)或1.25.7 36 (2203)以进一步混淆模式。

进一步增加感知随机性的一种简单方法是改变字母表。下面的例子将所有想法结合在一起:它使用一个混洗字母表,步长为1.13.25 36 (1789)并从0.1.2.3.4.5.6 36开始。运行代码段以查看超过4000万个字符串中的前10,000个。

var alphabet = "nes2jf7tkd4ha6grlz9qm0bxp8w1ovi3u5cy";
var base = 36;
var digits = 7;
var step = 1789;                                // 1.13.25 (base-36)
var number = step * Math.ceil(63970746 / step); // skip to 0.1.2.3.4.5.6 (base-36)
for (var i = 0; i < 10000; i++) {
    var n = number;
    var str = "";
    for (var j = 0; j < digits; j++) {
        var d = n % base;
        n = (n - d) / base;
        str = alphabet.charAt(d) + str;
    }
    document.write(("nnnnnn" + str).substr(-digits) + ", "); // pad with zero character
    number += step;
}

由于该方法是可逆的,并且您可以将每个字符串转换回应该是步长的倍数的数字,您可以将其用作简单的第一个检查,以查看是否输入了例如字符串。在线表格有效。

答案 1 :(得分:1)

对我而言,听起来非常像Hamming Code。但是,您仍然必须确保生成的基本单词不会发生冲突。在真正的随机设备上,你必须检查是否相等。而不是检查你也可以尝试找到确定性的伪随机序列。

答案 2 :(得分:1)

CREATE OR REPLACE FUNCTION number_to_base(num BIGINT, base INTEGER)
  RETURNS TEXT
  LANGUAGE sql
  IMMUTABLE
  STRICT
AS $function$
WITH RECURSIVE n(i, n, r) AS (
    SELECT -1, num, 0
  UNION ALL
    SELECT i + 1, n / base, (n % base)::INT
    FROM n
    WHERE n > 0
)
SELECT string_agg(ch, '')
FROM (
  SELECT CASE
           WHEN r=0 then 'z'
           WHEN r=1 then 'q'
           WHEN r=2 then 'w'
           WHEN r=3 then 'k'
           WHEN r=4 then 's'
           WHEN r=5 then 'g'
           WHEN r=6 then 'v'
           WHEN r=7 then '2'
           WHEN r=8 then '7'
           WHEN r=9 then 'l'
           WHEN r=10 then 'b'
           WHEN r=11 then 'p'
           WHEN r=12 then 'n'
           WHEN r=13 then 'h'
           WHEN r=14 then '1'
           WHEN r=15 then '3'
           WHEN r=16 then 'm'
           WHEN r=17 then 'o'
           WHEN r=18 then 'e'
           WHEN r=19 then 'u'
           WHEN r=20 then 'r'
           WHEN r=21 then 'i'
           WHEN r=22 then '4'
           WHEN r=23 then 'j'
           WHEN r=24 then 'y'
           WHEN r=25 then '0'
           WHEN r=26 then 'd'
           WHEN r=27 then 'x'
           WHEN r=28 then 'f'
           WHEN r=29 then '9'
           WHEN r=30 then '5'
           WHEN r=31 then '8'
           WHEN r=32 then '6'
           WHEN r=33 then 't'
           WHEN r=34 then 'c'
           WHEN r=35 then 'a'
           WHEN r=36 then 'C'
           WHEN r=37 then 'E'
           WHEN r=38 then 'Z'          
           WHEN r=39 then 'H'
           WHEN r=40 then 'Y'
           WHEN r=41 then 'I'
           WHEN r=42 then 'W'
           WHEN r=43 then 'Q'
           WHEN r=44 then 'M'
           WHEN r=45 then 'L'
           WHEN r=46 then 'P'
           WHEN r=47 then 'O'
           WHEN r=48 then 'K'
           WHEN r=49 then 'X'
           WHEN r=50 then 'S'
           WHEN r=51 then 'A'
           WHEN r=52 then 'U'
           WHEN r=53 then 'R'
           WHEN r=54 then 'V'
           WHEN r=55 then 'G'
           WHEN r=56 then 'B'
           WHEN r=57 then 'D'
           WHEN r=58 then 'N'
           WHEN r=59 then 'J'
           WHEN r=60 then 'F'
           WHEN r=61 then 'T'
           ELSE '%'
         END ch
  FROM n
  WHERE i >= 0
  ORDER BY i DESC
) ch
$function$;


select 
number_to_base((((gs % 
9)+1)::text||reverse(((gs*107)+20000000)::text))::bigint,62), gs
from generate_series(1,1000) gs

107用于创建2个字符差异。 2000万硬编码用于确保您有20个Mil代码,它们都具有相同的位数。其他功能用于帮助使数据呈现伪随机,但是该过程完全可以使用数据反转,或者根据ID重新生成。

为了测试,我将结果限制在1000。它比其他解决方案更长,但更随机。可能是一种更优雅的方式,但这是快速和可重复的。