简单的四则运算解析器
问题描述
小F面临一个编程挑战:实现一个基本的计算器来计算简单的字符串表达式的值。该字符串表达式有效,并可能包含数字(0-9)、运算符+、-及括号()。注意,字符串中不包含空格。除法运算应只保留整数结果。请实现一个解析器计算这些表达式的值,且不使用任何内置的eval函数。
思路
在面对这个题时,我们的核心解题方向是围绕着经典的表达式求值思路展开,具体涉及到中缀表达式的处理,而中缀表达式求值,又与前缀、后缀表达式紧密相关,它们之间的转换及求值逻辑是解决本题的关键理论支撑。
首先,回顾一下前缀、中缀、后缀表达式的概念。中缀表达式就是我们日常习惯书写的数学表达式形式,运算符处于操作数中间,例如 “3 + 4 * 2”,这种形式符合人的直观思维,但对于计算机程序来说,直接计算中缀表达式存在一定复杂性,因为它需要遵循运算符优先级规则以及括号改变运算顺序的约定。后缀表达式则是运算符紧跟在操作数之后,比如上述中缀表达式对应的后缀表达式就是 “3 4 2 * +”,计算后缀表达式遵循 “从左到右扫描,遇到操作数入栈,遇到运算符就弹出栈顶两个操作数进行运算,再将结果入栈” 的规则,这种方式使得计算过程更加规则化、易于实现。前缀表达式类似,只是运算符在操作数之前,其求值思路也有一套既定且相对清晰的流程。
回到本题的中缀表达式求值,我们采用的是利用两个栈,一个数字栈(numStack)用来存放操作数,一个运算符栈(opStack)用来存放运算符,以此模拟计算过程。
当遍历输入的字符串表达式时,我们分情况处理遇到的字符:
-
数字字符:如果当前字符是数字(在‘0’ - ‘9’范围内),直接将其转换为对应的整数值(利用字符与数字 ASCII 值的差值关系,如
c - '0'),并压入数字栈。因为表达式中的数字都是单个数字字符表示的,这样能快速提取出数字放入栈中,作为后续运算的操作数储备。 -
括号字符:
- 当遇到左括号‘(’时,直接将其压入运算符栈,它标志着一个子表达式的开始,其主要作用是在后续遇到右括号时界定子表达式的范围,确保子表达式内的运算优先级和独立性。
- 一旦碰到右括号‘)’,这就触发了对子表达式的求值操作。此时,需要不断从运算符栈弹出运算符,并从数字栈弹出相应操作数进行计算,直到遇到栈顶为左括号‘(’为止,这一系列操作实际就是把括号内的子表达式按照运算符优先级规则进行求值,求出这个子表达式的值后,把对应的左括号从运算符栈弹出,至此完成一个子表达式的处理,结果保留在数字栈中,以便融入后续更大范围的运算。
-
运算符字符:
- 当遇到‘+’‘-’‘*’‘/’这些运算符时,情况较为复杂但遵循明确优先级规则。
- 对于‘+’和‘-’,它们优先级相对较低。在将它们入栈前,要检查运算符栈是否为空以及栈顶元素是否为‘(’,只要栈非空且栈顶不是‘(’,就意味着栈顶运算符优先级大于等于当前‘+’‘-’,需要先把栈顶运算符弹出,结合数字栈顶两个操作数进行运算(调用
cal函数实现四则运算逻辑),将结果放回数字栈,如此反复,直到栈顶运算符优先级低于当前运算符或者栈为空,之后再把当前‘+’‘-’压入运算符栈,确保后续运算顺序符合数学逻辑。 - 针对‘’和‘/’,它们优先级较高。同样,在入栈前检查运算符栈,只要栈非空且栈顶是‘ ’或者‘/’,就按上述类似流程弹出栈顶运算符和操作数进行运算、放回结果,直至栈顶不是‘’‘/’,然后将当前‘ ’‘/’入栈。这个过程保证了乘除运算在合适时机优先执行,契合数学四则运算优先级规范。
遍历完整个表达式字符串后,此时数字栈和运算符栈可能还残留部分元素,由于表达式已经处理完毕,剩下的运算符都是待执行运算的,所以按照前面弹出运算符计算的逻辑,不断循环,直至运算符栈为空,最终数字栈中剩下的唯一元素就是整个表达式的求值结果。
不过,这个过程在调试(Debug)时确实比较麻烦,一方面要时刻留意两个栈的状态变化,确保操作数和运算符进出栈顺序、时机准确无误;另一方面,括号匹配、运算符优先级处理等逻辑分支较多,很容易在边界情况或者复杂嵌套表达式处出现错误,需要耐心细致地通过单步调试、打印栈状态等手段去排查问题,保障代码能准确计算各类合法的简单四则运算字符串表达式的值。
实现
import java.util.Deque;
import java.util.LinkedList;
public class Main {
private static int cal(int a,char op, int b){
switch (op) {
case '+': return a+b;
case '-': return a-b;
case '/': return a/b;
case '*': return a*b;
}
return 0;
}
public static int solution(String expression) {
// Please write your code here
Deque<Integer> numStack = new LinkedList<>();
Deque<Character> opStack = new LinkedList<>();
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if(c >= '0' && c<= '9'){
numStack.push(c-'0');
}else{
if(c == '(')
opStack.push(c);
else if(c==')'){
while (opStack.peek()!='(') {
char op = opStack.pop();
int b = numStack.pop();
int a = numStack.pop();
numStack.push(cal(a, op, b));
}
opStack.pop();
}else {
// 弹出栈中优先级高于或等于当前运算符的所有运算符
switch (c) {
case '+':
case '-':
while (!opStack.isEmpty()&&opStack.peek()!='(') {
char op = opStack.pop();
int b = numStack.pop();
int a = numStack.pop();
numStack.push(cal(a, op, b));
}
break;
case '*':
case '/':
while (!opStack.isEmpty()&&(opStack.peek()=='*'|| opStack.peek()=='/')) {
char op = opStack.pop();
int b = numStack.pop();
int a = numStack.pop();
numStack.push(cal(a, op, b));
}
break;
}
// 将当前运算符入栈
opStack.push(c);
}
}
}
// 将运算符号依次弹出
while (!opStack.isEmpty()) {
char op = opStack.pop();
int b = numStack.pop();
int a = numStack.pop();
numStack.push(cal(a, op, b));
}
return numStack.pop();
}
public static void main(String[] args) {
// You can add more test cases here
//System.out.println(solution("3+4*5/(3+2)") == 7);
System.out.println(solution("(1+(4+5+2)-3)+(6+8)") == 23);
}
}