【工具】算数表达式解析

package com.servlet.demo.framework.utill;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

/**
 * <b>表达式(JPEL)</b></br>
 * 使用逆波兰算法,支持:
 * <ul>
 * <li><b>算数运算表达式:</b><br/>
 * 加(+)、减(-)、乘(*)、除(/)、求余(%)、幂(^)运算</li>
 * <li><b>关系表达式:</b><br/>
 * 等于(==)、不等于(!=)、大于(>)、大于等于(>=)、小于(<)、小于等于(<=)</li>
 * <li><b>逻辑表达式:</b><br/>
 * 且(&&)、或( || )、非( ! )、true、false</li>
 * <li><b>括号优先级表达式:</b><br/>
 * 使用“(表达式)”构造,括号里的具有高优先级。</li>
 * </ul>
 * 不支持三目运算,不支持文本字符串
 * @author Yuanqy
 */
public class ElEngine {
    private final static HashMap<String, Integer> priority = new HashMap<>();

    private static int MIN = 100;
    private static String EOD = "#";

    private static int getPriority(String key) {
        if (priority.get(key) == null) return MIN;
        return priority.get(key);
    }

    /**
     * 执行表达式
     * @param calc
     * @return
     */
    public static Object excute(String calc) {
        Stack<String> s = createReversePolish(calc);
        return calcReversePolish(s);
    }

    static {
        // 加入优先级映射,可继续扩展,级别按百科填写
        // priority.put("?:", 13);
        priority.put("||", 12);
        priority.put("&&", 11);

        priority.put("!=", 7);
        priority.put("==", 7);

        priority.put("<=", 6);
        priority.put(">=", 6);
        priority.put("<", 6);
        priority.put(">", 6);

        priority.put("+", 4);
        priority.put("-", 4);

        priority.put("*", 3);
        priority.put("/", 3);
        priority.put("%", 3);

        priority.put("!", 2);
        priority.put("^", 2);// 幂运算,js引擎是不支持这么写的

        priority.put("(", 1);
        priority.put(")", 1);
    }

