第二版请见:https://www.cnblogs.com/xiandedanteng/p/11451359.html
入口类,这个类的主要用途是粗筛用户输入的算术表达式:
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==')' ){ //System.out.print(c); expression+=c; }else{ System.out.print(" "+c+"不是合法的算术表达式字符."); System.exit(0); } } // 送去解析 Parser p=new Parser(expression); //p.print(); // 转为后序表达式 Trans t=new Trans(p.getList()); //t.print(); // 计算结果 Calculator c=new Calculator(t.getPostfixList()); System.out.print(expression+"="+c.getResult()); } }
算术表达式解析器类,它主要起一个词法分析器的作用,由于算术表达式词法较简单,因此逐字读入处理也能完成任务,他的输入是如23+4*(5+6)这种算术表达式,处理完成以后得到23,+,4,*,(,5,+,6,)这些包含操作数和操作符的列表:
package com.hy; import java.util.ArrayList; import java.util.List; // 此类用于将算术表达式解析成包含操作数和操作符的链表 public class Parser { private List<String> list;// 用于存储表达式的链表 public List<String> getList() { return list; } public Parser(String expression){ list=new ArrayList<String>(); String str=""; for(int i=0;i<expression.length();i++){ char c=expression.charAt(i); if(Character.isDigit(c)){ str+=c; }else{ if(str.length()>0){// 此判断是因为有+(这种符号相连的情况 //System.out.println(str); list.add(str); str=""; } //System.out.println(c); list.add(String.valueOf(c)); } } if(str.length()>0){// 此判断是因为可能表达式不是以=结尾 //System.out.println(str); list.add(str); str=""; } } public void print(){ for(String str:list){ System.out.println(str); } } }
将中序表达式转后序表达式的转换类,他接收来自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); } } }
计算后续表达式运算结果类,它接受经过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(); } }
运行结果:
请输入算术表达式:(2+3)*6-20 (2+3)*6-20=10.0 请输入算术表达式:13-5*(1+2) 13-5*(1+2)=-2.0 请输入算术表达式:1+2+3+4+5+6+7+8+9+10 1+2+3+4+5+6+7+8+9+10=55.0
到这里,基本上算是实现了Javascript里的eval函数,当然还有需要完善的地方,比如用正则表达式对输入的算术表达式进行预验证,用二叉树形成语法结构等,这些留待日后完成。可以想象如果没有利用后续表达式助力,代码不知会写得多么复杂难懂。由此可知除了分解问题外,合适的数学工具也是改善代码的重要手段。
喝水不忘挖井人,我的参考资料如下:
1.Java数据结构与算法(第二版) [美]Robert Lafore著
2.栈的应用--中序表达式转后序表达式 https://www.cnblogs.com/bgmind/p/3989808.html
--END--2019年9月2日13点35分