前言:
逆波蘭表示法(Reverse Polish notation,RPN,或逆波蘭記法),是一種是由波蘭數學家揚·武卡謝維奇1920年引入的數學表達式方式,在逆波蘭記法中,所有操作符置於操作數的後面,因此也被稱爲後綴表示法。(百度百科)
示例:
下表中逆波蘭表達式的值輸入是以空格爲分隔符。
左右等價
常見表達式(中綴表達式) | 逆波蘭表達式(RPN 後綴表達式) |
---|---|
1 + 2 | 1 2 + |
1 + 2 * 3 | 1 2 3 * + |
(1 + 2)* ( 3 / 3) | 1 2 + 3 3 / * |
z = x + y | z x y + = |
用途:
逆波蘭表達式可以將複雜的表達式轉換爲普通的表達式進行運算;只用簡單的入棧和出棧操作,就可以搞定任何普通表達式的運算。所以也叫做金融計算器,財務會使用這種計算器,因爲財務的計算更復雜、多變。
(1)當有操作符時就計算,因此,表達式並不是從右至左整體計算而是每次由中心向外計算一部分,這樣在複雜運算中就很少導致操作符錯誤。
(2)堆棧自動記錄中間結果,這就是爲什麼逆波蘭計算器能容易對任意複雜的表達式求值。與普通科學計算器不同,它對表達式的複雜性沒有限制。
(3)逆波蘭表達式中不需要括號,用戶只需按照表達式順序求值,讓堆棧自動記錄中間結果;同樣的,也不需要指定操作符的優先級。
(4)逆波蘭計算器中,沒有“等號”鍵用於開始計算。
(5)逆波蘭計算器需要“確認”鍵用於區分兩個相鄰的操作數。
(6)機器狀態永遠是一個堆棧狀態,堆棧裏是需要運算的操作數,棧內不會有操作符。
(以上百度百科)
Java實現:
這裏僅實現逆波蘭表達式的輸入與輸出,沒有做表達式轉換(中綴表達式轉後綴表達式)。
一、Main類,測試類:
package com.jm.rpn;
import java.util.Scanner;
/**
* 測試類
*
* @author jm
*
*/
public class Main {
// 批測試用的表達式
static String[] testExpression = new String[] { "5 2", "2 sqrt", "clear 9 sqrt", "5 2 -", "-", "clear", "5 4 3 2",
"undo undo *", "5 *", "undo", "7 12 2 /", "*", "4 /", "1 2 3 4 5 *", "clear 3 4 -", "1 2 3 4 5", "* * * *",
"1 2 3 * 5 + * * 6 5" };
@SuppressWarnings("resource")
public static void main(String args[]) {
RpnCalculator cl = new RpnCalculator();
boolean flag = false;// 批測開關
try {
while (true) {
// 使用批量表達式測試
if (flag) {
for (String d : testExpression) {
System.out.println("Input expression: " + d);
cl.doRPN(d);
}
flag = false;
}
// 手動輸入表達式測試
System.out.println("Please enter an expression:");
Scanner scanner = new Scanner(System.in);
String rpn = scanner.nextLine();
System.out.println("Input expression: " + rpn);
cl.doRPN(rpn);
}
} catch (ExpressionFormatException e) {
e.printStackTrace();
}
}
}
二、計算類:
package com.jm.rpn;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/**
* PRN(逆波蘭)計算器類
*
* @author jm
* @Date
*/
public class RpnCalculator {
// 記錄當前可以操作的數的棧
private Stack<Double> numbers = new Stack<Double>();
// 記錄棧數據的操作日誌
private Stack<List<Double>> logList = new Stack<>();
/**
* 計算入口
* @param expression
* @throws Exception
*/
public void doRPN(String expression) throws ExpressionFormatException {
// 通過空格分隔切分輸入的表達式
String[] paramArr = expression.split(" ");
int paramArrLenth = paramArr.length;
CalculatorUtils cu = new CalculatorUtils();
for (int i = 0; i < paramArrLenth; i++) {
String operator = paramArr[i];
// 判斷是數字則入棧,記錄棧日誌
if (cu.isNumber(operator)) {
numbers.push(Double.valueOf(operator));
addLogList(numbers, logList);
continue;
}
// 判斷是加減乘除操作,則進行相應的計算,計算完成記錄日誌
if (operator.equals("+") || operator.equals("-") || operator.equals("*") || operator.equals("/")) {
if (numbers.size() > 1) {
cu.calculate(numbers, operator);
addLogList(numbers, logList);
} else {
System.out.print("operator " + operator + " (position: " + (i * 2 + 1) + "): insufficient parameters ");// 判斷如果操作數不足則退出循環,提示位置; (i*2+1)位置需要加上空格
break;
}
} else if ("sqrt".equals(operator)) {// 判斷如果是開平方,則進入
if (numbers.size() > 0) {
cu.sqrt(numbers, operator);
addLogList(numbers, logList);
} else {
System.out.print("operator " + operator + " (position: " + (i * 2 + 1) + "): insufficient parameters ");
break;
}
} else if ("undo".equals(operator)) {// 判斷如果是回退,則進入
cu.undo(numbers, logList, operator);
} else if ("clear".equals(operator)) {// 判斷是清除,則進入
cu.clear(numbers, logList, operator);
} else {
throw new ExpressionFormatException("輸入的RPN表達式錯誤!");
}
}
printStack(numbers);
//System.out.println("log-stack: " + logList);
}
/**
* 將操作數棧的裏數據 記錄的日誌棧中
* @param numbers
* @param logList
*/
private void addLogList(Stack<Double> numbers, Stack<List<Double>> logList) {
List<Double> numbersList = new ArrayList<>();
for (Double d : numbers) {
numbersList.add(d);
}
logList.push(numbersList);
}
/**
* 打印棧數據
* @param numbers
*/
private void printStack(Stack<Double> numbers) {
System.out.print("stack: ");
if(!numbers.isEmpty()) {
for(double d : numbers) {
System.out.print(numberFormat(d) + " ");
}
}
System.out.println();
}
/**
* 精度至少爲15位小數,但是顯示10位小數
* @param number
* @return
*/
private String numberFormat(double number) {
DecimalFormat numFormat = new DecimalFormat("##########.##########");
numFormat.setRoundingMode(RoundingMode.DOWN);// 捨去末尾
String output = numFormat.format(number);
return output;
}
}
三、計算工具類:
package com.jm.rpn;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/**
* RPN計算器工具類
*
* @author jm
* @Date
*
*/
public class CalculatorUtils {
/**
* 判斷是否是數字
*
* @param number
* @return
*/
public boolean isNumber(String number) {
try {
Double.valueOf(number);
} catch (Exception e) {
return false;
}
return true;
}
/**
* 進行加減乘除計算
*
* @param numbers
* @param operator
* @throws Exception
*/
void calculate(Stack<Double> numbers, String operator) throws ExpressionFormatException {
// 按照從左向右計算,則先彈出棧頂的數據是:被除數。
double num2 = numbers.pop();
double num1 = numbers.pop();
switch (operator) {
case "+":
numbers.push(num1 + num2);
break;
case "-":
numbers.push(num1 - num2);
break;
case "*":
numbers.push(num1 * num2);
break;
case "/":
if (num2 == 0) {
throw new ExpressionFormatException("被除數不能爲0!");
}
numbers.push(num1 / num2);
break;
default:
throw new ExpressionFormatException("RPN表達式錯誤!");
}
}
/**
* 進行開平方計算
*
* @param numbers
* @param operator
* @throws Exception
*/
void sqrt(Stack<Double> numbers, String operator) throws ExpressionFormatException {
double num = numbers.pop();
if ("sqrt".equals(operator)) {
if (num < 0) {
throw new ExpressionFormatException("負數不能開平方!");
}
double sqrtNum = (double) Math.sqrt(num);
numbers.push(sqrtNum);
}
}
/**
* 進行回退操作
*
* @param numbers
* @param logList
* @param operator
* @throws Exception
*/
void undo(Stack<Double> numbers, Stack<List<Double>> logList, String operator) throws ExpressionFormatException {
// 將棧內數據清空
while (!numbers.isEmpty()) {
numbers.pop();
}
// 將上一步的操作數據存入操作數棧中
if (!logList.isEmpty()) {
logList.pop();// 彈出計算結果的日誌
List<Double> numbersLog = logList.peek();// 獲取計算前的棧數據
for (Double d : numbersLog) {
if (d != null) {
numbers.push(d);
}
}
}
}
/**
* 進行清理操作
*
* @param numbers
* @param logList
* @param operator
* @throws Exception
*/
void clear(Stack<Double> numbers, Stack<List<Double>> logList, String operator) throws ExpressionFormatException {
// 清理棧裏的數據
while (!numbers.isEmpty()) {
numbers.pop();
}
// 清理動作在日誌棧裏存入null,用於回退時區分
List<Double> list = new ArrayList<>();
list.add(null);
logList.push(list);
}
}
四、異常類:
package com.jm.rpn;
/**
* RPN表達式格式化異常
* @author jm
*
*/
public class ExpressionFormatException extends Exception {
private static final long serialVersionUID = 1L;
public ExpressionFormatException(String message) {
super(message);
}
}
特別鳴謝謀司,讓我知道RPN這個概念,在寫這些代碼的時候感覺很嗨!