计算公式 - 四则运算实现
要对公式进行运算,首先需要实现基础的四则运算(包含符号()+-*/),用于解析一个字符串的计算式子(如:'(6+2)/4-2')。算法思路如下:
第一步:中缀表达式转后缀表达式
中缀表达式是利于人理解的表达方式,而后缀表达式更方便计算机的运算。
<section>
(或中缀记法)是一个通用的算术或逻辑公式表示方法, 操作符是以中缀形式处于操作数的中间(例:3 + 4),中缀表达式是人们常用的算术表示方法。
与前缀表达式(例:+ 3 4)或后缀表达式(例:3 4 +)相比,中缀表达式不容易被计算机解析,但仍被许多程序语言使用,因为它符合人们的普遍用法。
与前缀或后缀记法不同的是,中缀记法中括号是必需的。计算过程中必须用括号将操作符和对应的操作数括起来,用于指示运算的次序。
例:
(1)8+4-6*2用后缀表达式表示为:8 4+6 2*-
(2)2*(3+5)+7/1-4用后缀表达式表示为:235+*71/+4-
</section>
所以首先将中缀表达式转换成后缀表达式。这个过程需要一个辅助处理的栈,从左到右扫描表达式里的每一个字符,执行以下操作:
- 为数字,直接添加到后缀表达式末尾
- 为 +-*/ 运算符,弹出所有优先级大于或者等于该运算符的辅助栈顶元素到后缀表达式中,然后将该运算符入栈
- 为左括号(,直接入栈
- 为右括号),弹出栈顶元素到后缀表达式中,直到匹配到第一个左括号,括号不输出到后缀表达式
遍历完成后,将辅助栈中的元素输出,则得到后缀表达式。
说明:操作符优先级为: () < +- < */
以A*(B-C)/D为例,处理过程如下:
function infixToPostfix(infix, postfix) {
let postfixHelper = Stack();
for (let i = 0; i < infix.length; i++) {
let c = infix[i]
if (!isOper(c)) { // 处理操作数
let operands = ""
while (i < infix.length && !isOper(infix[i])) {
operands += infix[i++]
}
postfix.push(operands)
i--;
} else { // 处理操作符做出处理
handlerSymbol(c, postfixHelper, postfix)
}
}
console.log(postfixHelper)
// 如果输入结束,将栈内元素全部弹出,加入后缀表达式中
while (!postfixHelper.empty()) {
let c = postfixHelper.top()
postfix.push(c)
postfixHelper.pop()
}
}
第二步:计算后缀表达式
计算后缀表达式,同样需要一个辅助处理的栈,从左到右遍历字符,遇到操作数压入辅助栈中;遇到操作符则从栈顶取出两个元素,第一个为右运算数,第二个为左运算数(注意这个顺序在除法中是结果相关的)。运算后的结果入栈。遍历完成后,辅助栈顶的元素则为所求。
// 计算后缀表达式
function calcPostFix(postfix, data) {
let helperStack = Stack()
for (let i = 0; i < postfix.length; i++) {
let c = postfix[i]
// 如果是操作数,压入栈中
if (!isOper(c)) {
let op = Number(c); // 数值
helperStack.push(op)
} else {
// 如果是操作符,从栈中弹出元素进行计算
let op1 = helperStack.top()
helperStack.pop()
let op2 = helperStack.top()
helperStack.pop()
if (op1 === null || op2 === null) {
helperStack.push(null)
} else {
switch (c) {
case "+":
helperStack.push(op2 + op1)
break
case "-":
helperStack.push(op2 - op1)
break
case "*":
helperStack.push(op2 * op1)
break
case "/":
helperStack.push(op2 / op1) // 注意是op2(op)op1而不是op1(op)op2
break
}
}
}
}
return helperStack.top()
}