java棧實現簡單的計算器
阿新 • • 發佈:2019-01-07
一、程式碼
直接上程式碼,整個程式碼分為兩個類calc.StackCalculator.java,calc.Calculator.java
1、StackCalculator.java
/**
* 用棧實現表示式的運算v1.0
* 支援運算:+、-、*、/、%、^、!、()
* 輸入的表示式需以"#"結束
*/
package calc;
import java.util.Stack;
public class StackCalculator {
private Stack<Character> optr = new Stack<Character>();// 操作符棧
private Stack<Float> opnd = new Stack<Float>();// 運算元棧
private final Character END_Character = '#';// 輸入表示式的結束字元
public final int ADD = 0, SUB = 1, MUL = 2, DIV = 3, MOD = 4, POW = 5, FAC = 6, L_P = 7, R_P = 8, EOF = 9;
public final char[][] PRI = {//運算子優先等級 [棧頂]'levle'[當前]
// |----------------當前運算子----------------|
// + - * / % ^ ! ( ) #(結束字元)
/* + */ {'>','>','<','<','<','<','<','<','>','>'},
/* - */ {'>','>','<','<','<','<','<','<','>','>'},
/* 棧 * */ {'>','>','>' ,'>','>','<','<','<','>','>'},
/* 頂 / */ {'>','>','>','>','>','<','<','<','>','>'},
/* 運 % */ {'>','>','>','>','>','<','<','<','>','>'},
/* 算 ^ */ {'>','>','>','>','>','>','<','<','>','>'},
/* 符 ! */ {'>','>','>','>','>','>','>',' ','>','>'},
/* ( */ {'<','<','<','<','<','<','<','<','=',' '},
/* ) */ {' ',' ',' ',' ',' ',' ',' ',' ',' ',' '},
/* # */ {'<','<','<','<','<','<','<','<',' ','='}
};
/**
* 中綴表示式計算,程式核心部分
* @param expression
* 輸入的有效中綴表示式
* @param RPN
* 輸出的逆波蘭表示式(Reverse Polish Notation)
* @return
*/
public float calcExpression(String expression, StringBuffer RPN) throws Exception {
// 初始時操作符棧壓入開始識別符號,與結束字元對應
optr.push(END_Character);
// 剔除表示式中的空格
expression = removeSpace(expression);
int i = 0;// 表示讀取表示式的位置
while (!optr.empty()) {
char currenSymbol = expression.charAt(i);// 當前掃描到的表示式符號
if (Character.isDigit(currenSymbol)) {
i = readNumber(expression, i, opnd);// 讀入(可能多位的)運算元
RPN.append(opnd.peek() + " ");// 計入逆波蘭表示式
} else {
switch (orderBetween(optr.peek(), currenSymbol)) {
case '>':// 棧頂運算子的優先順序大於當前運算子,則1.取出操作符棧頂運算子,2.取出資料棧中的一個或多個數據(取決於運算子的型別),3.運算並把結果壓入資料棧中
char op = optr.pop();// 取出棧頂操作符
RPN.append(op + " ");// 當操作符可以計算時計入逆波蘭表示式,與逆波蘭表示式的計算過程恰好吻合
Calculator ca = new Calculator();// 基本計算操作物件
if ('!' == op) {// 一元運算子的計算
float number = opnd.pop();// 取出運算元棧頂數值
System.out.println("計算過程:" + "[" + number + "]" + "" + op + "=" + "[" +ca.calcu(number, op) +"]" );
opnd.push(ca.calcu(number, op));// 計算並將結果入棧
} else {// 二元運算子的計算
float number2 = opnd.pop();// 取出運算元棧頂數值
float number1 = opnd.pop();// 取出運算元棧頂數值
System.out.println("計算過程:" + "[" + number1 + "]" + "" + op + "" + "[" + number2 + "]" + "=" + "[" +ca.calcu(number1, op, number2) + "]");
opnd.push(ca.calcu(number1, op, number2));// 計算並將結果入棧
}
break;
case '<':// 棧頂運算子的優先順序小於當前運算子,計算推遲,當前運算子入棧
optr.push(currenSymbol);
i++;
break;
case '=':// 棧頂運算子的優先順序等於當前運算子,脫括號並接收下一個字元
optr.pop();
i++;
break;
case ' ':
throw new Exception("ERROR");
}
}
}
return opnd.pop();// 運算元棧的最後一個元素即為需要的結果
}
/**
* 只關注於求中綴表示式的介面
* @param expression
* @return
* @throws Exception
*/
public float calcExpression(String expression) throws Exception {
return calcExpression(expression, new StringBuffer());
}
/**
* 對逆波蘭表示式進行計算
* @param rpn
* @return
* @throws Exception
*/
public float calcExpressionRpn(String rpn) throws Exception{
String[] chs = rpn.split(" ");
int i = 0;
int chLength = chs.length;
while(i < chLength) {
Calculator ca = new Calculator();// 基本計算操作物件
if( convertStrToDigit(chs[i]) != null ) {// 運算元直接入棧
opnd.push(convertStrToDigit(chs[i]));
} else {
char op = chs[i].charAt(0);
if("!".equals( chs[i] )) {
float number = opnd.pop();
opnd.push(ca.calcu(number, op));
System.out.println("計算過程:" + "[" + number + "]" + "" + op + "=" + "[" +ca.calcu(number, op) +"]" );
} else {
float number2 = opnd.pop();// 取出運算元棧頂數值
float number1 = opnd.pop();// 取出運算元棧頂數值
System.out.println("計算過程:" + "[" + number1 + "]" + "" + op + "" + "[" + number2 + "]" + "=" + "[" +ca.calcu(number1, op, number2) + "]");
opnd.push(ca.calcu(number1, op, number2));// 計算並將結果入棧
}
}
i++;
}
return opnd.pop();
}
/**
* 將字串轉化為浮點數
* @param str
* @return
*/
private Float convertStrToDigit(String str) {
try{
float num = Float.valueOf(str);
return num;
} catch(Exception e){
return null;
}
}
/**
* 將char型的數字字元轉為浮點型資料
* @param ch
* @return
*/
private float CharToFloat(char ch) {
return Float.valueOf("" + ch);
}
/**
* 將起始為i的子串解析為數值,並存入運算元棧中
* @param expression 表示式
* @param i 開始解析的位置
* @param stk 將解析完畢的數值存入此棧中
* @return 該數值解析完畢後,返回表示式需要解析的下一個位置
* @throws Exception 解析錯誤
*/
private int readNumber(String expression, int i, Stack<Float> stk) throws Exception {
stk.push(CharToFloat(expression.charAt(i++)));// 當前數位對應的數值進棧
char op = expression.charAt(i); // 讀取下一個字元
while (Character.isDigit(op)) {// 只要後續還有緊鄰的數字
stk.push(stk.pop() * 10 + CharToFloat(op));// 彈出原運算元並追加新數位後,新數值重新入棧
op = expression.charAt(++i);// 下一個字元
}
if ('.' != op)
return i;// 如果最後一個數字後面不是小數點,說明解析完成,則返回當前位置
op = expression.charAt(++i);
float fraction = 1;
while (Character.isDigit(op)) {
stk.push(stk.pop() + CharToFloat(op) * (fraction /= 10));
op = expression.charAt(++i);
}
if ('.' == op)
throw new Exception("ERROR");// 如果還有小數點則錯誤
return i;// 返回當前解析字元的位置
}
/**
* 根據運算子獲取在優先順序表中的秩(RANK)
* @param op
* @return
* @throws Exception
*/
private int getOperRank(char op) throws Exception {
switch (op) {
case '+':
return ADD;
case '-':
return SUB;
case '*':
return MUL;
case '/':
return DIV;
case '%':
return MOD;
case '^':
return POW;
case '!':
return FAC;
case '(':
return L_P;
case ')':
return R_P;
case '#':
return EOF;
default:
throw new Exception("ERROR");
}
}
/**
* 比較棧頂和當前字元的優先順序
* @param peekOptr 棧頂運算子
* @param currenOptr 當前掃描到的運算子
* @return 優先順序表中的資料項
* @throws Exception
*/
private Character orderBetween(Character peekOptr, char currenOptr) throws Exception {
return PRI[getOperRank(peekOptr)][getOperRank(currenOptr)];
}
/**
* 剔除字串之間的空格
* @param str
* @return
*/
public String removeSpace(String str) {
char[] chs = str.toCharArray();
StringBuffer newStr = new StringBuffer();
int i = 0;
while (i < str.length()) {
if (' ' != chs[i]) {
newStr.append(chs[i]);
}
i++;
}
return newStr.toString();
}
// test
public static void main(String[] args) {
StackCalculator sc = new StackCalculator();
String s = "(1 + 2^3!-4)*(5!-(6-( 7-(89-0!))))#";// 2013
StringBuffer rpn = new StringBuffer();
try {
System.out.println("結果:" + sc.calcExpression(s, rpn));
System.out.println("逆波蘭表示式:" + rpn);
System.out.println("\n計算逆波蘭表示式:");
sc.calcExpressionRpn(rpn.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
2、Calculator.java
package calc;
public class Calculator {
/**
* 一元運算子
*
* @param a
* 運算元
* @param op
* 操作符
* @return 結果浮點型
*/
public float calcu(float n, char op) throws Exception {
return fact(n);// 這裡只有階乘一個一元運算子
}
/**
* 二元運算子
*
* @param a
* 運算元1
* @param op
* 操作符
* @param b
* 運算元2
* @return 結果浮點型
* @throws Exception
*/
public float calcu(float a, char op, float b) throws Exception {
switch (op) {
case '+':
return a + b;
case '-':
return a - b;
case '*':
return a * b;
case '/':
return div(a, b);
case '%':
return mod(a, b);
case '^':
return (float) Math.pow(a, b);
default:
throw new Exception("ERROR");
}
}
private float div(float a, float b) throws Exception {
if (b == 0)
throw new Exception("除數不能為0!");
return a / b;
}
// 取餘
private float mod(float a, float b) throws Exception {
if (b == 0)
throw new Exception("除數不能為0!");
return a % b;
}
// 階乘 n!(n<=20)
private float fact(float n) throws Exception {
if (n < 0)
throw new Exception("階乘運算數不能為負數!");
if (n > 34)
throw new Exception("階乘數不能超過34,否則越界!");// 可以考慮改進以使計算更大數值的階乘
if (n == 0)
return 1;// 0!=1
int num = (int) n;
if (num == n) {// n為整數
float result = 1;
while (num > 0) {
result *= num--;
}
return result;
} else
throw new Exception("階乘運算數必須為整數!");
}
}
二、視訊資源
如果想要弄明白關於表示式求值的實現細節和邏輯,建議參考鄧俊輝的資料結構MOOC課程(b站上有),下面給出專門講解表示式求值的視訊的百度雲連結:https://pan.baidu.com/s/1vlkCpHTVjvzSalgrewgYoA 密碼:ixo6