Java 实现RPN(逆波兰)计算器

32 篇文章 1 订阅
19 篇文章 0 订阅

前言:

逆波兰表示法(Reverse Polish notation,RPN,或逆波兰记法),是一种是由波兰数学家扬·武卡谢维奇1920年引入的数学表达式方式,在逆波兰记法中,所有操作符置于操作数的后面,因此也被称为后缀表示法。(百度百科)

示例:

下表中逆波兰表达式的值输入是以空格为分隔符。
左右等价

常见表达式(中缀表达式)逆波兰表达式(RPN 后缀表达式)
1 + 21 2 +
1 + 2 * 31 2 3 * +
(1 + 2)* ( 3 / 3)1 2 + 3 3 / *
z = x + yz 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 (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这个概念,在写这些代码的时候感觉很嗨!

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值