需求
最近弄薪资模块, 需要解析一个字符串运算规则解析(这东西叫啥名字我也不知道);
反正就是计算类似于这样一个字符串: ((("参数1" + 1) * 2 + ("参数2" + 1) * 2) + 1) * 2
还真的没有想到曾今遇到的算法题会运用的工作上。。。。
代码
里面做调试的注释也懒得删了,方便以后忘记了从新调试。。。。
对于 精度丢失
的问题的话,导一下自己熟悉的 第三方包 重写一下 clac
方法就可以了。
class MyCalc {
paramsObj = {
};
operator = [];
constructor(paramsObj, operator = []) {
this.paramsObj = paramsObj;
this.operator = operator.filter(item => item != "(" && item != ")");// 左右括号做单独管理
}
// main
do(aimStr = "") {
aimStr = this.formatAimStr(aimStr);
return this.clacAimStr(aimStr);
}
/**************************************
* 计算携带括号的字符串
* @param {String} aimStr
* @returns {Number}
*************************************/
clacAimStr(aimStr = "") {
aimStr = aimStr?.replace(/\s/g, "") || "";
aimStr = "(" + aimStr + ")";
let leftBracketIndexs = []; // 左括号的脚标
for (let i = 0; i < aimStr.length; i++) {
if (aimStr[i] === "(") {
leftBracketIndexs.push(i);
// console.log(leftBracketIndexs);
}
else if (aimStr[i] === ")") {
let lastLBIS = leftBracketIndexs.pop();
let baseStr = aimStr.slice(lastLBIS + 1, i);
let res = this.clacBaseStr(baseStr);
// console.log(aimStr, "(" + baseStr + ")", res);
aimStr = aimStr.replace("(" + baseStr + ")", res)
i = leftBracketIndexs[leftBracketIndexs.length - 1];
}
}
return Number(aimStr);
}
/***********************************************************************
* 根据 this.paramsObj , 对传入的字符串做计算
* - 传入的字符串为最基本的字符串, 只含有运算符和数值、变量, 不含有括号
* @param {String} aimStr
************************************************************************/
clacBaseStr(aimStr = "") {
aimStr = aimStr?.replace(/\s/g, "");
let stack = [];
// 数字压栈
function addNum(num) {
num = Number(num);
if (stack.length > 0) {
let op = stack.pop();
if (op === "+" || op === "-") {
stack.push(op);
}
// 如果是乘除, 先与前一个数字做运算再压栈
else if (op === "*" || op === "/") {
let preNum = Number(stack.pop());
num = op === "*" ? preNum * num : preNum / num;
}
}
stack.push(num);
}
// 先计算乘除
let isGetNum = true; // 当前是取数字还是运算符
while (aimStr.length > 0) {
if (isGetNum) {
let t = this.getFirstValue(aimStr);
let num = t.value;
aimStr = t.str;
addNum(num);
} else {
let t = this.getFirstOperator(aimStr);
stack.push(t.value)
aimStr = t.str;
}
isGetNum = !isGetNum;
}
// 再加减
let isAdd = true;
let result = 0;
stack.forEach(item => {
if (item === "+") {
isAdd = true }
else if (item === "-") {
isAdd = false }
else {
result += isAdd ? +item : -item;
}
})
return result;
}
/********************************
* 获取第一个计算值 数值 | 变量
* @param {String} aimStr
* @return {Object}
* - value: 传入 aimStr 中截取的 第一个计算值 数值 | 变量(参数1)
* - str: 截取了 value & operator 之后的字符串
********************************/
getFirstValue(aimStr = "") {
aimStr = aimStr?.replace(/\s/g, "") || "";
let result = {
value: null, str: null, };
let startIndex = 0; // 第一个非运算符字符的脚标(支持负数)
for (let i = 0; i < aimStr.length; i++) {
if (!this.operator.includes(aimStr[i])) {
startIndex = i;
break;
}
}
let lastIndex = startIndex; // 当前需要截取的数值 | 变量的末尾脚标
for (let i = startIndex; i < aimStr.length; i++) {
if (this.operator.includes(aimStr[i])) {
lastIndex = i;
break;
}
else if (i === aimStr.length - 1) {
lastIndex = aimStr.length;
break;
}
}
result.value = aimStr.slice(0, lastIndex);
result.str = aimStr.slice(lastIndex);
return result;
}
/********************************
* 获取第一个有效运算符
* @param {String} aimStr
* @return {Object}
* - value: 传入 aimStr 中截取的 第一个有效运算符
* - str: 截取了 第一个有效运算符(operator) 之后的字符串
********************************/
getFirstOperator(aimStr = "") {
aimStr = aimStr?.replace(/\s/g, "") || "";
let result = {
value: "", str: "", };
for (let i = 0; i < aimStr.length; i++) {
if (this.operator.includes(aimStr[i])) {
result.value = aimStr[i];
result.str = aimStr.slice(i + 1);
break;
}
}
return result;
}
clac(num1, num2, operator) {
num1 = Number(num1);
num2 = Number(num2);
if (operator === "+") {
return num1 + num2;
}
else if (operator === "-") {
return num1 - num2;
}
else if (operator === "*") {
return num1 * num2;
}
else if (operator === "/") {
return num1 / num2;
}
else return num1;
}
/************************************************
* 依据 this.paramsObj, 替换字符串中的参数(被引号包裹的字符串)
* @param {String} aimStr
*******************************************/
formatAimStr(aimStr = "") {
aimStr = aimStr?.replace(/\s/g, "");
let limitSymbol = `"`;
for (let i = 0; i < aimStr.length; i++) {
if (aimStr[i] === limitSymbol) {
for (let j = i + 1; j < aimStr.length; j++) {
if (aimStr[j] === limitSymbol) {
let param = aimStr.slice(i, j + 1);
let value = this.paramsObj[param.replace(/"/g, "")];
if (value === undefined) {
throw new Error(`字符串中的参数 “${
param}” 不存在于参数列表中!`)
} else {
aimStr = aimStr.replace(param, value)
// console.log(param, value);
break;
}
}
}
}
}
return aimStr;
}
/******************************************
* 规则 正确性验证
* @param {String} aimStr 目标规则
* @param {Array} paramArr 参数数组
******************************************/
valid(aimStr = "", paramArr = []) {
aimStr = aimStr?.replace(/\s/g, "") || "";
let tempChar = String.fromCharCode(0x10f0); // 替换 aimStr 中参数的特殊字符
let paramsReg = /".*?"/g;
let validCharReg = `[0-9\\+\\-\\*\\/\\(\\)\\.${
tempChar}]`;// 有效字符
validCharReg = new RegExp(validCharReg)
// 验证字符串的参数(两端携带引号的子字符串)是否在参数列表 paramArr 中
let aimStrParams = aimStr.match(paramsReg) || [];
for (let i = 0; i < aimStrParams.length; i++) {
const item = aimStrParams[i];
if (!paramArr.includes(item)) {
throw new Error(`参数 ${
item} 不存在于列表当中!`);
}
}
aimStr = aimStr.replace(/".*?"/g, tempChar);
// 验证左右括号的正确性
let leftBracketIndexs = []; // 左括号的脚标
for (let i = 0; i < aimStr.length; i++) {
if (aimStr[i] === "(") {
leftBracketIndexs.push(i);
}
else if (aimStr[i] === ")") {
let lastLBIS = leftBracketIndexs.pop();
if (lastLBIS === undefined) {
throw new Error(`括号数量不对称`);
}
let baseStr = aimStr.slice(lastLBIS + 1, i);
if (baseStr.length === 2) {
throw new Error(`出现()空括号错误`);
}
}
else if (!validCharReg.test(aimStr[i])) {
throw new Error(`“${
aimStr[i]}” 不是有效字符!如果是变量请用 {""} 英文双引号引用。`);
}
}
if (leftBracketIndexs.length !== 0) {
throw new Error(`括号数量不对称`);
}
return true
}
}
/ ************* ---------- test ------------ *************** /
let paramsObj = {
"参数1": 100,
"参数2": 10,
"参数3": 5,
}
let paramsArr = [];
for (const key in paramsObj) {
paramsArr.push("\"" + key + "\"");
}
let operator = ["+", "-", "*", "/", "(", ")"];
let aimStr = `((("参数1" + 1) * 2 + ("参数2" + 1) * 2) + 1) * 2`; // 450
let mc = new MyCalc(paramsObj, operator);
// 1. getFirstValue
// console.log(mc.getFirstValue("+999+666")); // value: '+999', str: '+666'
// console.log(mc.getFirstValue(`+"999"+666`)); // value: '+"999"', str: '+666'
// console.log(mc.getFirstValue(`+++"999"+666`)); // value: '+++"999"', str: '+666'
// console.log(mc.getFirstValue(`+++9+666`)); // value: '+++9', str: '+666'
// console.log(mc.getFirstValue(`9+666`)); // value: '9', str: '+666'
// console.log(mc.getFirstValue(`9`)); // value: '9', str: ''
// console.log(mc.getFirstValue(``)); // // value: "", str: ""
// 2. getFirstOperator
// console.log(mc.getFirstOperator("+999+666")); // value: '+', str: '999+666'
// console.log(mc.getFirstOperator("++999+666")); // value: '+', str: '+999+666'
// console.log(mc.getFirstOperator("896+999+666")); // value: '+', str: '999+666'
// console.log(mc.getFirstOperator("")); // value: "", str: ""
// 3. clacBaseStr
// console.log(mc.clacBaseStr("12+2+4+8")); // 26
// console.log(mc.clacBaseStr("12+2+4")); // 18
// console.log(mc.clacBaseStr("12+2")); // 14
// console.log(mc.clacBaseStr("12")); // 12
// console.log(mc.clacBaseStr("12+4-6-8")); // 2
// console.log(mc.clacBaseStr("5+5*5")); // 30
// console.log(mc.clacBaseStr("5*5")); // 25
// console.log(mc.clacBaseStr("5*5+5")); // 30
// console.log(mc.clacBaseStr("5*5+5*5")); // 50
// console.log(mc.clacBaseStr("5*5+5*5+5")); // 55
// console.log(mc.clacBaseStr("1*2*3*4*5")); // 120
// console.log(mc.clacBaseStr("1*2*3*4*5/4/3/2")); // 5
// 4. clacAimStr
// console.log(mc.clacAimStr(`((1+2)*2+(2+2)*3)+5`)); // 23
// console.log(mc.clacAimStr(``)); // 0
// console.log(mc.clacAimStr(`5`)); // 5
// console.log(mc.clacAimStr(`2+3*4/2`)); // 8
// console.log(mc.clacAimStr(`(2+3)*(4/2)`)); // 10
// console.log(mc.clacAimStr(`(((1+2*3)+(4+5*6)*2+3)/3)+2*5`)); // 36
// 5. formatAimStr
// console.log(mc.formatAimStr(`((("参数1" + 1) * 2 + ("参数2" + 1) * 2) + 1) * 2`)); // (((100+1)*2+(10+1)*2)+1)*2
// console.log(mc.formatAimStr(` 1 1`)); // 11
// 6. do (main)
// console.log(mc.do(`((("参数1" + 1) * 2 + ("参数2" + 1) * 2) + 1) * 2`)); // 450
// console.log(mc.do(`()`)); // 0
// console.log(mc.do(`"参数1"`)); // 100
// console.log(mc.do(`"参数1"*2`)); // 200
// console.log(mc.do(`100`)); // 100
// console.log(mc.do(`"参数1"*"参数2"`)); // 1000
// console.log(mc.do(`2*2*2+3`)); // 11
// console.log(mc.do(`2*2*2+3*2`)); // 14
// console.log(mc.do(`2+2+2+2*2*2+3*2*2+2+2`)); // 30
// console.log(mc.do(`100-10*1+10*100`)); // 1090
// 7. valid
// console.log(mc.valid(aimStr, [])); // 参数 "参数1" 不存在于列表当中!
// console.log(mc.valid(aimStr, paramsArr)); // true
// console.log(mc.valid(`"参数1" + 1 * 2 + "参数2" + 1`, paramsArr)); // true
// console.log(mc.valid(`("参)数1" + 1 * 2 + "参数2" + 1`, paramsArr)); // 参数 "参)数1" 不存在于列表当中!
// console.log(mc.valid(`出勤天数*10`, ['"出勤天数"', '"请假"', '"出勤金额"'])) // 出 不是有效字符!如果是变量请用 {""} 英文双引号引用。
// console.log(mc.valid(`"出勤天数"*10`, ['"出勤天数"', '"请假"', '"出勤金额"'])) // true
字符串加减乘除运算
- 调用
.do(params)
方法就可实现 - 详细使用看
6. do (main)
测试用例