我正在寻找一种方法如何生成唯一的随机(随机查找)字母数字字符串,其约束条件是每个此类字符串在非相邻位置至少有两个不同的字符。字符串的长度应该支持至少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,它只会从列表中过滤掉一个或两个字符串,但这会增加更多记录。
相关问题:
答案 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。它比其他解决方案更长,但更随机。可能是一种更优雅的方式,但这是快速和可重复的。