字符串运算式的计算

  最近一次在重构过程中,遇到一个功能,它实现对字符串表达式的计算,对类似 (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.

 

posted on 2016-05-29 14:11  来碗板面  阅读(1841)  评论(0编辑  收藏  举报

导航