    // == 解析 ========================================================
    public static Stack<String> createReversePolish(String source) {
        String[] str = editExpress(source);
        Stack<String> s1 = new Stack<String>();// 运算符
        Stack<String> s2 = new Stack<String>();// 逆波兰
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < str.length; i++) {
            String t = str[i];
            if (t.equals("")) continue;
            if (t.equals(EOD)) {// 结束符
                while (true) {
                    if (s1.size() == 0) break;
                    s2.push(s1.pop());
                }
                break;
            }
            sb.append(t);
            if (isNumber(t)) {
                // 若取出的字符是操作数,则分析出完整的运算数,该操作数直接送入S2栈
                if (!isNumber(str[i + 1])) RefreshTemp(s2, sb);
            } else {
                // 若取出的字符是“(”,则直接送入S1栈顶。
                // 若取出的字符是“)”,则将距离S1栈栈顶最近的“(”之间的运算符,逐个出栈,依次送入S2栈,此时抛弃“(”。
                if (getPriority(t) == 1) {
                    if (t.equals("(")) {
                        spush(s1, s2, sb.deleteCharAt(sb.length() - 1), sb.toString());
                        s1.push(t);
                    }
                    if (t.equals(")")) {
                        while (true) {
                            String s1t = s1.pop();
                            if (s1t.equals("(")) break;
                            s2.push(s1t);
                        }
                    }
                    sb.setLength(0);// ?
                } else if (isNumber(str[i + 1])) {
                    // 若取出的字符是运算符,则将该运算符与S1栈栈顶元素比较,如果该运算符优先级大于S1栈栈顶运算符优先级,则将该运算符进S1栈,
                    // 否则,将S1栈的栈顶运算符弹出,送入S2栈中,直至S1栈栈顶运算符低于(不包括等于)该运算符优先级,最后将该运算符送入S1栈。
                    spush(s1, s2, sb, t);
                }
            }
        }
        s1.clear();
        return s2;
    }

    private static String[] editExpress(String source) {
        source = "(" + source.replaceAll("\\s*", "") + ")" + EOD;// 去除空格
        source = source.replaceAll("true", "(1==1)");// 支持true/false
        source = source.replaceAll("false", "(0==1)");
        source = source.replaceAll("([^\\d|^)])([+|-])(\\d+)", "$1(0$2$3)");// 支持一元[+-]
        return source.split("");
    }

    private static void spush(Stack<String> s1, Stack<String> s2, StringBuilder sb, String t) {
        while (true) {
            if (s1.size() == 0 || getPriority(t) < getPriority(s1.peek()) || getPriority(s1.peek()) == 1) {
                RefreshTemp(s1, sb);
                break;
            } else {
                s2.push(s1.pop());
            }
        }
    }

    private static void RefreshTemp(Stack<String> rpStack, StringBuilder sb) {
        String str = sb.toString();
        if (str.length() > 0) {
            if ((isNumber(str) || getPriority(str) < MIN)) {
                rpStack.push(sb.toString());
                sb.setLength(0);
            } else {
                throw new ArithmeticException("错误的表达式:" + str);
            }
        }
    }

    private static boolean isNumber(String t) {
        char i = t.charAt(0);// 值判断一个字符
        return (i >= 48 && i <= 57) ? true : false;
    }

    // == 计算 ========================================================
    public static Object calcReversePolish(Stack<String> s2) {
        Stack<Object> no = new Stack<Object>();
        for (String s : s2) {
            if (isNumber(s)) {
                no.push(Double.parseDouble(s));
            } else {
                Object d2 = no.pop();
                Object d1 = 0.0;
                if (!no.isEmpty()) d1 = no.pop();
                switch (s) {
                    case "!":
                        no.push(!((boolean) d2));
                        break;
                    case "+":
                        no.push((double) d1 + (double) d2);
                        break;
                    case "-":
                        no.push((double) d1 - (double) d2);
                        break;
                    case "*":
                        no.push((double) d1 * (double) d2);
                        break;
                    case "/":
                        no.push((double) d1 / (double) d2);
                        break;
                    case "%":
                        no.push((double) d1 % (double) d2);
                        break;
                    case "==":
                        no.push((double) d1 == (double) d2 ? true : false);
                        break;
                    case "!=":
                        no.push((double) d1 != (double) d2 ? true : false);
                        break;
                    case ">=":
                        no.push(((double) d1 >= (double) d2 ? true : false));
                        break;
                    case "<=":
                        no.push(((double) d1 <= (double) d2 ? true : false));
                        break;
                    case ">":
                        no.push(((double) d1 > (double) d2 ? true : false));
                        break;
                    case "<":
                        no.push(((double) d1 < (double) d2 ? true : false));
                        break;
                    case "||":
                        no.push(((boolean) d1 || (boolean) d2 ? true : false));
                        break;
                    case "&&":
                        no.push(((boolean) d1 && (boolean) d2 ? true : false));
                        break;
                    case "^":
                        no.push(Math.pow((double) d1, (double) d2));
                        break;
                }
            }
        }
        return no.pop();
    }

    // == 附加 js 引擎
    /**
     * Must java version >=1.6
     */
    private static ScriptEngine se;

    public static ScriptEngine getSE() {
        if (se == null) se = new ScriptEngineManager().getEngineByName("js");//
        return se;
    }

    public static void main(String[] args) {
        try {
            int i = -10;
            List<String> calcs = new ArrayList<>();
            calcs.add("+11+-22-33 ");
            calcs.add("-(-(-1))+(-3)*(-2)*3/4%(-5)+" + i++);
            calcs.add("-1+2*(3+4)-5+(-2)*8/2+8+0 == 5*6/2-5 && 10%4*3>-1+2*3");
            calcs.add("!false");
            calcs.add("1!=-3-(-4)");
            calcs.add("2>=-2+3");
            calcs.add("1+1");
            calcs.add("true");
            for (String calc : calcs) {
                System.out.println("\n中缀表达式:" + calc);
                System.out.println("逆波兰表达式:" + createReversePolish(calc));
                // == 逆波兰 ========================
                long t1 = System.currentTimeMillis();
                System.out.println("逆波兰=" + excute(calc) + "\ttime:" + (System.currentTimeMillis() - t1));
                // // == JS引擎==========================
                long t3 = System.currentTimeMillis();
                System.out.println("JS引擎=" + getSE().eval(calc) + "\ttime:" + (System.currentTimeMillis() - t3));
                Thread.sleep(500);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

测试结果:

中缀表达式:+11+-22-33 
逆波兰表达式:[0, 11, +, 0, 22, -, +, 33, -]
逆波兰=-44.0	time:1
JS引擎=-44.0	time:122

中缀表达式:-(-(-1))+(-3)*(-2)*3/4%(-5)+-10
逆波兰表达式:[0, 1, -, -, -, 0, 3, -, 0, 2, -, *, 3, *, 4, /, 0, 5, -, %, +, 0, 10, -, +]
逆波兰=-6.5	time:1
JS引擎=-6.5	time:1

中缀表达式:-1+2*(3+4)-5+(-2)*8/2+8+0 == 5*6/2-5 && 10%4*3>-1+2*3
逆波兰表达式:[0, 1, -, 2, 3, 4, +, *, +, 5, -, 0, 2, -, 8, *, 2, /, +, 8, +, 0, +, 5, 6, *, 2, /, 5, -, ==, 10, 4, %, 3, *, 0, 1, -, 2, 3, *, +, >, &&]
逆波兰=false	time:1
JS引擎=false	time:3

中缀表达式:!false
逆波兰表达式:[0, 1, ==, !]
逆波兰=true	time:0
JS引擎=true	time:1

中缀表达式:1!=-3-(-4)
逆波兰表达式:[1, 0, 3, -, 0, 4, -, -, !=]
逆波兰=false	time:0
JS引擎=false	time:4

中缀表达式:2>=-2+3
逆波兰表达式:[2, 0, 2, -, 3, +, >=]
逆波兰=true	time:0
JS引擎=true	time:2

中缀表达式:1+1
逆波兰表达式:[1, 1, +]
逆波兰=2.0	time:0
JS引擎=2.0	time:1

中缀表达式:true
逆波兰表达式:[1, 1, ==]
逆波兰=true	time:0
JS引擎=true	time:4

 

转载于:https://my.oschina.net/jweb/blog/775630

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值