乐趣区

关于javascript:js实现数学表达式计算并兼容中文数字计算

介绍

要实现数学表达式计算器,波及到两个办法,波兰表示法 (维基百科) 和逆波兰表示法(维基百科),具体的概念能够自行查看百科。

波兰表达式是将操作符前置,也就是前缀表达式;逆波兰表达式是将操作符后置,即后缀表达式。而咱们失常去输出的叫中断表达式,要是间接去对中断表达式进行计算,那么优先级的判断就不是很不便,而前缀或者后缀就很不便去写计算逻辑。

成果展现

  • 在线 Demo
  • 代码地址

实现思路

转换表达式

所以首要任务是将输出的数学表达式进行转换,在这里我抉择的是将中断转换成后缀表达式来计算,当然用前缀也一样。

咱们须要申明两个栈,一个用来存符号,一个用来存输入的后缀表达式。

const stack: string[] = [];
const output: string[] = [];

先解决输出的表达式字符串,将其宰割为数字和符号两种,都存在同一个数组中。这里我将 -(减号) 替换成 + 号解决,不便前面做计算。

split 反对应用正则表达式去宰割字符串,这里就是用操作符去宰割字符串,须要留神的是,得加上将正则匹配项加上(),split 才会收集用来宰割的操作符。

const reg = new RegExp(/(\+|\*|x|\/|\(|\))/g);
const list = value.replace(/\-/g, "+-").split(reg);
  • 符号优先级

用一个对象去记录符号的优先级,不便转换的时候判断是符号还是数字,以及入栈的程序。

优先级 ) < 加减 < 乘除 < (

这里多加了 x 是因为我想用 x 示意乘法,打字时少按一个键

export const Single = {
  "+": 1,
  "-": 1,
  "*": 2,
  x: 2,
  "/": 2,
  "(": 3,
  ")": -1,
};
  • 外围转换逻辑

便当咱们解决好的 list,我没有提前对空格做解决,因为我不想先用 filter 便当一遍,所以在转换得时候得去判断一下,如果 str 为空,跳过此次循环;

str 有两种状况,数字或者是操作符:

数字很简略,间接 pushoutput (最终的表达式)中;

如果是操作符,就要分状况解决了。

  • 当栈顶没有元素时,间接 push 到 stack 操作符栈中
  • 如果以后操作符的优先级大于栈顶元素,或者以后是左括号操作符,间接推到符号栈,因为我用 pop 获取的栈顶元素,因而还得把栈顶元素 push 回去
  • 如果以后操作符的优先级小于栈顶元素,则把栈顶元素 push 到表达式栈中,而后持续获取栈顶元素

    • 当栈顶元素是 ( 并且以后操作符不是 ),须要把 ( push 回去,保障括号内表达式的优先级
    • 当遇到 ) 时,( 就被 pop 掉了

这里没有间接去判断 str 是否等于 (),因为咱们提前做了优先级的设定的

( 的优先级最高,会被间接推入栈中;) 的优先级最低,遇到时会把左括号后的操作符解决掉,并且做了 index>0 的限度,右括号不会入栈。

for (let i = 0; i < list.length; i++) {const str = list[i];
  if (!str) continue;
  const index = Single[str];
  if (!!index) {let topValue = stack.pop() || "";
    let topIndex = Single[topValue];
    if (topIndex === undefined) {stack.push(str);
      continue;
    }

    if (index > topIndex || topIndex === 3) {stack.push(topValue);
      stack.push(str);
      continue;
    } else {while (topIndex && index <= topIndex) {if (topIndex < 3) output.push(topValue);
        topValue = stack.pop() || "";
        topIndex = Single[topValue];
        // 解决括号
        // topIndex === 3 标识 符号 栈顶 为 (// index > 0 示意 以后检索 符号 不为)
        // 因为符号 不为 ) 时,pop 掉的 ( 须要加回去
        if (topIndex === 3 && index > 0) {stack.push(topValue);
          break;
        }
      }
      if (index > 0) stack.push(str);
    }
  } else if (/\d/.test(str)) {output.push(str);
  }
}

计算表达式

其实次要麻烦就在转换表达式,转换实现后就很简略了。后缀表达式的计算逻辑就是遇到操作出栈两个元素,进行运算。

  • 申明计算方法
  • 申明计算方法对应的操作符
const counterFn = {add: (a: number, b: number) => a + b,
  minus: (a: number, b: number) => a - b,
  multiply: (a: number, b: number) => a * b,
  divide: (a: number, b: number) => a / b,
};
const counterMap = {
  "+": counterFn.add,
  "-": counterFn.minus,
  "*": counterFn.multiply,
  x: counterFn.multiply,
  "/": counterFn.divide,
};

遍历后缀表达式,数字之间入栈

遇到操作符,获取对应的计算方法,出栈两个元素,进行计算

export function counter(list: string[]) {const stack: number[] = [];
  let result = 0;

  for (let i = 0; i < list.length; i++) {const value = list[i];
    if (/\d/.test(value)) {stack.push(Number(value));
    } else {const a = stack.pop() || 0;
      const b = stack.pop() || 0;
      const res = counterMap[value](b, a);
      stack.push(res);
      result = res;
    }
  }
  return result;
}

扩大

再顺便加一点好玩的货色,输出的是中文数字的话能计算吗

OK,来做一下中文数字的兼容,去写中文数字转换阿拉伯数字太麻烦也有点简单,我间接找了一个第三库 cnwhy/nzh

其实应用库就变得很简略了,因为外围逻辑咱们曾经写好了,只须要在解决字符串的时候兼容中文数字就能够了。

给咱们的正则匹配项加上中文操作符,就能够依据 加 | 减 | 乘 | 除 | 左括号 | 右括号 来进行宰割。还有一个问题,那数字呢,数字还是中文啊,不能进行计算;当然也能够在这里遍历宰割的数组,用 map 把去解决中文数字,然而我不想在这里多一次数组的循环,去转换逻辑外面做。

const reg = new RegExp(/(\+|\*|x|\/|\(|\)|\^| 加 | 减 | 乘 | 除 | 左括号 | 右括号)/g);

只须要在转换的判断逻辑最初加上一个 else 调用 nzh 提供的 Nzh.cn.decodeS 办法转换一下即可。因为除了操作符和数字,就是中文数字了。

import Nzh from "nzh";

if (/\d/.test(str)) {output.push(str);
} else {output.push(Nzh.cn.decodeS(str));
}

仔细的小伙伴应该发现了,还有一个问题,转换了数字,操作符还是中文呢。中文操作符咱们不在这里解决,只须要再计算中给 counterMap 多加几个映射就能够了。

const counterMap: any = {
  "+": counterFn.add,
  "-": counterFn.minus,
  "*": counterFn.multiply,
  x: counterFn.multiply,
  "/": counterFn.divide,
  加: counterFn.add,
  减: counterFn.minus,
  乘: counterFn.multiply,
  除: counterFn.divide,
};

OK,功败垂成,一个数学表达式的计算工具就实现了,并且反对中文数字计算。当然,还有一个问题,没有对输出的字符串做错误处理以及提醒,明天临时不弄了,改天再弄吧。

退出移动版