起因
关于随机不重复字符串,如果没有长度限制,那么最简单的方法当前时间戳 + 固定位数随机字符串
的形式完全可以满足(仍有概率重复,但几乎可以忽略),至于长度,有很多办法解决,但最终也无法做到很短。
最近公司有个小需求,把邀请码降低到四位,四位不重复邀请码的实现方式就不那么随意了,直观的想法是通过10个数字(0-9) + 26个小写字母(a-z)排列组合实现,即所谓的排列可重复问题。
这类问题公式如下,每次n种选择,选择r次的排列共有:n的r次幂。这很好理解,一次有n种选择,第二次有n∗n种选择,……,第r次有nr种选择。
对应我们的需求,也就是可以有36的4次幂 = 1679616种组合,完全可以满足一个小型项目的需求。如果项目比较大,完全可以通过加入其他项(比如特殊字符、大写字母等等)到可择列表实现,最终还可以提升邀请码位数进一步扩展。
思路比较直观简单,但也比较实用,欢迎大家学习交流。
实现
思路:
1、首先要确定随机数的依据,采用递增数字(可以对应数据库id)作为基础,创建随机串。
2、打乱10个数字 + 26个字母的组合,这样随机数看起来会舒服一些。
3、把基数转换成36进制数字,转换后不足4位则补充0位。
4、把四位数字的每一位作为下标对应打乱后10个数字 + 26个字母的组合的列表。
5、组合成4位唯一随机字符串。
下面是代码:
exports.random = (number) => {
// 打乱的10个数字 + 26个字母
const arr = ["m","0","j","f","8","o","z","w","5","t","p","a","1","d","s","h","v","x","9","b","r","y","2","e","7","4","3","q","6","n","u","l","c","g","i","k"];
// 把number由十进制数转换成36进制数
const transNumber = binaryConversion(+number, 10, 36);
// 排除大于4位的情况
const len = transNumber.toString().length;
if (len > 4) {
console.log('数字过大');
return;
}
// 转换后的数字不足4位则补充0位,并按字符转换成数组
const list = prefixInteger(transNumber, 4).toString().split('');
const len4Arr = [];
for (const num of list) {
// 判断当前字符是数字还是字母
const type = checkStrType(num);
// 如果是数字不处理,如果是字母则转换成对应的数组(10 - 35)
const index = type === 'string' ? stringTonum(num) + 9 : num;
len4Arr.push(arr[+index]);
}
// 返回结果
return len4Arr.join('');
}
补充
缺点:基数数字低于36无法创建,比如起始数字大于36。
欢迎大家提出高效思路,不足之处请指出。
其他代码如下:
function binaryConversion(num, m ,n) {
return parseInt(String(num), m).toString(n);
}
function prefixInteger(num, length) {
return (Array(length).join('0') + num).slice(-length);
}
function stringTonum(a) {
const str = a.toLowerCase().split('');
const al = str.length;
const getCharNumber = charx => charx.charCodeAt() - 96;
let numout = 0;
let charnum = 0;
for (let i = 0; i < al; i++) {
charnum = getCharNumber(str[i]);
numout += charnum * Math.pow(26, al - i - 1);
};
return numout;
}
function checkStrType(str) {
//验证是否是英文
var pattern = new RegExp("[A-Za-z]+");
if (pattern.test(str)) return 'string';
//验证是否是数字
var patternNumber = new RegExp("[0-9]+");
if (patternNumber.test(str)) return 'number';
return '';
}