JAVA四則運算字串直譯器
最近學習到字尾表示式,於是基於字尾表示式的思想,寫了一個四則運算直譯器,輸入字串型別的四則運算表示式,可以直接得到結果,支援括號巢狀. 實現時主要考慮以下兩點:
- 字串中運算子和數字分離
- 運算子優先順序
- 括號的巢狀
- 運算子和數字分離:可以考慮用字串陣列儲存
- 關於運算子優先順序,最開始的想法是將乘除法看作一類,加減法看作一類,乘除法的優先順序大於加減法,相同型別的運算子按照從左到右順序依次計算.
- 括號的巢狀:由於括號內部本身也是表示式,因此可以使用遞迴處理,但重點在於括號的配對,由於使用遞迴,所以希望獲取最大的巢狀單元.
具體實現:
首先實現運算子識別的程式碼,判斷一個字元是否為運算子:
public static boolean opjudge(char c) { if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')') return true; else return false; }
然後實現字串表示式轉為字串陣列, 由於要求輸入的字串運算子和數字之間沒有空格,所以直接拆分不方便,而且運算子和數字的數目不確定,因此先使用ArrayList儲存,然後再轉成字串陣列,主要方法是遍歷字串中每一個字元並判斷是否為運算子,如果是運算子,則直接將運算子轉為字串加入ArrayList,如果不是,則從此位置繼續查詢到下一個運算子出現的位置,兩個位置之間即為運算元,使用substring擷取字串存入Arraylist,具體實現如下:
public static String[] convert(String s) { ArrayList<String> arrayList = new ArrayList<>(); for (int i = 0; i < s.length(); i++) { if (!opjudge(s.charAt(i))) { int j = i; while ((i < s.length()) && !opjudge(s.charAt(i))) i++; arrayList.add(s.substring(j, i)); i--; } else arrayList.add(String.valueOf(s.charAt(i))); } return arrayList.toArray(new String[arrayList.size()]); }
然後對運算子進行分類和分級,加減法視作一類,乘除法視作一類,乘除法優先順序高於加減法:
public static int opvalue(String s) { switch (s) { case "+": case "-": return 1; case "*": case "/": return 2; default: return -1; } }
運算子優先順序的比較,左邊大於右邊則返回true,( 兩個運算子相等返回false)
public static boolean opcompare(String s1, String s2) { if (opvalue(s1) > opvalue(s2)) return true; else { return false; } }
括號內字串的獲取: 由於括號都是成對出現,要從左至右獲取第一個最長的括號表示式,則從左至右遍歷字串時,當右括號 ‘)’ 出現的數目等於左括號’(’ 時,即為所求,此處使用計數器實現,返回的是字串中從k位置(第k個元素,一個數字算一個元素 )開始第一個帶完整括號的子串,比如輸入bracketGet(“(8-(2+3))*2+(5+7)*3”,0),返回8-(2+3):
public static String bracketGet(String s, int k) { int m=0; int i; String[] arr=convert(s); for(i=k;i<arr.length;i++){ if(arr[i].equals("(")){ m++; continue; } if(arr[i].equals(")")){ m--; if(m==0) break; else continue; } } StringBuilder sb=new StringBuilder(); for(int j=k+1;j<i;j++){ sb.append(arr[j]); } return sb.toString(); }
以上都是需要用到的配件,下面將利用上面寫好的函式實現四則運算字串的解析和運算,基本思想是使用兩個棧,一個儲存數字,一個儲存運算子,先考慮沒有括號的簡單情況: 遍歷字串陣列: 1.當前元素為運算元時,入數字棧 2.當前元素為運算子時:
- 如果運算子棧為空,則入運算子棧
- 如果運算子棧不空,則比較當前運算子和棧頂運算子優先順序(按照之前定義的優先順序,乘除大於加減,乘除相同,加減相同):如果當前運算子優先順序大於棧頂運算子,則當前運算子入棧;反之,如果當前運算子優先順序等於或小於棧頂運算子,則將棧頂運算子出棧,同時數字棧中出棧兩個數字參與運算,結果壓入數字棧中,然後當前運算子入棧;
- 最後將運算子棧中所有元素依次出棧,數字棧中相應將數字出棧參與運算得到結果並壓入棧中;
- 最後返回數字棧中棧頂元素即為表示式結果.
- 有括號時括號內的運算步驟同上,可以獲取括號內部的字串,使用遞迴計算; 程式碼如下:
-
public static int caculate(String formula) { String[] arr = convert(formula); Stack<Integer> val = new Stack<>(); Stack<String> op = new Stack<>(); for (int i = 0; i < arr.length; i++) { if (arr[i].equals("(")) { val.push(caculate(bracketGet(formula, i)));//遞迴計算括號內部的值,並將該值入棧 i = i + bracketGet(formula, i).length() + 1;//括號串所在的位置遍歷過,i需要跳過該段 } else { if (arr[i].equals("+") || arr[i].equals("-") || arr[i].equals("*") || arr[i].equals("/")) { if (op.isEmpty()) op.push(arr[i]); else if (opcompare(arr[i], op.lastElement())) { op.push(arr[i]); } else { switch (op.pop()) { case "+": val.push(val.pop() + val.pop()); break; case "-": int c = val.pop(); int d = val.pop(); val.push(d-c);//減法,pop的順序從右至左,所以需要先取出元素 break; case "*": val.push(val.pop() * val.pop()); break; case "/": int a = val.pop(); int b = val.pop(); val.push(b / a);//同減法 break; default: break; } op.push(arr[i]); } } else val.push(Integer.parseInt(arr[i])); } } while (!op.isEmpty()) { switch (op.pop()) { case "+": val.push(val.pop() + val.pop()); break; case "-": int c = val.pop(); int d = val.pop(); val.push(d-c); break; case "*": val.push(val.pop() * val.pop()); break; case "/": int a = val.pop(); int b = val.pop(); val.push(b / a); break; default: break; } } return val.pop(); } System.out.println(caculate("(((8-(2+3))*2+(5+7)*3))")); 輸出:42 正確
以上實現是使用int型別,除法可能會不準確,改為double會準確
後面又發現上面的實現方法仍然有冗餘之處,下面是改進的程式碼,主要是優化了運算子的優先順序,依次為 除法 乘法 減法 加法 依次降低, 遍歷到運算子時,優先順序高才入棧, 否則將棧中運算子出棧參與運算,直到棧空或 棧頂運算子優先順序低於遍歷到的運算子,才將該運算子入棧. 主要改動的是opjudge(),opcompare(), caculate()中少量改動,其餘程式碼同上; 具體程式碼如下:
-
public static int opvalue2(String s) { switch (s) { case "+": return 1; case "-": return 2; case "*": return 3; case "/": return 4; default: return -1; } }
public static boolean opcompare2(String s1, String s2) { if (opvalue2(s1) >= opvalue2(s2)) return true; else { return false; } }
public static double caculate2(String formula) { String[] arr = convert(formula); Stack<Double> val = new Stack<>(); Stack<String> op = new Stack<>(); for (int i = 0; i < arr.length; i++) { if (arr[i].equals("(")) { val.push(caculate2(bracketGet(formula, i))); i = i + bracketGet(formula, i).length() + 1; } else if (arr[i].equals("+") || arr[i].equals("-") || arr[i].equals("*") || arr[i].equals("/")) { while (!op.isEmpty() && opcompare2(op.lastElement(), arr[i])) { switch (op.pop()) { case "+": val.push(val.pop() + val.pop()); continue; case "-": double c = val.pop(); double d = val.pop(); val.push(d - c); continue; case "*": val.push(val.pop() * val.pop()); continue; case "/": double a = val.pop(); double b = val.pop(); val.push(b / a); continue; default: break; } } op.push(arr[i]); } else val.push(Double.parseDouble(arr[i])); } while (!op.isEmpty()) { switch (op.pop()) { case "+": val.push(val.pop() + val.pop()); break; case "-": double c = val.pop(); double d = val.pop(); val.push(d - c); break; case "*": val.push(val.pop() * val.pop()); break; case "/": double a = val.pop(); double b = val.pop(); val.push(b / a); break; default: break; } } return val.pop(); }
運算例項:
-
public static void main(String[] args) { double a=(1+2*(3.0/2)/(2+8)-(2*6.0)/(1+7))-(21+3*(5-2-(7*(4-3)))); System.out.println(a); System.out.println(caculate2("(1+2*(3/2)/(2+8)-(2*6)/(1+7))-(21+3*(5-2-(7*(4-3))))")); } //output: -9.2 -9.2
至此,完成了四則運算字串直譯器,還有很多可以完善, 比如異常的處理,以及程式碼的簡潔性,作為一個初學者,如果能被看到的話,希望多提建議:
完整版程式碼如下:
-
import java.util.ArrayList; import java.util.Stack; public class Main { public static void main(String[] args) { double a=(1+2*(3.0/2)/(2+8)-(2*6.0)/(1+7))-(21+3*(5-2-(7*(4-3)))); System.out.println(a); System.out.println(caculate2("(1+2*(3/2)/(2+8)-(2*6)/(1+7))-(21+3*(5-2-(7*(4-3))))")); } //對運算子優先順序進一步排序 減法大於加法 除法大於乘法 public static double caculate2(String formula) { String[] arr = convert(formula); Stack<Double> val = new Stack<>(); Stack<String> op = new Stack<>(); for (int i = 0; i < arr.length; i++) { if (arr[i].equals("(")) { val.push(caculate2(bracketGet(formula, i))); i = i + bracketGet(formula, i).length() + 1; } else if (arr[i].equals("+") || arr[i].equals("-") || arr[i].equals("*") || arr[i].equals("/")) { while (!op.isEmpty() && opcompare2(op.lastElement(), arr[i])) { switch (op.pop()) { case "+": val.push(val.pop() + val.pop()); continue; case "-": double c = val.pop(); double d = val.pop(); val.push(d - c); continue; case "*": val.push(val.pop() * val.pop()); continue; case "/": double a = val.pop(); double b = val.pop(); val.push(b / a); continue; default: break; } } op.push(arr[i]); } else val.push(Double.parseDouble(arr[i])); } while (!op.isEmpty()) { switch (op.pop()) { case "+": val.push(val.pop() + val.pop()); break; case "-": double c = val.pop(); double d = val.pop(); val.push(d - c); break; case "*": val.push(val.pop() * val.pop()); break; case "/": double a = val.pop(); double b = val.pop(); val.push(b / a); break; default: break; } } return val.pop(); } public static String bracketGet(String s, int k) { int m=0; int i; String[] arr=convert(s); for(i=k;i<arr.length;i++){ if(arr[i].equals("(")){ m++; continue; } if(arr[i].equals(")")){ m--; if(m==0) break; else continue; } } StringBuilder sb=new StringBuilder(); for(int j=k+1;j<i;j++){ sb.append(arr[j]); } return sb.toString(); } public static String[] convert(String s) { ArrayList<String> arrayList = new ArrayList<>(); for (int i = 0; i < s.length(); i++) { if (!opjudge(s.charAt(i))) { int j = i; while ((i < s.length()) && !opjudge(s.charAt(i))) i++; arrayList.add(s.substring(j, i)); i--; } else arrayList.add(String.valueOf(s.charAt(i))); } return arrayList.toArray(new String[arrayList.size()]); } public static boolean opjudge(char c) { if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')') return true; else return false; } public static int opvalue2(String s) { switch (s) { case "+": return 1; case "-": return 2; case "*": return 3; case "/": return 4; default: return -1; } } public static boolean opcompare2(String s1, String s2) { if (opvalue2(s1) >= opvalue2(s2)) return true; else { return false; } }
最近學習到字尾表示式,於是基於字尾表示式的思想,寫了一個四則運算直譯器,輸入字串型別的四則運算表示式,可以直接得到結果,支援括號巢狀. 實現時主要考慮以下兩點:
- 字串中運算子和數字分離
- 運算子優先順序
- 括號的巢狀
- 運算子和數字分離:可以考慮用字串陣列儲存
- 關於運算子優先順序,最開始的想法是將乘除法看作一類,加減法看作一類,乘除法的優先順序大於加減法,相同型別的運算子按照從左到右順序依次計算.
- 括號的巢狀:由於括號內部本身也是表示式,因此可以使用遞迴處理,但重點在於括號的配對,由於使用遞迴,所以希望獲取最大的巢狀單元.
具體實現:
首先實現運算子識別的程式碼,判斷一個字元是否為運算子:
public static boolean opjudge(char c) {
if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')')
return true;
else
return false;
}
然後實現字串表示式轉為字串陣列, 由於要求輸入的字串運算子和數字之間沒有空格,所以直接拆分不方便,而且運算子和數字的數目不確定,因此先使用ArrayList儲存,然後再轉成字串陣列,主要方法是遍歷字串中每一個字元並判斷是否為運算子,如果是運算子,則直接將運算子轉為字串加入ArrayList,如果不是,則從此位置繼續查詢到下一個運算子出現的位置,兩個位置之間即為運算元,使用substring擷取字串存入Arraylist,具體實現如下:
public static String[] convert(String s) {
ArrayList<String> arrayList = new ArrayList<>();
for (int i = 0; i < s.length(); i++) {
if (!opjudge(s.charAt(i))) {
int j = i;
while ((i < s.length()) && !opjudge(s.charAt(i)))
i++;
arrayList.add(s.substring(j, i));
i--;
} else
arrayList.add(String.valueOf(s.charAt(i)));
}
return arrayList.toArray(new String[arrayList.size()]);
:
public static void main(String[] args) {
double a=(1+2*(3.0/2)/(2+8)-(2*6.0)/(1+7))-(21+3*(5-2-(7*(4-3))));
System.out.println(a);
System.out.println(caculate2("(1+2*(3/2)/(2+8)-(2*6)/(1+7))-(21+3*(5-2-(7*(4-3))))"));
}
//output:
-9.2
-9 }
//對運算子優先順序進一步排序 減法大於加法 除法大於乘法
public static double caculate2(String formula) {
String[] arr = convert(formula);
Stack<Double> val = new Stack<>();
Stack<String> op = new Stack<>();
for (int i = 0; i < arr.length; i++) {
if (arr[i].equals("(")) {
val.push(caculate2(bracketGet(formula, i)));
i = i + bracketGet(formula, i).length() + 1;
} else if (arr[i].equals("+") || arr[i].equals("-") || arr[i].equals("*") || arr[i].equals("/")) {
while (!op.isEmpty() && opcompare2(op.lastElement(), arr[i])) {
switch (op.pop()) {
case "+":
val.push(val.pop() + val.pop());
continue;
case "-":
double c = val.pop();
double d = val.pop();
val.push(d - c);
continue;
case "*":
val.push(val.pop() * val.pop());
continue;
case "/":
double a = val.pop();
double b = val.pop();
val.push(b / a);
continue;
default:
break;
}
}
op.push(arr[i]);
}
else
val.push(Double.parseDouble(arr[i]));
}
while (!op.isEmpty()) {
switch (op.pop()) {
case "+":
val.push(val.pop() + val.pop());
break;
case "-":
double c = val.pop();
double d = val.pop();
val.push(d - c);
break;
case "*":
val.push(val.pop() * val.pop());
break;
case "/":
double a = val.pop();
double b = val.pop();
val.push(b / a);
break;
default:
break;
}
}
return val.pop();
}
public static String bracketGet(String s, int k) {
int m=0;
int i;
String[] arr=convert(s);
for(i=k;i<arr.length;i++){
if(arr[i].equals("(")){
m++;
continue;
}
if(arr[i].equals(")")){
m--;
if(m==0)
break;
else
continue;
}
}
StringBuilder sb=new StringBuilder();
for(int j=k+1;j<i;j++){
sb.append(arr[j]);
}
return sb.toString();
}
public static String[] convert(String s) {
ArrayList<String> arrayList = new ArrayList<>();
for (int i = 0; i < s.length(); i++) {
if (!opjudge(s.charAt(i))) {
int j = i;
while ((i < s.length()) && !opjudge(s.charAt(i)))
i++;
arrayList.add(s.substring(j, i));
i--;
} else
arrayList.add(String.valueOf(s.charAt(i)));
}
return arrayList.toArray(new String[arrayList.size()]);
}
public static boolean opjudge(char c) {
if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')')
return true;
else
return false;
}
public static int opvalue2(String s) {
switch (s) {
case "+":
return 1;
case "-":
return 2;
case "*":
return 3;
case "/":
return 4;
default:
return -1;
}
}
public static boolean opcompare2(String s1, String s2) {
if (opvalue2(s1) >= opvalue2(s2))
return true;
else {
return false;
}