前言:
逆波兰表示法(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 (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这个概念,在写这些代码的时候感觉很嗨!