前言
近期,由于业务需要,需要对字符串公式进行解析计算,包括加减乘除,幂,取最大最小值运算,同时还包括圆括号的优先级问题。
首先想到的解决思路是,使用栈,将运算符优先级进行排序,计算值按优先级依次入栈,低的在栈底,高的在栈顶。高优先级符号计算完毕后,得到的中间值再次入栈,用于下一个运算符号的计算。符号的优先级计算问题,转为入栈出栈操作。
为实现上述操作,需要将计算优先级混乱的字符串公式,消除括号,按优先级进行排序,后缀式完美贴合了上述操作。
后缀表达式又称逆波兰表达式,明显的特点是:逆波兰表达式中没有括号,计算时将操作符之前的第一个数作为右操作数,第二个数作为左操作数,进行计算,得到的值继续放入逆波兰表达式中。
加(+)、减(-)、乘(*)、除(/)、幂(^),属于二元运算,后缀式可满足,遇到操作符只需取栈顶两个元素进行运算即可。
而最值运算(MAX、MIN),属于多元运算,且要取最值得参数个数不确定,遇到最值运算符时,无法确定从栈顶取多少个元素。为解决上述问题,可以在最值运算符的参数后,再将一个记录参数元素个数的数值入栈。这样遇到最值运算符后,栈弹出的第一个元素标识后续接下来的n个元素都是要取最值的参数元素。
整体解决思路如上,接下来上代码,详情见注释。
接收字符串默认语法正确。关于校验的相关方法,笔者也写过一个拙码,后续分享。
不足可改进之处欢迎指出。
计算类
核心方法:
- calculate:计算方法,返回计算结果
- convertToPostfix:转换方法,将字串表达式转换为后缀式,并存入后缀式栈中
注:为区分最值运算符MAX、MIN的括号和优先级圆括号,字符串表达式中MAX、MIN的括号改为方括号 MAX[1,2,3 ],MIN[1,2,3]。
/**
* 表达式字符串计算工具类
*
* @since 2020/11/17 15:59
* @author MYeshion
*/
public class Calculator {
/** 后缀式栈*/
private Stack<String> postfixStack = new Stack<>();
/** 运算符栈*/
private Stack<String> operatorStack = new Stack<>();
/** 最大值最小值参数个数栈*/
private Stack<Integer> paramCountStack = new Stack<>();
/** 最大最小值前缀*/
private static final String MAX_MIN_PREFIX = "M";
/** 最低级运算符*/
private static final String LOWEST_OPERATOR = ",";
/** 最值多元运算符*/
private static final List<String> MULTIMODAL_OPERATOR = Arrays.asList("MIN", "MAX");
/** 取最大值*/
private static final String MAX = "MAX";
/** 取最小值*/
private static final String MIN = "MIN";
/** 右方括号*/
private static final String RIGHT_SQUARE_BRACKET = "]";
/** 右括号*/
private static final String RIGHT_BRACKET = ")";
/** 左括号*/
private static final String LEFT_BRACKET = "(";
/**
* 运算符优先级,越大优先级越高
*/
private static final Map<String, Integer> OPERATOR_PRECEDENCE = new HashMap<>();
static {
// 逗号默认最低优先级
OPERATOR_PRECEDENCE.put(",", -1);
OPERATOR_PRECEDENCE.put("(", 0);
OPERATOR_PRECEDENCE.put("[", 0);
OPERATOR_PRECEDENCE.put("MAX", 1);
OPERATOR_PRECEDENCE.put("MIN", 1);
OPERATOR_PRECEDENCE.put("+", 2);
OPERATOR_PRECEDENCE.put("-", 2);
OPERATOR_PRECEDENCE.put("*", 3);
OPERATOR_PRECEDENCE.put("/", 3);
OPERATOR_PRECEDENCE.put("^", 4);
OPERATOR_PRECEDENCE.put(")", 5);
OPERATOR_PRECEDENCE.put("]",