逆波蘭式實現四則運算(加減乘除)

最近做一個項目需要做表達式的解析,初想不難,仔細研究之後,發現做細點可能會涉及到編譯原理的詞法解析和語法解析。但是如果只做簡單的表達式計算,可以使用逆波蘭式。
何爲逆波蘭式,可以看這個鏈接:https://baike.baidu.com/item/%E9%80%86%E6%B3%A2%E5%85%B0%E5%BC%8F/128437?fr=aladdin
簡單研究了一下,下面是實現的簡單四則運算:

public class RPNUtils {
    private static final Logger logger = LoggerFactory.getLogger(RPNUtils.class);
    /**
     * 運算符優先級map(數字越大,優先級越高)
     */
    static Map<String,Integer> priorityMap = new HashMap<>(4);
    static{
        priorityMap.put("+",1);
        priorityMap.put("-",1);
        priorityMap.put("*",2);
        priorityMap.put("/",2);
        priorityMap.put("(",0);
    }

    /**
     * 計算表達式
     * @param exp(中綴表達式)
     * @return 計算結果
     */
    public static Double calExp(String exp){
        try{
            logger.info("傳入計算表達式:{}", exp);
            List<String> rpnList = transToRPN(exp);
            logger.info("逆波蘭式爲:{}",JSON.toJSONString(rpnList));
            Double result = cal(rpnList);
            logger.info("計算結果爲:{}",result);
            return result;
        }catch (Exception e){
            e.printStackTrace();
            logger.error("表達式計算異常:"+exp,e);
        }
        return 0.0;
    }

    /**
     * 轉化成逆波蘭式
     * @param exp
     * @return
     */
    public static List<String> transToRPN(String exp){
        //操作數棧
        Stack<String> numStack = new Stack<>();
        //運算符棧
        Stack<String> operStack = new Stack<>();
        //轉化成字符數組
        char[] expArray = exp.toCharArray();
        StringBuffer sb = new StringBuffer();
        List<String> numOperList = new ArrayList<>();
        //分離運算符和操作數(在運算符和操作數之間添加空格符)
        for (int i = 0; i < expArray.length; i++){
            if (Character.isDigit(expArray[i]) || ".".equals(String.valueOf(expArray[i]))){
                sb.append(expArray[i]);
            }else{
                sb.append(" ").append(expArray[i]).append(" ");
            }
        }
        //得到操作數和運算符的數組
        String[] array = sb.toString().trim().split(" ");
        //轉化成list
        numOperList = Arrays.asList(array);
        for (int i = 0; i < numOperList.size(); i++) {
            String a = numOperList.get(i);
            //過濾空字符串
            if (a.equals("")){
                continue;
            }
            if (Character.isDigit(a.charAt(0))){
                //如果是操作數直接放入操作數棧
                numStack.push(a);
            }else{
                if (operStack.isEmpty()){
                    //如果是運算符,且運算符棧是空的,直接將運算符放入運算符棧
                    operStack.push(a);
                    continue;
                }
                if (a.equals("(")){
                    //如果是左括號,直接放入運算符棧
                    operStack.push(a);
                    continue;
                }
                if (a.equals(")")){
                    //如果是右括號,則運算符棧依次出棧,並放入操作數棧,直到出棧運算符爲左括號,並捨棄左括號
                    while (!operStack.peek().toString().equals("(")){
                        numStack.push(operStack.pop());
                    }
                    operStack.pop();
                    continue;
                }
                //比較當前運算符和運算符棧頂的運算符
                //如果棧頂運算符爲左括號,則直接放入運算符棧
                String topOper = operStack.peek();
                if (topOper.equals("(")){
                    operStack.push(a);
                    continue;
                }
                //比較當前運算符的優先級和運算符棧頂運算符的優先級,若大於(等於)棧頂運算符優先級,則直接放入運算符棧
                if (priorityMap.get(a.toString()) > priorityMap.get(topOper.toString())){
                    operStack.push(a);
                }else{
                    // 否則,運算符棧頂運算符出棧並放入操作數棧,直到運算符棧頂操作符優先級低於(不包含等於)該運算符優先級
                    do{
                        numStack.push(operStack.pop());
                    }while(!operStack.isEmpty() && priorityMap.get(operStack.peek()) >= priorityMap.get(a));
                    //最後當前運算符放入運算符棧
                    operStack.push(a);
                }

            }
        }
        //如果上面步驟走完了,運算符棧中還有運算符,則依次出棧,放入到操作數棧
        while(!operStack.isEmpty()){
            numStack.push(operStack.pop());
        }
        return new ArrayList<>(numStack);
    }

    /**
     * 根據逆波蘭式計算表達式結果
     * @param list
     * @return
     */
    public static Double cal(List<String> list){
        System.out.println(JSON.toJSONString(list));
        //計算棧
        Stack<String> s = new Stack<>();
        for (int i = 0; i < list.size(); i++) {
            String a = list.get(i);
            if (Character.isDigit(a.charAt(0))){
                //如果是數字,則直接入棧
                s.push(a);
            }else{
                //如果是操作符,則計算棧棧頂兩個元素依次出棧,進行計算,然後將計算結果入棧
                if (a.equals("+")){
                    Double result = Double.valueOf(s.pop()) + Double.valueOf(s.pop());
                    s.push(String.valueOf(result));
                }
                if (a.equals("-")){
                    Double s1 = Double.valueOf(s.pop());
                    Double s2 = Double.valueOf(s.pop());
                    s.push(String.valueOf(s2 - s1));
                }
                if (a.equals("*")){
                    Double s1 = Double.valueOf(s.pop());
                    Double s2 = Double.valueOf(s.pop());
                    s.push(String.valueOf(s1*s2));
                }
                if (a.equals("/")){
                    Double s1 = Double.valueOf(s.pop());
                    Double s2 = Double.valueOf(s.pop());
                    s.push(String.valueOf(s2 / s1));
                }
            }
        }
        //計算完成,棧中元素便是計算結果
        return Double.valueOf(s.pop());
    }
    public static void main(String[] args) {
        //String str = "(50-50*50/50)*5.1/5.1+50*20+15.5*(20-10)/5.0-(2-1)*(5/1.0)-2+(2*2)/ 4.0";
        //String str = "10-1*5-2";
        String str = "(10-3-2-3-1)*(20-10-5)*(3-2-2+2+4)";
        System.out.println(calExp(str));
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章