在做URule规则引擎PRO版时,遇到表达式计算问题,就是用户随意输入一个包含+、-、*、/以及()的字符串表达式时,引擎要能根据表达式计算出结果,如果表达式中包含普通字符串,则以字符串连接方式处理。
一开始想到用apache的jexl实现,用了之后发现jexl性能实在太差,所以就自己写了一个,利用下面这个表达式计算类,可实现类似下面这些表达式的计算:
表达式
计算结果
2+20*3-10
52
2+20*3-10+superman
52superman
2+20*3-10+super man
52superman
2+20*3-10+"super man"
52super man
(2+20)*3-10+"super man"
56super man
2+20%3-10
-6
2+20/3-10
-1.3333333333
2+18/3-10
-2
2+18%3-10
-8
"super man:"+(30-20)
super man10
算法实现源码如下:
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Stack;
import com.bstek.urule.Utils;
/**
* @author Jacky.gao
* @since 2018年6月28日
*/
public class ElCompute {
private Stack dataStack=new Stack();
private Stack operateStack=new Stack();
public static void main(String[] args) {
long start=System.currentTimeMillis();
String expr="21%3+\"ga ojie \"+\" is \"+superman";
for(int i=0;i<1;i++) {
ElCompute el=new ElCompute();
Object data=el.doCompute(expr);
System.out.println(data);
}
long end=System.currentTimeMillis();
System.out.println(end-start);
}
public Object doCompute(String expr) {
init(expr);
return dataStack.pop();
}
private void init(String expr){
StringBuilder dataSb=new StringBuilder();
char prevQuote='0';
for(int i=0;i
char c=expr.charAt(i);
switch(c){
case '+':
addDataStack(dataSb);
doCalculate(0);
operateStack.push(c);
break;
case '-':
doMinus(dataSb, prevQuote);
break;
case '*':
addDataStack(dataSb);
doCalculate(2);
operateStack.push(c);
break;
case '/':
addDataStack(dataSb);
doCalculate(2);
operateStack.push(c);
break;
case '%':
addDataStack(dataSb);
doCalculate(2);
operateStack.push(c);
break;
case '(':
operateStack.push(c);
break;
case ')':
addDataStack(dataSb);
doCalculate(1);
break;
case '"':
if(prevQuote=='"'){
prevQuote='0';
dataStack.push(dataSb.toString());
dataSb.setLength(0);
}else{
prevQuote='"';
}
break;
case ' ':
if(prevQuote=='"') {
dataSb.append(c);
}
break;
default:
dataSb.append(c);
}
}
if(dataSb.length()>0){
addDataStack(dataSb);
}
doCalculate(0);
}
private void doMinus(StringBuilder dataSb,char prevQuote){
if(dataSb.length()==0){
dataSb.append('-');
}else{
addDataStack(dataSb);
doCalculate(0);
operateStack.push('-');
}
}
private void doCalculate(int current) {
if(operateStack.empty()){
return;
}
char prevOp=operateStack.peek();
if(prevOp=='('){
return;
}
if(current==0 || current==1){
char op=operateStack.pop();
do{
Object right=dataStack.pop();
Object left=dataStack.pop();
Object result=calculate(left, op, right);
dataStack.push(result);
if(operateStack.isEmpty()){
break;
}
op=operateStack.pop();
}while(op!='(');
}else if(current==2){
while(prevOp=='*' || prevOp=='/' || prevOp=='%') {
Object right=dataStack.pop();
Object left=dataStack.pop();
char op=operateStack.pop();
Object result=calculate(left, op, right);
dataStack.push(result);
if(operateStack.isEmpty()){
break;
}
prevOp=operateStack.peek();
if(prevOp=='('){
break;
}
}
}
}
private Object calculate(Object left,char op,Object right){
if((op=='*' || op=='/' || op=='%' || op=='-')){
if(right instanceof String || left instanceof String){
throw new RuntimeException(left + " and "+right+" can't do "+op+"!");
}
BigDecimal b1=(BigDecimal)left;
BigDecimal b2=(BigDecimal)right;
if(op=='*'){
return b1.multiply(b2);
}else if(op=='/'){
return b1.divide(b2,10,RoundingMode.HALF_UP).stripTrailingZeros();
}else if(op=='%'){
return b1.divideAndRemainder(b2)[1];
}else if(op=='-'){
return b1.subtract(b2);
}
}else if(op=='+'){
if(right instanceof String || left instanceof String){
return left.toString()+right.toString();
}else{
BigDecimal b1=(BigDecimal)left;
BigDecimal b2=(BigDecimal)right;
return b1.add(b2);
}
}
throw new RuntimeException("Unkown operate "+op+"");
}
private void addDataStack(StringBuilder dataSb) {
if(dataSb.length()==0)return;
String data=dataSb.toString();
dataSb.setLength(0);
try{
BigDecimal bd=Utils.toBigDecimal(data);
dataStack.push(bd);
}catch(Exception ex){
dataStack.push(data);
}
}
}
在上面的代码中Utils.toBigDecimal(data)的作用是尝试将当前数据转换为BigDecimal,Utils.toBigDecimal方法内容如下:
public static BigDecimal toBigDecimal(Object val) {
try{
if (val instanceof BigDecimal) {
return (BigDecimal) val;
} else if (val == null) {
throw new IllegalArgumentException("Null can not to BigDecimal.");
} else if (val instanceof String) {
String str = (String) val;
if ("".equals(str.trim())) {
return BigDecimal.valueOf(0);
}
str=str.trim();
return new BigDecimal(str);
} else if (val instanceof Number) {
return new BigDecimal(val.toString());
} else if (val instanceof Character) {
int i = ((Character) val).charValue();
return new BigDecimal(i);
}
}catch(Exception ex){
throw new NumberFormatException("Can not convert "+val+" to number.");
}
throw new IllegalArgumentException(val.getClass().getName()+" can not to BigDecimal.");
}
如果您的项目中要用到字符串表达式计算,那么可以直接用上述方法实现,目前这个算法已应用在URule PRO版以及UReport2的当中。