四则运算表达式解析,分离分子和分母表达式(Java实现)
背景需求
最近项目中遇到这样一个问题。在做指标归因分析的过程中,需要将一个指标运算的逻辑拆解出来,也就是将指标的分子表达式和分母表达式解析出来。比如这样一个指标: (xinzhuangxiu_mendiandaofang+xinzhuangxiu_mendianchenhui)/xinzhuangxiu_qiandaozongliang,需要将分子分母分别提取出来返回。
最终采用了栈+逆波兰 的处理方式。
Step1:将运算表达式字符串分解为运算表达式List
Step2:将运算表达式List转换为逆波兰表达式List
Step3:逆波兰表达式运算
最终可以实现分子分母表达式的解析,并支持中文指标名
执行代码
package com.ke.bigdata.aas.util;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
/**
* 四则混合运算表达式解析
* @author WeiZhaolin
*/
public class ExpParserUtil {
/**
* 运算符枚举
*/
private enum Operator {
ADD("+", 10), SUBTRACT("-", 10), MULTIPLY("*", 20), DIVIDE("/", 20),
PARENTHESIS_LEFT("(", 100), PARENTHESIS_RIGHT(")", 100);
private String operator;
private int priority;
private Operator(String operator, int priority) {
this.operator = operator;
this.priority = priority;
}
}
/**
* 获取字符串所对应的运算符枚举
* @param str
* @return
*/
private static Operator getOperator(String str) {
for (Operator op : Operator.values()) {
if (str.equals(op.operator)) {
return op;
}
}
return null;
}
/**
* 第1步: 将运算表达式字符串分解为运算表达式List
*
* @param exp
* @return
*/
private static List<String> resolveExpr(String exp) {
List<String> list = new LinkedList<String>();
String temp = "";
exp = exp.replace(" ", "");
for (int i = 0; i < exp.length(); i++) {
String str = exp.substring(i, i + 1);
Operator op = getOperator(str);
// Operand od = getOperand(str);
if (op != null) {
if (!temp.isEmpty()) {
list.add(temp);
temp = "";
}
list.add(str);
} else if (str.matches("[^\\+^\\-^\\*^\\/^\\(^\\)]")) {
temp += str;
} else {
System.out.println("表达式[" + str + "]非法! ");
return null;
}
}
if (!temp.isEmpty()) {
list.add(temp);
}
return list;
}
/**
* 第2步: 将运算表达式List转换为逆波兰表达式List
* @param expList
* @return
*/
private static List<String> dealExpr(List<String> expList) {
if(expList == null) {
return null;
}
List<String> list = new LinkedList<>();
Stack<String> stack = new Stack<>();
for (String str : expList) {
Operator op = getOperator(str.substring(0, 1));
// Operand od = getOperand(str.substring(0, 1));
// System.out.println(str.matches("[^\\+^\\-^\\*^\\/^\\(^\\)]+"));
if (str.matches("[^\\+^\\-^\\*^\\/^\\(^\\)]+")) {
//操作数直接入队列
list.add(str);
} else if (op != null) {
if (Operator.PARENTHESIS_LEFT.equals(op)) {
//左括号入栈
stack.push(str);
list.add("(");
} else if (Operator.PARENTHESIS_RIGHT.equals(op)) {
//右括号: 循环将栈顶的运算符取出并存入队列,直到取出左括号
while (true) {
if (stack.empty()) {
System.out.println("缺少左括号! ");
return null;
} else if (Operator.PARENTHESIS_LEFT.operator.equals(stack.peek())) {
stack.pop();
list.add(")");
break;
} else {
list.add(stack.pop());
}
}
} else {
//非括号类运算符
if (!stack.empty()) {
Operator top_op = getOperator(stack.peek());
//当前运算符优先级大于栈顶运算符优先级,或者栈顶为左括号时,当前运算符直接入栈
if(op.priority > top_op.priority
|| Operator.PARENTHESIS_LEFT.equals(top_op)) {
stack.push(str);
}
//否则,将栈顶的运算符取出并存入队列,然后将自己入栈
else {
list.add(stack.pop());
stack.push(str);
}
} else {
stack.push(str);
}
}
}
}
while(!stack.empty()) {
String str = stack.peek();
if(Operator.PARENTHESIS_LEFT.operator.equals(str)) {
System.out.println("缺少右括号! ");
return null;
} else {
list.add(stack.pop());
}
}
return list;
}
/**
* 操作数运算
* @param x
* @param y
* @param op
* @return
*/
private static String operation(String x, String y, Operator op) {
switch (op) {
case ADD:
return x + "+" + y;
case SUBTRACT:
return x + "-" + y;
case MULTIPLY:
return x + "*" + y;
case DIVIDE:
return x + "/" + y;
case PARENTHESIS_RIGHT:
return x + y + ")";
default:
return null;
}
}
/**
* 第3步: 逆波兰表达式运算
* @param exp
* @return
*/
public static String[] parse(String exp) {
String[] res = new String[2];
List<String> expList = dealExpr(resolveExpr(exp));
if(expList == null) {
return null;
}
expList.remove(expList.size()-1);
Stack<String> stack = new Stack<String>();
for(String str : expList) {
Operator op = getOperator(str.substring(0, 1)); // 获取操作符枚举值
// Operand od = getOperand(str.substring(0, 1)); // 获取操作数枚举值
if(str.matches("[^\\+^\\-^\\*^\\/^\\(^\\)]+")) {
stack.push(str);
} else if (op != null) {
if (op == Operator.PARENTHESIS_LEFT)
stack.push(op.operator);
//目前仅针对二元运算符
else {
String x = "";
String y = "";
if (!stack.empty()) {
y = stack.pop();
}
if (!stack.empty()) {
x = stack.pop();
}
if (!x.isEmpty() && !y.isEmpty()) {
String result = operation(x, y, op);
if (result == null) {
return null;
}
stack.push(result);
} else {
return null;
}
}
}
}
String fm = stack.pop();
String fz = stack.pop();
if (fm.matches("[\\d]+")){
res[0] = fz+"/"+fm;
res[1] = null;
} else {
res[0] = fz;
res[1] = fm;
}
return res;
}
}
测试用例
package com.ke.bigdata.aas.util;
import org.junit.Test;
import static org.junit.Assert.*;
public class ExpParserUtilTest {
String exp0 = "((i1-i2)/i3)/(i4/i5+i5/i6)";
String exp1 = "index/2";
String exp2 = "((今日总量-今日成交量)/今日总量)/((昨日总量-昨日成交量)/昨日总量)";
@Test
public void parse() {
String[] res0 = ExpParserUtil.parse(exp0);
System.out.println("测试结果0: "+res0[0]+"|"+res0[1]);
String[] res1 = ExpParserUtil.parse(exp1);
System.out.println("测试结果1: "+res1[0]+"|"+res1[1]);
String[] res2 = ExpParserUtil.parse(exp2);
System.out.println("测试结果2: "+res2[0]+"|"+res2[1]);
}
}
测试结果
测试结果0: ((i1-i2)/i3)|(i4/i5+i5/i6)
测试结果1: index/2|null
测试结果2: ((今日总量-今日成交量)/今日总量)|((昨日总量-昨日成交量)/昨日总量)