zoukankan      html  css  js  c++  java
  • 字符串运算式的计算

      最近一次在重构过程中,遇到一个功能,它实现对字符串表达式的计算,对类似 (a+b)*c 这种表达式进行实时计算,老的方式采用分割字符串的方式来实现,经常出错,我改写了一下。采用了几种方式。在此记录下来。

    1.正则分组

    使用正则的组匹配类似于 ((.+))(.+) 匹配之后结果为

    根据正则组的顺序来计算。

    但是对于这种也是有很多缺点的,一旦表达式复杂起来,正则将变得异常复杂和难以调试。而且,这种方式也必须事先规定字符串的格式。

    2.后缀表达式

    我们平时写的这种 (a+b)*c 表达式叫做中缀表达式,与之相对的有前缀表达式和后缀表达式,后缀表达式也叫逆波兰式。

    它的原理是将数字计算符后移,并将括号中的运算符至于前面,即不使用括号来保证运算顺序的一种方式,这种方式很适合计算机运算。

    例如,将中缀表达式“1+((2+3)×4)-5”转换为后缀表达式的过程如下:

    扫描到的元素 S2(栈底->栈顶) S1 (栈底->栈顶) 说明
    1 1 数字,直接入栈
    + 1 + S1为空,运算符直接入栈
    ( 1 + ( 左括号,直接入栈
    ( 1 + ( ( 同上
    2 1 2 + ( ( 数字
    + 1 2 + ( ( + S1栈顶为左括号,运算符直接入栈
    3 1 2 3 + ( ( + 数字
    ) 1 2 3 + + ( 右括号,弹出运算符直至遇到左括号
    × 1 2 3 + + ( × S1栈顶为左括号,运算符直接入栈
    4 1 2 3 + 4 + ( × 数字
    ) 1 2 3 + 4 × + 右括号,弹出运算符直至遇到左括号
    - 1 2 3 + 4 × + - -与+优先级相同,因此弹出+,再压入-
    5 1 2 3 + 4 × + 5 - 数字
    到达最右端 1 2 3 + 4 × + 5 - S1中剩余的运算符


    因此结果为“1 2 3 + 4 × + 5 -”(注意需要逆序输出)。

    详细原理参考:http://blog.csdn.net/antineutrino/article/details/6763722/

    在对比一系列实现之后,这里我使用栈来实现这种方式。

    package suffixformular;
    
    import java.util.Collections;
    import java.util.Stack;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.regex.Pattern;
    
    import javax.script.ScriptException;
    
    /**
     * 后缀表达式原理
     */
    public class SuffixFormula {
    
        private static SuffixFormula instance = new SuffixFormula();
    
        private SuffixFormula() {
        }
    
        public static SuffixFormula getInstnace() {
            return instance;
        }
    
        private Stack transformToSuffix(String infix) {
            InToPost inToPost = new InToPost(infix);
            return inToPost.doTrans();
        }
    
        public void c(Stack<Double> sk, String ch) {
            switch (ch) {
            case "+":
                try {
                    sk.push(sk.pop() + sk.pop());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
            case "-":
                double tmp = sk.pop();
                sk.push(sk.pop() - tmp);
                break;
            case "*":
                sk.push(sk.pop() * sk.pop());
                break;
            case "/":
                double temp = sk.pop();
                sk.push(sk.pop() / temp);
                break;
            case "^":
                double tp = sk.pop();
                sk.push(Math.pow(sk.pop(), tp));
                break;
            case "%":
                double mp = sk.pop();
                sk.push(sk.pop() % mp);
                break;
    
            default:
                throw new RuntimeException();
            }
        }
    
        // 完整版
        public double formula(String infix) {
            Stack result = new Stack<Double>();
            Stack suffix = transformToSuffix(infix);
            Collections.reverse(suffix);
            Pattern pattern = Pattern.compile("[0-9]*");
            //
            while (!suffix.isEmpty()) {
                String str = String.valueOf(suffix.pop());
                if (pattern.matcher(str).matches()) {
                    double dou = Double.parseDouble(str);
                    result.push(dou);
                } else {
                    c(result, str);
                }
            }
            return (double) result.pop();
    
        }
    
        public static void main(String[] args)  {
            String abc = "2^((4+2)/3)"; //"1+((2+3)*4)-5";//
    
            double value = // 0;
            SuffixFormula.getInstnace().formula(abc);
    
            System.err.println(value);
        }
    
    }
    
    class InToPost {
        private Stack stack;
        private String input;// 输入中缀表达式
        private Stack output;// 输出的后缀表达式
    
        public InToPost(String input) {
            this.input = input;
            int size = input.length();
            stack = new Stack<Object>();
            output = new Stack<Object>();
        }
    
        public Stack doTrans() {// 转换为后缀表达式方法
            boolean ischar = false;
            for (int i = 0; i < input.length(); i++) {
                char ch = input.charAt(i);
                if (ch == ' ') {
                    continue;
                } 
                //负号部分,未测试
    //            else if (ch == '-' && (output.size()<=0 || ischar)) {
    //                output.push(ch);
    //
    //                ischar = false;
    //                continue;
    //            }
                switch (ch) {
                case '+':
                case '-':
                    getOper(ch, 1);
                    ischar = true;
                    break;
                case '*':
                case '/':
                case '%':
                    getOper(ch, 2);
                    ischar = true;
                    break;
                case '^':
                    getOper(ch, 3);
                    ischar = true;
                    break;
                case '(':
                    stack.push(ch);
                    ischar = true;
                    break;
                case ')':
                    getParent(ch);
                    ischar = true;
                    break;
                default:
                    output.push(ch);
    
                    ischar = false;
                    break;
                }
            }
            while (!stack.isEmpty()) {
                output.push(stack.pop());
            }
    
            return output;
        }
    
        public void getParent(char ch) {
            while (!stack.isEmpty()) {
                char chx = (char) stack.pop();
                if (chx == '(') {
                    break;
                } else {
                    output.push(chx);
                }
            }
        }
    
        public void getOper(char ch, int prec1) {
            while (!stack.isEmpty()) {// 判断栈是否为空
                char operTop = (char) stack.pop();
                if (operTop == '(') {
                    stack.push(operTop);
                    break;
                } else {
                    int prec2 = 0;
                    if (operTop == '+' || operTop == '-') {
                        prec2 = 1;
                    } else if (operTop == '*' || operTop == '/') {
                        prec2 = 2;
                    } else if (operTop == '^') {
                        prec2 = 3;
                    }
                    if (prec2 < prec1) {
                        stack.push(operTop);
                        break;
                    } else {
                        output.push(operTop);
                    }
                }
            }
            stack.push(ch);
        }
    }

     这段代码没有优化

    稍微与实际有所不同的是我贴出的实现只能计算单个数字,比如 (2+3)*6。如果需要计算更高位的数字,可以先使用 (a+b)*c ,待后缀表达式转换完成,在①处用实际数字来替换a,b,c占位符即可。

    实际上关于后缀表达式的实现还有一种更简单的方式,例如:

    a+b*c-(d+e)

    第一步:按照运算符的优先级对所有的运算单位加括号~
            式子变成拉:((a+(b*c))-(d+e))
    第二步:转换前缀与后缀表达式
            前缀:把运算符号移动到对应的括号前面
                  则变成拉:-( +(a *(bc)) +(de))
                  把括号去掉:-+a*bc+de  前缀式子出现
            后缀:把运算符号移动到对应的括号后面
                  则变成拉:((a(bc)* )- (de)+ )-
                  把括号去掉:abc*-de+-  后缀式子出现

    这里只做参考,没有实现。具体参见:http://blog.sina.com.cn/s/blog_4e0c21cc01010x38.html

    3.使用JDK自带的脚本解释器。

          static ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript");
    
        public static void main(String[] args) throws ScriptException {
            String abc = "(1+7)%3";//"2^((4+2)/3)";
        
    //        double value =  // 0;
    //                SuffixFormula.getInstnace().formula(abc);
            
            Object value2=jse.eval(abc);
        
            System.err.println("eval:"+value2);
        }

    需要注意的是java实现的javascript脚本解释器,只支持javascript的运算模式,像^是不支持的必须使2^2这种个就会出错,需要用Math.pow()这种javascript的函数。另外这种脚本解释器只有在JDK6以上才有,低版本是没有的。

    对于简单的我推荐使用方法3.一般使用方法2.

  • 相关阅读:
    原代码,反码,解释和具体的补充 Java在&gt;&gt;和&gt;&gt;&gt;差异
    开源 自由 java CMS
    Socket方法LAN多线程文件传输
    《》猿从程序书评项目经理-猿自办节目
    今年,我开始在路上
    mysql 拒绝访问的解决办法
    Mysql连接错误:Mysql Host is blocked because of many connection errors
    基于jquery的从一个页面跳转到另一个页面的指定位置的实现代码
    【转】URL编码(encodeURIComponent和decodeURIComponent)
    oracle sql日期比较
  • 原文地址:https://www.cnblogs.com/wanglao/p/5539250.html
Copyright © 2011-2022 走看看