概述
学习二维码生成的时候卡在纠错码部分,然后就接触到了伽罗华域,了解到模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) {
//头部去0
bs0 = 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);
//首位除0
p0 = p0.replace(/^0/, '');
//从p1头部获取一位
if (p1.length) {
p0 += p1.shift()
}
}
return quo.replace(/^0+/, '');
}
取模M2Mod(a,b)
其实是取商运算的简化,不用对商做维护;
M2Mod('1011010','10011')
=> "101"
101
---------
10011 丿1011010
10011
------
01011
00000
-----
10110
10011
------
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);
//首位除0
p0 = 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)
};