Inlet类,入口类,这个类的主要用途是验证用户输入的算术表达式:

package com.hy;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

// 此类用于把算术表达式送入解析器
public class Inlet {
    public static void main(String[] args) throws IOException{
        // 取得用户输入的表达式
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String rawExpression = null;
        System.out.print("请输入算术表达式:");
        rawExpression = br.readLine(); 

        // 得到合法的算术表达式
        String expression="";
        for(int i=0;i<rawExpression.length();i++){
            // 拿到表达式的每个字符
            char c=rawExpression.charAt(i);
            //System.out.print(c+","); 

            if(Character.isDigit(c) || c=='+' || c=='-' || c=='*' || c=='/' || c=='(' || c==')' || c=='.'){
                //System.out.print(c);
                expression+=c;
            }else{
                System.out.print(" "+c+"不是合法的算术表达式字符.");
                System.exit(0);
            }
        }

        // 送去解析
        Lexer p=new Lexer(expression);
        //p.print();

        // 转为后序表达式
        Trans t=new Trans(p.getList());
        //t.print();

        // 计算结果
        Calculator c=new Calculator(t.getPostfixList());
        System.out.print(expression+"="+c.getResult());
    }
}

Lexer类,主要起一个词法分析器的作用,注意这里采用正则表达式简化了代码,比https://www.cnblogs.com/xiandedanteng/p/11445994.html 中Parser类的处理方式简洁些:

package com.hy;

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

// 此类用于将算术表达式解析成包含操作数和操作符的链表,扮演分词器的角色
public class Lexer {
    private List<String> list;// 用于存储表达式的链表

    public List<String> getList() {
        return list;
    }

    public Lexer(String expression){
        list=new ArrayList<String>();

        // 使用正则表达式后,代码简洁多了
        String regExp = "(\\d+(\\.*)\\d*)|(\\+)|(\\-)|(\\*)|(\\/)|(\\()|(\\))";

        Pattern pattern=Pattern.compile(regExp);
        Matcher matcher=pattern.matcher(expression);
        while(matcher.find()){
            list.add(matcher.group(0));
        }
    }

    public void print(){
        for(String str:list){
            System.out.println(str);
        }
    }
}

Trans类 将中序表达式转后序表达式的转换类,他接收来自Parser的包含操作符和操作数的列表,然后根据规则将算术表达式转化成后序表达式,利用的数据结构是栈java.util.Statck,转化的规则如下:

见到操作数->直接送到postfixList中
见到操作符->将栈顶输出,直到栈顶优先级小于该操作符,最后把该操作符压入栈
见到左括号 ->入栈
见到右括号 ->将栈中在左括号之后的操作符全部输出
(以上'栈'在代码中指的是Trans类的成员变量Stack):

package com.hy;

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

// 此类用于将中序表达式转译成后序表达式
public class Trans {
    private Stack<String> stack;// 用于存储操作符的栈
    private List<String> postfixList;// 用于存储后序表达式的链表

    public List<String> getPostfixList() {
        return postfixList;
    }

    public Trans(List<String> list){
        stack=new Stack<String>();
        postfixList=new ArrayList<String>();

        for(String str:list){
            // 这个分支是当前项是操作符号的情况
            if(str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/") || str.equals("(") || str.equals(")")  ){
                String opThis=str;

                if(stack.size()==0){
                    // 如果栈为空,直接把操作符推入栈
                    stack.push(opThis);
                }else if(str.equals("(")){
                    // 如果操作符是左括号,直接推入栈
                    stack.push(opThis);
                }else if(str.equals(")")){
                    // 如果操作符是右括号,则往前找左括号,将左括号之后的操作符放到后续表达式列表中

                    while(stack.peek().equals("(")==false){ // stack.peek()是取栈顶元素而不弹出
                        postfixList.add(stack.pop());
                    }

                    stack.pop();// 左括号丢弃,由此完成了去括号的过程
                }else{
                    // 看栈顶元素,如果它优先级大于等于当前操作符的优先级,则弹出放到后续表达式列表中
                    while( stack.size()>0 && (getOpLevel(stack.peek())>=getOpLevel(opThis)) ){
                        postfixList.add(stack.pop());
                    }

                    stack.push(opThis);// 当前操作符入栈
                }

            }else{
                // 这个分支是当前项是操作数的情况
                postfixList.add(str);// 操作数直接入栈
            }
        }

        // 将栈中余下的操作符弹出放到后续表达式列表中
        while(stack.size()>0){
            String opTop=stack.pop();
            postfixList.add(opTop);
        }
    }

