js 规则解析, 字符串中携带括号、加、减、乘、除运算,精度丢失的问题;字符串加减乘除运算

需求

最近弄薪资模块, 需要解析一个字符串运算规则解析(这东西叫啥名字我也不知道);

反正就是计算类似于这样一个字符串: ((("参数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) 测试用例
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用栈来实现计算字符串数字的乘除括号运算,不需要使用eval函数。具体实现步骤如下: 1. 定义一个栈,用来存储数字和运算符。 2. 遍历字符串,遇到数字则入栈,遇到运算符则判断栈顶元素是否为数字,如果是则将运算符入栈,否则弹出两个数字和一个运算符进行运算,将结果入栈。 3. 遇到左括号则直接入栈,遇到右括号则弹出栈顶元素直到遇到左括号为止,将弹出的元素进行运算,将结果入栈。 4. 遍历完成后,将栈剩余的元素进行运算,得到最终结果。 示例代码如下: ```python def calculate(s): stack = [] num = 0 sign = "+" for i in range(len(s)): if s[i].isdigit(): num = num * 10 + int(s[i]) if s[i] in "+-*/" or i == len(s) - 1: if sign == "+": stack.append(num) elif sign == "-": stack.append(-num) elif sign == "*": stack.append(stack.pop() * num) elif sign == "/": stack.append(int(stack.pop() / num)) num = 0 sign = s[i] if s[i] == "(": stack.append(sign) sign = "+" if s[i] == ")": if sign == "+": stack.append(num) elif sign == "-": stack.append(-num) num = 0 while isinstance(stack[-1], int): num += stack.pop() if stack[-1] == "+": stack.pop() stack.append(num) elif stack[-1] == "-": stack.pop() stack.append(-num) break return sum(stack) ``` 测试代码: ```python s = "(1+(2+3)*4)-5" print(calculate(s)) # 输出:16 s = "2*(1+3)" print(calculate(s)) # 输出:8 ``` 注意:此方法只适用于字符串只包含数字、乘除括号的情况,如果有其他字符或者不合法的运算式,程序会报错或者得到错误的结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值