解析字符串形式的数学表达式

import java.util.*;

/**
 * Created by threetwo on 17/3/23.
 */
public class Calculator {
    /**
     * opd: 运算数
     * ops: 运算符
     */
    private static final int TYPE_OPD = 0;
    private static final int TYPE_OPS = 1;

    /* 运算符原始优先级映射 */
    private static final Map<String, Integer> opsPriority = new HashMap<>();
    /* 运算符中优先级最大的数值*/
    private static int maxPriority;

    static {
        opsPriority.put("+", 1);
        opsPriority.put("-", 1);
        opsPriority.put("*", 2);
        opsPriority.put("/", 2);
        maxPriority = opsPriority.values().stream().max(Integer::compareTo).get();
    }

    /**
     * opsList: 运算符结点列表
     * last: 最新添加的结点,便于添加新的结点
     * base: 变动的优先级值,随着左括号的出现增加,右括号的出现减少
     * mathExpression: 字符串形式的数学表达式,目前只支持 加减乘除,小括号
     * result: 数学表达式的值
     */
    private List<Node> opsList = new ArrayList<>();
    private Node last = null;
    private int base = 0;
    private String mathExpression;
    private Double result = null;

    private class Node{
        int type;
        double value;
        int priority;
        String ops;
        Node prev;

        Node next;
        public Node(int type, String ops){
            this.type = type;
            this.ops = ops;
            this.priority = opsPriority.get(ops);
        }
        public Node(int type, double value){
            this.type = type;
            this.value = value;
        }

    }

    public Calculator(String mathExpression){
        this.mathExpression = mathExpression;
        parseExpression(this.mathExpression);
    }

    /**
     * 添加操作数结点
     * @param value: 操作数结点的值
     * @return 刚添加的结点
     */
    private Node addOpdNode(double value){
        Node opdNode = new Node(TYPE_OPD, value);
        if (last == null){
            last = opdNode;
        }else{
            last.next = opdNode;
            opdNode.prev = last;
            last = opdNode;
        }
        return opdNode;
    }

    /**
     * 添加运算符结点
     * @param ops: 运算符
     * @return 刚添加的结点
     */
    private Node addOpsNode(String ops){
        Node opsNode = new Node(TYPE_OPS, ops);
        opsNode.priority = base + opsPriority.get(ops);
        last.next = opsNode;
        opsNode.prev = last;
        last = opsNode;
        return opsNode;
    }

    /**
     * 字符串形式的数学表达式解析成为元素具有优先级的链表
     * @param expression: 数学表达式。操作数,运算符,括号必须使用一个空格作为分隔符,例:1 + 2 * ( 3 - 4 )
     */
    private void parseExpression(String expression) {
        String[] parts = expression.split(" ");

        for (int i = 0; i < parts.length; i++) {
            String c = parts[i];
            if(c.equals("(")){
                base += maxPriority;
            }else if(c.equals(")")){
                base -= maxPriority;
            }else if(Character.isDigit(c.charAt(0))){
                addOpdNode(Double.parseDouble(c));
            }else{
                Node opsNode = addOpsNode(c);
                opsList.add(opsNode);
            }
        }
    }

    /**
     * 将字符串形式的数学表达式解析成为链表(其中的元素具有优先级)
     * @param expression: 数学表达式。操作数,运算符,括号是否使用空格作为分隔符无所谓
     */
    private void parseExpressionWithPerCharacter(String expression) {

        // 目前解析出的值
        double value = 0;

        // 记录目前已知有多少位小数
        int count = 0;

        // 如果出现小数点则是浮点数
        boolean isFloat = false;

        // 是否已经存在解析出的值,在添加结点后置为false,若出现数字字符置为true
        boolean hasValue = false;

        String[] parts = expression.split(" ");

        // 对字符串形式的数学表达式一个一个字符的解析
        for (int i = 0; i < expression.length(); i++) {
            char c = expression.charAt(i);
            if(c == '('){
                base += 2;
            }else if(c == ')'){
                if (hasValue){
                    addOpdNode(value);
                    hasValue = false;
                    value = 0;
                }
                base -= 2;
            }else if(c == '.'){
                isFloat = true;
            }else if(Character.isDigit(c)){
                hasValue = true;
                int n = Integer.parseInt(Character.toString(c));
                if (isFloat){
                    value = value + n * Math.pow(10, -(++count));
                }else{
                    value = value * 10 + n;
                }
            }else if(c == ' '){
                continue;
            }else{
                if (hasValue){
                    addOpdNode(value);
                    value = 0;
                }
                Node opsNode = addOpsNode(Character.toString(c));
                opsList.add(opsNode);

                isFloat = false;
                count = 0;
                hasValue = false;
            }
        }
        if (hasValue){
            addOpdNode(value);
        }
    }

    /**
     * 依据运算符类型执行双目运算
     * @param ops: 双目运算符
     * @param leftOpd: 左操作数
     * @param rightOpd: 右操作数
     * @return 运算结果
     */
    private static double doOps(String ops, double leftOpd, double rightOpd){
        if(ops.equals("+")){
            return leftOpd + rightOpd;
        }else if(ops.equals("-")){
            return leftOpd - rightOpd;
        }else if(ops.equals("*")){
            return leftOpd * rightOpd;
        }else if(ops.equals("/")){
            return leftOpd / rightOpd;
        }
        return 0;
    }

    /**
     * 执行指定的运算符结点的运算
     * @param ops: 需要执行运算的运算符结点
     */
    private static void apply(Node ops){
        Node arg1Node = ops.prev;
        Node arg2Node = ops.next;

        ops.value = doOps(ops.ops, arg1Node.value, arg2Node.value);
        if(arg1Node.prev != null){
            arg1Node.prev.next = ops;
        }
        if(arg2Node.next != null){
            arg2Node.next.prev = ops;
        }
        ops.prev = arg1Node.prev;
        ops.next = arg2Node.next;
    }

    /**
     * 获取表达式的结果
     * @return
     */
    public double getResult(){
        if (result != null){
            return result;
        }

        // 将运算符结点列表按照优先级从高到低排序
        opsList.sort((n1, n2)->{
            if(n1.priority > n2.priority)
                return -1;
            if(n1.priority < n2.priority)
                return 1;
            else
                return 0;
        });

        // 执行每一个运算符结点的运算
        for(Node ops: opsList){
            apply(ops);
        }

        // 最终结果就在最后执行运算的运算符结点中
        Node resultNode = opsList.get(opsList.size()-1);
        result = resultNode.value;

        return result;
    }

    /**
     * 获取数学表达式
     * @return
     */
    public String getMathExpression(){
        return this.mathExpression;
    }


    public static void main(String[] args) {
        Calculator calculator = new Calculator("( ( 1.1 * 3 ) ) / 1 + 2");
        System.out.println(calculator.getResult());

    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容