本篇来用java编写一个计算器,来计算一个算数表达式,这个表达式支持加减乘除和任意层次的括号嵌套(仅支持圆括号)。

思路设计

首先讨论一下思路。假设有一个算数表达式为(2 + 1) + 200 * 3.2 / (5 * (2 + 6 )),我们首先需要按照优先级先计算最内层括号内的表达式,然后再计算外层括号的,直到演化为一个不含括号的普通加减乘除表达式,最终得出一个结果。

代码设计:
子过程:computeFlatExp(String flatExp)函数用来计算一个不含括号的加减乘除表达式,它先轮训计算乘和除,再轮训计算加和减,直到得出最终结果。

主流程:,不断地发现闭合的括号,然后调用computeFlatExp()函数计算字表达式的结果,从而简化算数表达式,直到简化为算数表达式不包含括号位置,最后在调用一次computeFlatExp()函数得出最终结果

代码及分析

下面来看下具体代码的实现。先来看下computeFlatExp()

computeFlatExp()子过程

private double computeFlatExp(String flatExp) {
    // 先递归计算所有乘法和除法
    Pattern pattern = Pattern.compile("(?<num1>[\\d\\.]+)\\s*(?<operator>[\\*/])\\s*?(?<num2>[\\d\\.]+)");
    Matcher matcher = pattern.matcher(flatExp);
    while (matcher.find()) {
        double num1 = Double.parseDouble(matcher.group("num1"));
        double num2 = Double.parseDouble(matcher.group("num2"));
        String operator = matcher.group("operator");
        double result = operator.contains("*") ? num1 * num2 : num1 / num2;
        flatExp = flatExp.replace(matcher.group(0), String.valueOf(result));
        matcher = pattern.matcher(flatExp);
    }

    // 再递归计算所有加法和减法
    pattern = Pattern.compile("(?<num1>[\\d\\.]+)\\s*(?<operator>[\\+\\-])\\s*?(?<num2>[\\d\\.]+)");
    matcher = pattern.matcher(flatExp);
    while (matcher.find()) {
        Double num1 = Double.parseDouble(matcher.group("num1"));
        Double num2 = Double.parseDouble(matcher.group("num2"));
        String operator = matcher.group("operator");
        double result = operator.contains("+") ? num1 + num2 : num1 - num2;
        flatExp = flatExp.replace(matcher.group(0), String.valueOf(result));
        matcher = pattern.matcher(flatExp);
    }

    return Double.parseDouble(flatExp.trim());
}

上面的代码思路比较简单,主要的手段就是依赖正则表达式,我在上一篇中讨论了java中使用正则表达式的方式,这次要好好用一下了。它从左至右依次抓取并计算所有的例如1.2 * 3.4或者5.6 / 7.8之类的乘法和除法的二元表达式,然后再从左至右抓取并计算所有的加法表达式或减法二元表达式,直至剩下最后一个数字为止,最后将这一个表达式变化为数字,并将它转换为Double的对象返回。

主流程

再来看一下主流程的代码

public double compute(String expression) {
    int start = -1;
    for (int i = 0; i < expression.length(); i++) {
        if (expression.charAt(i) == '(') {
            start = i;
        }
        if (expression.charAt(i) == ')') {
            // 递归来简化问题
            String flatExp = expression.substring(start + 1, i);
            double result = computeFlatExp(flatExp);
            StringBuilder sb = new StringBuilder();
            if (start > 0) {
                sb.append(expression.substring(0, start));
            }
            sb.append(String.valueOf(result));
            if (i < expression.length() - 1) {
                sb.append(expression.substring(i + 1));
            }
            return compute(sb.toString());
        }
    }

    // 计算加减乘除
    return computeFlatExp(expression);
}

上面的代码先记录下来最右侧的左括号,一旦遇到右括号,则意味着找到了一组匹配的括号了,用这种方式来处理括号的嵌套比较有用。找到一组匹配的括号后,就调用computeFlatExp()子过程来计算匹配到的字表达式,并将计算结果替换到源表达式中,从而简化了整个源表达式。不断重复这个过程,最终将所有括号中的子表达式全部算出来,从而消除了所有的括号。最后再调用一次computeFlatExp()子过程,从而得出最终结果。

调用方式

再来看一下这个计算器的外部调用方式

public static void main(String[] args) {
    MyCalculator cal = new MyCalculator();
    double result = cal.compute("(2 + 1) + 200 * 3.2 / (5 * (2 + 6 ))");
}

上面表达式的计算结果是19.0。

完整的代码

下面是完整的代码

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MyCalculator {
    public double compute(String expression) {
        int start = -1;
        for (int i = 0; i < expression.length(); i++) {
            if (expression.charAt(i) == '(') {
                start = i;
            }
            if (expression.charAt(i) == ')') {
                // 递归来简化问题
                String flatExp = expression.substring(start + 1, i);
                double result = computeFlatExp(flatExp);
                StringBuilder sb = new StringBuilder();
                if (start > 0) {
                    sb.append(expression.substring(0, start));
                }
                sb.append(String.valueOf(result));
                if (i < expression.length() - 1) {
                    sb.append(expression.substring(i + 1));
                }
                return compute(sb.toString());
            }
        }

        // 计算加减乘除
        return computeFlatExp(expression);
    }


    /**
     * 计算加减乘除,不含括号
     *
     * @param flatExp
     * @return
     */
    private double computeFlatExp(String flatExp) {
        // 先递归计算所有乘法和除法
        Pattern pattern = Pattern.compile("(?<num1>[\\d\\.]+)\\s*(?<operator>[\\*/])\\s*?(?<num2>[\\d\\.]+)");
        Matcher matcher = pattern.matcher(flatExp);
        while (matcher.find()) {
            double num1 = Double.parseDouble(matcher.group("num1"));
            double num2 = Double.parseDouble(matcher.group("num2"));
            String operator = matcher.group("operator");
            double result = operator.contains("*") ? num1 * num2 : num1 / num2;
            flatExp = flatExp.replace(matcher.group(0), String.valueOf(result));
            matcher = pattern.matcher(flatExp);
        }

        // 再递归计算所有加法和减法
        pattern = Pattern.compile("(?<num1>[\\d\\.]+)\\s*(?<operator>[\\+\\-])\\s*?(?<num2>[\\d\\.]+)");
        matcher = pattern.matcher(flatExp);
        while (matcher.find()) {
            Double num1 = Double.parseDouble(matcher.group("num1"));
            Double num2 = Double.parseDouble(matcher.group("num2"));
            String operator = matcher.group("operator");
            double result = operator.contains("+") ? num1 + num2 : num1 - num2;
            flatExp = flatExp.replace(matcher.group(0), String.valueOf(result));
            matcher = pattern.matcher(flatExp);
        }

        return Double.parseDouble(flatExp.trim());
    }



    public static void main(String[] args) {
        MyCalculator cal = new MyCalculator();
        double result = cal.compute("(2 + 1) + 200 * 3.2 / (5 * (2 + 6 ))");
    }
}