概述
学习二维码生成的时候卡在纠错码部分,然后就接触到了伽罗华域,了解到模2运算,恰好前不久刚了解了波兰表达式,就尝试写一个支持模2运算的算式解析计算。
结果
'10011*101+101'.M2Calc()
=>"1011010"
涉及内容
- 模2运算
- 波兰表达式(运算式解析)
实现
没有经过大量检测,如有问题请指正。
部分解读为个人理解,表述不会太标准。
异或xor(a,b)
mod2 加,减,乘,除都用到异或操作,js中的异或符^
是将两侧内容看作10进值然后转成2进值异或,如下
'1001100' ^ '10011' //1008023 错误
(parseInt('1001100',2)^parseInt('10011',2)).toString(2) //"1011111" 正确
因此不能直接使用此符进行异或,需要先转成十进值,运算完成后在再转回二进制字符串;或者也可以自定义二进制异或处理方式。
将异或作为公共方法
/*** 二进制字符串异或* @param bs0* @param bs1* @param type* @returns {string}*/
function xor(bs0, bs1, type = 1, resultLen) {//头部去0bs0 = bs0.replace(/^0+/, '');bs1 = bs1.replace(/^0+/, '');//多种方式进行异或//1) 异或符let result;if (type == 1) {result = (parseInt(bs0, 2) ^ parseInt(bs1, 2)).toString(2);}//2)自定义二进制字符串异或if (type == 2) {let maxLen = Math.max(bs0.length, bs1.length);bs0 = bs0.padStart(maxLen, '0');bs1 = bs1.padStart(maxLen, '0');let temp = '';for (let i = 0; (bs0[i] != null || bs1[i] != null); i++) {temp += (bs0[i] || 0) ^ (bs1[i] || 0)}result = temp.replace(/^0+/, '');}return resultLen ? result.padStart(resultLen, '0') : result
}
加法 M2Add(a,b)
mod2 加法定义为0+0=0
;0+1=1
; 1+0=1
; 1+1=0
;实际上就是二进制的异或
/*** 模2加法 等于异或* * @param bs0* @param bs1* @returns {string}*/
function M2Add(bs0, bs1) {return xor(bs0, bs1,1)
}
减法 M2Sub(a,b)
mod2 减法定义为 0-0=0
; 0-1=1
; 1-0=1
; 1-1=0
;实际上也是二进制的异或
/*** 模2减法 等于 异或* * @param bs0* @param bs1* @returns {string}*/
function M2Sub(bs0, bs1) {return xor(bs0, bs1,1)
}
乘法M2Pow(a,b)
M2Pow('10011','101')
=>"1011111"
10011
X 101
---------10011 组1 (这里标注'组'与下方乘法函数中的注释对应)10011 组2
---------1011111
/*** 模2乘法* @param bs0 乘数 头部去零* @param bs1 乘数 头部去零* @returns {string} 如果输入的乘数头部不为零,则输出结果头部也不为零*/
function M2Pow(bs0, bs1) {let arr = [];//保存每一组乘数结果for (let i = 0; i < bs1.length; i++) {if (bs1[i] == '1') {arr.push(bs0 + ''.padStart(i, '0')) //补零}}//累计求和,不直接调用模2加,采用更直接的方式return arr.reduce((a, b) => xor(a, b,1), '0');
}
除法(取商) M2Div(a,b)
M2Div('1011010','10011')
=> "101"
/*** 模2 取商运算* @param bs0* @param bs1* @returns {string}*/
function M2Div(bs0, bs1) {//TODO 可以使用查表优化的,但是要分情况,所以暂时不用let fastDict = {'11': '0101','10': '0110','01': '0010','00': '0000',};//头部去0操作bs0 = bs0.replace(/^0+/, '');bs1 = bs1.replace(/^0+/, '');let bs1_len = bs1.length;//被除数根据除数的长度分为两部分,第一部分长度为除数长度let p0 = bs0.substr(0, bs1_len);let p1 = bs0.substr(bs1_len).split('');//每次相除只要余数长度(头部去零之后的长度)大于等于除数,就可以继续进行下一轮运算let quo = '';while (p0.length >= bs1_len) {//第一部分的首位为0,则与0 模2减,否则与除数模2减quo += p0[0];p0 = xor(p0, p0[0] == '0' ? '0': bs1,1,bs1_len);//首位除0p0 = p0.replace(/^0/, '');//从p1头部获取一位if (p1.length) {p0 += p1.shift()}}return quo.replace(/^0+/, '');
}
取模M2Mod(a,b)
其实是取商运算的简化,不用对商做维护;
M2Mod('1011010','10011')
=> "101"
101---------10011 丿101101010011------0101100000-----1011010011------101
/*** 模2 取余运算,没有余数返回 ''* @param bs0 被除数* @param bs1 除数* @returns {string}*/
function M2Mod(bs0, bs1) {let fastDict = {'11': '0101','10': '0110','01': '0010','00': '0000',};//头部去0操作bs0 = bs0.replace(/^0+/, '');bs1 = bs1.replace(/^0+/, '');let bs1_len = bs1.length;//被除数根据除数的长度分为两部分,第一部分长度为除数长度let p0 = bs0.substr(0, bs1_len);let p1 = bs0.substr(bs1_len).split('');//每次相除只要余数长度(头部去零之后的长度)大于等于除数,就可以继续进行下一轮除法while (p0.length >= bs1_len) {//第一部分的首位为0,则与0 模2减,否则与除数模2减p0 = xor(p0, p0[0] == '0' ? '0': bs1,1,bs1_len);//首位除0p0 = p0.replace(/^0/, '');//从p1头部获取一位if (p1.length) {p0 += p1.shift()}}return p0.replace(/^0+/, '');
}
运算式M2Calc(str)
使用逆波兰表达式计算
'10011*101+101'.M2Calc()
=>"1011010"
/***判断是 运算符|操作数|括号*@return 1:操作数 2:括号 3:运算符*/
function typeChk(d) {return /[01]+/.test(d) ? 1 : (/[\(\)]/.test(d) ? 2 : 0) //操作数返回1
}/*** 处理输入* 1) 替换中英括号* 2) 去除空格* 4) 返回字符分割*/
function dealStr(str) {return str.replace(/(/, '(').replace(/)/, ')').replace(/[^01\(\)\+\-\*\/%]/g, '').match(/([01]+)|\(|\)|\+|\-|\*|\/|%/g)
}/*** 模2算式计算 + - * /(取商) %(取模)* 使用逆波兰表达式* @param str*/
function M2Calc(str) {//一般的算数表达式即为中缀表达式str = str || "10011 * 101";//这里可以不用加默认组织//运算符权重let sdw = {'+': 1, '-': 1, '*': 2, '/': 2, '%': 2};//运算符转模2计算let fnDict = {'+': M2Add, '-': M2Sub, '*': M2Pow, '/': M2Div, '%': M2Mod};//循环安全数let saveCircle = 50;let operand = [];//操作数栈let operator = [];//操作符栈//去空格(主要是避免数字被空格拆分),分割成数组let arr = dealStr(str);//按照规则入栈let type;for (let i = 0, curr; (curr = arr[i]) != null; i++) {type = typeChk(curr);if (type == 1) {operand.push(+curr)} else if (type == 0) {let si = 0, stackTop;while (true && si++ < saveCircle) {stackTop = operator[operator.length - 1];if (stackTop == null || stackTop == '(') {operator.push(curr);break;} else if (sdw[curr] > sdw[stackTop]) {operator.push(curr);break;} else {operand.push(operator.pop())}}} else {if (curr == '(') {operator.push(curr)} else {let s, si = 0;while (s != '(' && si++ < saveCircle) {s = operator.pop();if (s == '(') {break;} else {operand.push(s)}}}}}//将操作符栈中剩下的弹出压入操作数栈let prevStack = [...operator, ...operand.reverse()];//计算let resultStack = [], left, right;for (let i = prevStack.length - 1, curr; (curr = prevStack[i]) != null; i--) {if (typeChk(curr)) {//是操作数resultStack.push(curr)} else {//是操作符 注意这里right/left 和后缀是相反地,先出的是右right = resultStack.pop();left = resultStack.pop();resultStack.push(fnDict[curr](left + '', right + ''))}}// console.log(prevStack);// console.log(resultStack);return resultStack.length != 1 ? '表达式有误' : resultStack[0]
}
//绑定到字符串
String.prototype.M2Calc = function(){return M2Calc(this)
};