    // 取得操作符的等级
    private int getOpLevel(String op){
        if(op.equals("+") || op.equals("-") ){
            return 0;
        }else if(op.equals("*") || op.equals("/") ){
            return 1;
        }

        return -1;
    }

    public void print(){
        for(String str:postfixList){
            System.out.print(str);
        }
    }
}

Calculator类 计算后续表达式运算结果类,它接受经过Trans类处理的postfixList,又采用了栈进行辅助,计算结果方式是见到操作数先入栈,见到操作符则从栈中弹出两个操作数进行运算,得到结果后再入栈,执行完毕后弹出栈的顶项(必是最后一项)即是算术表达式的最终结果::

package com.hy;

import java.util.List;
import java.util.Stack;

// 此类用于计算后续表达式的值
public class Calculator {
    private Stack<String> stack;

    public Calculator(List<String> list){
        stack=new Stack<String>();

        for(String str:list){
            // 这个分支是当前项是操作符号的情况
            if(str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/") || str.equals("(") || str.equals(")")  ){
                float op2=Float.parseFloat(stack.pop());
                float op1=Float.parseFloat(stack.pop());
                float result=0;

                if(str.equals("+")){
                    result=op1+op2;
                }else if(str.equals("-")){
                    result=op1-op2;
                }else if(str.equals("*")){
                    result=op1*op2;
                }else if(str.equals("/")){
                    result=op1/op2;
                }

                stack.push(String.valueOf(result));
            }else{
                // 如果是操作数直接入栈
                stack.push(str);
            }
        }
    }

    // 取得结果
    public String getResult(){
        return stack.peek();
    }
}

输出示例:

请输入算术表达式:1.2+3.4-5*(1+3.1)
1.2+3.4-5*(1+3.1)=-15.9
请输入算术表达式:23-4-5*7
23-4-5*7=-16.0
请输入算术表达式:(2+3)*4-(5-6)*7.0
(2+3)*4-(5-6)*7.0=27.0

到这里,基本上算是实现了算术表达式的计算,当然还有需要完善的地方,比如用正则表达式对输入的算术表达式进行预验证,用二叉树形成语法结构等,这些留待日后完成。可以想象如果没有利用波兰数学家卢卡希维茨(Jan Lukasiewicz)发明的后续表达式助力,代码不知会写得多么复杂难懂。由此可知除了分解问题外,合适的数学工具也是改善代码的重要手段。

喝水不忘挖井人,我的参考资料如下:

1.Java数据结构与算法(第二版) [美]Robert Lafore著

2.栈的应用--中序表达式转后序表达式  https://www.cnblogs.com/bgmind/p/3989808.html

3.波兰式,逆波兰式与表达式求值 https://blog.csdn.net/linraise/article/details/20459751

另外使用二叉树计算算术表达式的方案请见 https://www.cnblogs.com/xiandedanteng/p/11457783.html

--END--2019年9月3日10点08分

