常见到计算器算法中都会用到RPN,PRN是什么呢?英文名字 Reverse Polish Notation,中文译作逆波兰表达式,即后缀表达式。一般用户输入的是中缀表达式,而程序计算执行的都是后缀表达式。下面是对逆波兰表达式计算的简单算法模型。
功能要求:操作符+,-,*,/ ,undo(撤销), clear(清空)。
分成两个类CalForResult和CalRules,代码如下,程序中有详细的注解,不在此赘述。
package myCalculator;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.Stack;
public class CalForResult {
private Stack<Double> numbers = new Stack<Double>(); // 操作数栈
private Stack<List<Double>> numlogs = new Stack<>(); // 操作数栈的日志栈,用来存放操作数栈的历史记录
/**
* 该方法对RPN表达式进行计算
*
* @param rpn 为用户输入的RPN表达式
*/
public void calRpnExpression(String rpn) throws Exception {
String[] arpn = rpn.split(" "); // 以空格来分割rpn字符串成为字符串数组
int i = 0;
int apLength = arpn.length; // 获取表达式字符串数组的长度
while (i < apLength) { // 该循环开始操作符计算
CalRules cr = new CalRules(); // 创建计算法则类的对象,以便后面的引用
int n = numbers.size(); // 获取栈的长度
if (strToDigit(arpn[i]) != null) { // 字符串是操作数,则直接入栈
numbers.push(strToDigit(arpn[i]));
numlogs.push(getStack(numbers)); // 日志栈记录操作数栈的数据变化
} else { // 字符串不是操作数,则要判断是哪种操作符
String opt = arpn[i];
if ("undo".equals(opt) || "clear".equals(opt)) { // 操作符是功能符undo或clear
cr.funcRules(numbers, numlogs, opt);
} else if ("sqrt".equals(opt)) { // 操作符是一元运算符sqrt
if (n > 0) { // 如果栈中有操作数,则进行单目运算
cr.unaryOptRules(numbers, numlogs, opt);
} else { // 栈中没有操作数,输出提示信息,并跳出循环
System.out.print("operator" + opt + "(position:" + (2 * i - 1) + "):insufficient parameters ");
break;
}
} else if ("+".equals(opt) || "-".equals(opt) || "*".equals(opt) || "/".equals(opt)) { // 操作符是二元运算符
if (n > 1) { // 栈的操作数大于等于2,则进行双目运算
cr.binaryOptRules(numbers, numlogs, opt);
} else { // 栈中没有操作数,输出提示信息,并跳出循环
System.out.print("operator" + opt + "(position:" + (2 * i + 1) + "):insufficient parameters ");
break;
}
} else {
throw new Exception("输入的RPN表达式不合法!");
}
}
i++;
}
displayStack(numbers);
}
/**
* 该方法将字符串转换为数字类型Double
*
* @param str
*/
private Double strToDigit(String str) {
try {
double num = Double.valueOf(str);
return num;
} catch (Exception e) { // 出现异常,则str字符串不是操作数
return null;
}
}
/**
* 该方法获取栈中数据,将其存在List集合中
*
* @param stk
*/
public List<Double> getStack(Stack<Double> stk) {
List<Double> getStk = new ArrayList<>();
for (Double x : stk) {
getStk.add(x);
}
return getStk;
}
/**
* 该方法将栈中的数据显示出来,从底层开始
*
* @param stk
*/
public void displayStack(Stack<Double> stk) {
if (stk.size() != 0) {
System.out.print("stack:");
for (Double x : stk) {
System.out.print(outputFormat(x) + " ");
}
} else {
System.out.println("stack:");
}
System.out.println();
}
/**
* 该方法设置运算结果的显示格式,最多显示10位精度
*
* @param value 运算结果
*/
public String outputFormat(double value) {
DecimalFormat numformat = new DecimalFormat("##########.##########");
String output = numformat.format(value);
return output;
}
public static void main(String[] args) {
CalForResult cf = new CalForResult();
try {
while (true) {
System.out.println("请输入逆波兰表达式:");
Scanner scan = new Scanner(System.in);
String rpn = scan.nextLine();
cf.calRpnExpression(rpn);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package myCalculator;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class CalRules {
/**
* 该方法封装了一元运算符计算法则,可在该方法中增加操作符功能
*
* @param stk1 操作符栈
* @param stk1 日志栈
* @param opt 操作符
*/
public void unaryOptRules(Stack<Double> stk1, Stack<List<Double>> stk2, String opt) throws Exception {
double num = stk1.pop();
switch (opt) {
case "sqrt": // 操作符为sqrt
stk1.push(sqrt(num));
stk2.push(getStack(stk1));
break;
default:
throw new Exception("ERROR");
}
}
/**
* 该方法封装了二元运算符计算法则,可在此方法中增加更多操作符,实现扩展。
*
* @param stk1 操作符栈
* @param stk1 日志栈
* @param opt 操作符
*/
public void binaryOptRules(Stack<Double> stk1, Stack<List<Double>> stk2, String opt) throws Exception {
double num2 = stk1.pop(); // 取出操作数栈顶数值
double num1 = stk1.pop(); // 取出操作数栈顶数值
switch (opt) {
case "+":
stk1.push(num1 + num2); // 计算并将结果入栈
stk2.push(getStack(stk1)); // 同时日志栈记录操作数栈的数据
break;
case "-":
stk1.push(num1 - num2);
stk2.push(getStack(stk1));
break;
case "*":
stk1.push(num1 * num2);
stk2.push(getStack(stk1));
break;
case "/":
stk1.push(div(num1, num2));
stk2.push(getStack(stk1));
break;
default:
throw new Exception("ERROR");
}
}
/**
* 该方法封装了功能操作符undo、clear的计算法则
*
* @param stk1 操作数栈
* @param stk2 日志栈
* @param opt 操作符
*/
public void funcRules(Stack<Double> stk1, Stack<List<Double>> stk2, String opt) throws Exception {
switch (opt) {
case "undo": // undo的情况
while (!stk1.empty()) { // 操作数栈清空
stk1.pop();
}
if (!stk2.empty()) { // 如果日志栈不为空,将栈顶数据弹出
stk2.pop();
if (!stk2.empty()) { // 弹出数据后,日志栈不为空,将现在的栈顶数据压入操作数栈
List<Double> list1 = stk2.peek(); // 读取日志栈顶数据,并将其存放到list1集合中
for (int i = 0; i < list1.size(); i++) { // 将现在的栈顶数据压入操作数栈
if (list1.get(i) != null) {
stk1.push(list1.get(i));
}
}
}
}
break;
case "clear": // clear的情况
while (!stk1.empty()) { // 清空操作数栈
stk1.pop();
}
List<Double> list2 = new ArrayList<>(); // 将null压入日志栈,以便执行undo时可以区别
list2.add(null);
stk2.push(list2);
break;
default:
throw new Exception("ERROR");
}
}
/**
* 除法计算法则
*
* @param a 操作数1
* @param b 操作数2
*/
private double div(double a, double b) throws Exception {
if (b == 0) {
throw new Exception("除数不能为0!");
}
return a / b;
}
/**
* 开平方计算法则
*
* @param f
*/
private double sqrt(double f) throws Exception {
if (f < 0) {
throw new Exception("不能对负数开平方!");
}
double a = (double) Math.sqrt(f);
return a;
}
/**
* 该方法获取栈中数据,将其存在List集合中
*
* @param stk
*/
public List<Double> getStack(Stack<Double> stk) {
List<Double> getStk = new ArrayList<>();
for (Double x : stk) {
getStk.add(x);
}
return getStk;
}
}