本文参照护照编码的规则实现类似算法,仅供学习研究使用
先看一下护照的编码规则 :
例如:E00000001、E00000002、E00000003...E99999999、EA0000001
【规则分析】
可以发现,固定E开头,后面的数字按顺序增加,到达99999999时高位开始变成字母从A开始
也就是说先使用8位数十进制开始顺序编码,十进制数字用完以后由高位至低位逐渐变成36进制(10个数字+26个大写字母)
【实现解析】
如果直接使用36进制去编码,显然不对,就会出现 00000A 这样的编码。而需要让字母在高位 必须先使用10进制,10进制用完再改变高位的进制类型,也就是说不同位数使用不同的进制来处理,到达该进制最大值时升级进制。
以下是实现效果和完整的代码,先看效果,再讲解代码:
【效果展示】
* 为方便查看结果,使用3位长度测试
图1:开始使用十进制编码
图2:达到十进制最大值999以后高位变成36进制
图3:到达36进制3位数最大值ZZZ后报错
【代码】
// 不同进制配置 let num36 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; let num10 = '0123456789'; // val=当前值 ,numList=进制列表(支持不同位使用不同的进制) function plusOne(val, numList) { // 拆分成单个位数,反转后让索引为0的=低位 let valArr = val.split('').reverse(); numList.reverse(); // 指定进制,在当前位的基础上加1位,返回'0'时表示需要向前进一位 function add(num, cur) { let nexIndex = num.indexOf(cur) + 1; if (nexIndex === num.length) nexIndex = 0; return num[nexIndex]; } // 从低位到高位处理 for (let i = 0; i < valArr.length; i++) { let c = add(numList[i] || num10, valArr[i]); valArr[i] = c; // 没有达到进位要求 if (c !== '0') break; // 等于0时需要在高一位的位置加1,push增加一个高位 valArr.push(''); } return valArr.reverse().join(''); } // 获取当前数值下一个值,限制总位数 function nextVal(curVal, len) { // 计算不同位的进制 let numList = []; // 处理不同位的进制, 总位数达到设置的长度才开始升进制 if (curVal.length === len) { let curValArr = curVal.split(''); numList = curValArr.map((a) => { // 当前位本来就是36进制的 return num10.indexOf(a) === -1 ? num36 : num10; }); // 将36进制位的后一位到达9的10进制位变成36进制 for (let i = 0; i < curValArr.length; i++) { if (numList[i].length === num36.length) continue; // 最高位达到9升36进制,非最高位达到9并且它的上一位到达36进制最后一个值也升36进制 if (num10.indexOf(curValArr[i]) === num10.length - 1 && (i === 0 || curValArr[i - 1] === num36[num36.length - 1])) { numList[i] = num36; } } } let nextVal = plusOne(curVal, numList); // 超出最大值时报错 if (nextVal.length > len) throw new Error('Digital exceeds the maximum'); // 高位补0并返回 return (new Array(len).join('0') + nextVal).substr(-1 * len); } // 计算指定位数可生成的最大编码量 function allCount(len) { let max = 0; // 计算超过10进制最大值后的数量 for (let i = 0; i < len; i++) { let tmp = 26; for (let j = 0; j < len - i - 1; j++) { tmp = tmp * 10; } max += tmp; } // 10进制最大值 max += Math.pow(10, len); console.log(max); } allCount(3); // 测试 let testNum = '0'; for (let i = 0; i < 20000; i++) { testNum = nextVal(testNum, 3); console.log(testNum); }
* 直接使用nodejs跑这段代码即可看到输出结果
【代码解析】
代码中共出现3个方法,生成编码主要使用了其中两个方法:plusOne、nextVal,计算指定长度最大生成编码数量方法:allCount
plusOne 负责通过指定当前值和进制方案增加自然数1返回结果
nextVal 负责指定当前值和编码长度(数字位数)生成进制方案并调用plusOne获得结果并对结果进行范围判断(超过指定位数允许的最大值时将throw异常)、高位补0操作
allCount 传递编码长度计算可生成的最大编码数量,以便根据业务设定规则