第二版請見: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分