[Java]算术表达式求值之二(中序表达式转后序表达式方案,支持小数)的更多相关文章

  1. [Java]算术表达式求值之三(中序表达式转二叉树方案 支持小数)

    Entry类 这个类对表达式的合法性进行了粗筛: package com.hy; import java.io.BufferedReader; import java.io.IOException; ...

  2. [Java]算术表达式求值之一(中序表达式转后序表达式方案)

    第二版请见:https://www.cnblogs.com/xiandedanteng/p/11451359.html 入口类,这个类的主要用途是粗筛用户输入的算术表达式: package com.h ...

  3. 刁肥宅详解中缀表达式求值问题:C++实现顺序/链栈解决

    1. 表达式的种类 如何将表达式翻译成能够正确求值的指令序列,是语言处理程序要解决的基本问题,作为栈的应用事例,下面介绍表达式的求值过程. 任何一个表达式都是由操作数(亦称运算对象).操作符(亦称运算 ...

  4. NYOJ 35 表达式求值(逆波兰式求值)

    http://acm.nyist.net/JudgeOnline/problemset.php?typeid=4 NYOJ 35 表达式求值(逆波兰式求值) 逆波兰式式也称后缀表达式. 一般的表达式求 ...

  5. 利用栈实现算术表达式求值(Java语言描述)

    利用栈实现算术表达式求值(Java语言描述) 算术表达式求值是栈的典型应用,自己写栈,实现Java栈算术表达式求值,涉及栈,编译原理方面的知识.声明:部分代码参考自茫茫大海的专栏. 链栈的实现: pa ...

  6. java实现算术表达式求值

    需要根据配置的表达式(例如:5+12*(3+5)/7.0)计算出相应的结果,因此使用java中的栈利用后缀表达式的方式实现该工具类. 后缀表达式就是将操作符放在操作数的后面展示的方式,例如:3+2 后 ...

  7. Dijkstra的双栈算术表达式求值算法

    这次来复习一下Dijkstra的双栈算术表达式求值算法,其实这就是一个计算器的实现,但是这里用到了不一样的算法,同时复习了栈. 主体思想就是将每次输入的字符和数字分别存储在两个栈中.每遇到一个单次结束 ...

  8. 蓝桥杯算法训练 java算法 表达式求值

    问题描述 输入一个只包含加减乖除和括号的合法表达式,求表达式的值.其中除表示整除. 输入格式 输入一行,包含一个表达式. 输出格式 输出这个表达式的值. 样例输入 1-2+3*(4-5) 样例输出 - ...

  9. 【算法】E.W.Dijkstra算术表达式求值

    算术表达式求值 我们要学习的一个栈的用例同时也是展示泛型的应用的一个经典例子,就是用来计算算术表达式的值,例如 ( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) ) 如果将4乘以5,把3 ...

随机推荐

  1. selectpage

    官方文档地址 https://terryz.oschina.io/selectpage/docs.html

  2. python如何编译py文件生成pyc、pyo、pyd以及如何和C语言结合使用

    python执行py文件的流程 当我们执行一个py文件的时候,直接python xx.py即可,那么这个流程是怎么样的呢.先说明一下,python执行代码实际上是先打开文件然后执行里面的代码,所以文件 ...

  3. deep_learning_MNIST数据集

    Code_link:https://pan.baidu.com/s/1dshQt57196fhh67F8nqWow 本文是为既没有机器学习基础也没了解过TensorFlow的码农.序媛们准备的.如果已 ...

  4. Linux使用wget仿站

    运行命令 $ wget -r -p -np -k www.avatrade.cn 参数说明 -r --recursive(递归) specify recursive download.(指定递归下载) ...

  5. 2019.5.13-5.17知识点:文件管理、目录操作、索引节点、软硬链接,vim文本编辑 知识点

    文件管理 ● cp 复制文件 cp /路径(原文件) /路径(复制文件)/文件重命名(可选)    -i:覆盖前询问    -r:递归复制目录及内部内容   -a:归档,相当于-dr --preser ...

  6. 【获取url 问号后参数】防中文乱码

    function getQueryString(name) { var reg = new RegExp("(^|&)" + name + "=([^&] ...

  7. Ubuntu 18.04 系统 Update 后内核升级到5.0 导致 VMware Workstation 不正常工作

    Ubuntu 18.04 系统升级后,发现内核从 4.18 升级到 5.0,从而导致 VMware Workstation 启动主动更新,但总会显示 vmmon 和 vmnet 俩模块编译失败. 感谢 ...

  8. 第七章 路由 71 路由-router-link的使用

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8&quo ...

  9. kubernetes 创建超级管理员和密匙

    # 创建一个超级管理员adm_account="k8s-dash-admin"kubectl create serviceaccount ${adm_account} -n kub ...

  10. VIDENT iSmart900自动多系统扫描工具OBDII支持ABS / SRS / EPB /传输诊断DPF再生/上油复位编码电池配置

    Vident系列中有许多多功能产品,其中最好的是Vident iSmart 900.购买视频系列后,以下是用户的一些评论 乔:因为我想它很好用.该工具很容易更新.我将公制重量单位的代码放到工具中.很容 ...