Java 實現RPN(逆波蘭)計算器

前言:

逆波蘭表示法(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這個概念,在寫這些代碼的時候感覺很嗨!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章