java之基于RPN表达式的计算器

常见到计算器算法中都会用到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 numbers = new Stack(); // 操作数栈
	private Stack> 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 getStack(Stack stk) {
		List getStk = new ArrayList<>();
		for (Double x : stk) {
			getStk.add(x);
		}
		return getStk;
	}

	/**
	 * 该方法将栈中的数据显示出来,从底层开始
	 * 
	 * @param stk
	 */
	public void displayStack(Stack 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 stk1, Stack> 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 stk1, Stack> 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 stk1, Stack> stk2, String opt) throws Exception {
		switch (opt) {
		case "undo": // undo的情况
			while (!stk1.empty()) { // 操作数栈清空
				stk1.pop();
			}
			if (!stk2.empty()) { // 如果日志栈不为空,将栈顶数据弹出
				stk2.pop();
				if (!stk2.empty()) { // 弹出数据后,日志栈不为空,将现在的栈顶数据压入操作数栈
					List 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 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 getStack(Stack stk) {
		List getStk = new ArrayList<>();
		for (Double x : stk) {
			getStk.add(x);
		}
		return getStk;
	}
}

你可能感兴趣的:(Java,Stack栈的应用,RPN)