四则运算表达式解析,分离分子和分母表达式(Java实现)

四则运算表达式解析,分离分子和分母表达式(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: ((今日总量-今日成交量)/今日总量)|((昨日总量-昨日成交量)/昨日总量)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值