
虾搞,数字换算为固定长度的进制码
类似短网址算法,把数字换算为固定长度的进制码。纯属个人兴趣研究,暂无实际论证,仅供思路参考。
采用语言,Javascript( ES6+ )。
短网址,相信这个已经不是什么陌生产物。经典的短网址示例,t.cn/abcdefg。
这里,不谈短网址具体的实现方案,就说说关于将数字转换成进制key的方案。
62进制,将数字0-9,小写a-z,大写A-Z;
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
自己写的也好,别人写的也好,或者找在线工具,都能进行换算;
例如数字"123",转换后,可以得到一个62进制数"1Z"。
我瞎搞的方法:
class Base { constructor() { this.char = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); this.hex = this.char.length; } toKey(n) { if (!n) return this.char[0]; const num = n * 1; const a = Math.floor(num / this.hex); const b = num % this.hex; let str = ''; if (a > 0) { str += this.toKey(a); } str += this.char[b]; return str; } toNum(s) { if (!s) return 0; const str = s.toString(); const len = str.length; const list = str.split(''); let num = 0; list.forEach((i, key) => { let index = this.char.indexOf(i); if (index == -1) index = 0; const now = index * Math.pow(this.hex, len - key - 1); num += now; }); return num; } }
但可以看到,123转换后,进制数1Z,长度只有2位。
如果想要得到一个固定长度key,可能就需要将数字的起始数做大一点;
例如62进制,想要一个长度为6位的key,至少需要从916132832后开始。
这是一个相对的方案,算是满足了6位长度。
而我期望的是,进制码长度固定,数字ID也不能从这么大就开始。如果可以,最好前后数字得到的进制码,没有一眼看穿的规律。
这类方案就可以用于邀请码、兑换码等功能的使用。
一、可以避免重复性;
二、相对系统可以反向得到出数字ID,而不是查询数据库得到对应的;
具体,在设计对应功能的时候再考虑。
如何实现长度固定位数?假设为6位。
我的想法与方案:
把62进制的char,拆成两部分。
从62位中,随机抽取出固定长度部分,做为填充替换符号。假设抽取了10位为替换符号,那么就变成了52位进制法。
1、先用数字ID转换为52进制码,在没有超过最低数字的情况下,长度会不足6位。
2、得到进制码长度x,从替换符号中,随机获得(6-x)长度的字符。
3、在进制码随意位置,循环随机插入替换符号,得到一个长度为6的特殊进制码。
如何反向推算数字?
1、获得特殊进制码,字符串替换方法,将替换符号全部剔除;
2、正常的进制码,反向推算数字;
示例代码:
/** * 将数字转换成自定义混淆进制码 */ class Base { constructor(len = 6) { this.char = '456789defghijklmnopqrstuvwxyzDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); // 进制码 this.symbol = '0123abcABC'.split(''); // 混淆码 this.charLen = this.char.length; // 进制编码长度 this.symbolLen = this.symbol.length; // 混淆填充编码长度 this.len = len; // 期望的固定长度,如果数字过大,长度会超过这个数字 } /** * 数字转进制码 * @param {Number} n 数字 */ toKey(n) { if (!n) return this.char[0]; const num = n * 1; const a = Math.floor(num / this.charLen); const b = num % this.charLen; let s = ''; if (a > 0) { s += this.toKey(a); } s += this.char[b]; return s; } /** * 进制码转数字 * @param {Number} s 进制码 */ toNum(s) { if (!s) return 0; const str = s.toString(); const len = str.length - 1; let n = 0; str.split('').forEach((i, index) => { let key = this.char.indexOf(i); if (key === -1) key = 0; const now = key * (this.charLen ** (len - index)); n += now; }); return n; } /** * 随机数 * @param {Number} a 起始数字 * @param {Number} b 截止数字 */ random(a, b) { return Math.floor(Math.random() * (a - b)) + b; } /** * 在字符串中随机插入一个字符 * 前提是str.length > 1 * * @param {String} str * @param {String} ins */ randomInsert(str, ins) { if (str.length === 1) { return `${str}${ins}`; } const r = this.random(0, str.length + 1); const s1 = str.substring(0, r); const s2 = str.substring(r); return `${s1}${ins}${s2}`; } /** * 把KEY转出固定位数 * 不足部分混入替补符号 * 剔除替补符号以后,原字符串保持不变 * * @param {String} key 已转换的进制码 */ toFixed(key) { if (key.length >= this.len) return key; const num = this.len - key.length; let symbol = []; symbol.length = num; symbol.fill(1, 0); symbol = symbol.map(() => this.symbol[this.random(0, this.symbolLen)]); if (num < 2) { return this.randomInsert(symbol.join(''), key); } let newKey = key; symbol.forEach(i => newKey = this.randomInsert(newKey, i)); return newKey; } /** * 清除key中替补符号 * * @param {String} key 待清除的进制码 */ clearSymbol(key) { const regexp = new RegExp(`[${this.symbol.join('')}]`, 'g'); return key.replace(regexp, ''); } /** * 获取keyId * * @param {Number} n 转换的数字 */ getKeyId(n) { const key = this.toKey(n); return this.toFixed(key); } /** * 获取NumId * * @param {String} s 转换的进制码 */ getNumId(s) { if (!/[a-zA-Z0-9]/.test(s)) { throw Error('getNumId传入的字符串不正确'); return null; } const key = this.clearSymbol(s); return this.toNum(key); } } export default Base;
为了增加混淆性,还可以将进制码的char字符串先自行随机一次。