如何实现基于Java函数的逆波兰表达式运算
逆波兰表达式是一种运算符放在操作数后面的表示方式,如2 3 +,其计算方式为将2和3入栈,执行+运算符后,弹出2和3,计算2+3=5,将结果5入栈。逆波兰表达式的优点是避免了括号匹配的问题,减少了运算符的优先级判断和括号的处理,因此在实现计算器等应用时比较常用。本文主要介绍如何在Java中实现基于函数的逆波兰表达式运算。
1. 定义函数类
首先需要定义一个函数类,用来存储每个函数的名称和参数个数。代码如下:
public class Function {
private String name; //函数名称
private int numParams; //参数个数
public Function(String name, int numParams) {
this.name = name;
this.numParams = numParams;
}
public int getNumParams() {
return numParams;
}
public String getName() {
return name;
}
}
2. 定义函数列表
接着需要定义一个函数列表,将每个函数名称和参数个数存储在其中。代码如下:
public class FunctionList {
private static Map<String, Function> functions;
static {
functions = new HashMap<>();
functions.put("sin", new Function("sin", 1));
functions.put("cos", new Function("cos", 1));
functions.put("tan", new Function("tan", 1));
functions.put("sqrt", new Function("sqrt", 1));
functions.put("log", new Function("log", 2));
}
public static Function getFunction(String name) {
return functions.get(name);
}
}
以上代码定义了五个函数:sin、cos、tan、sqrt和log,分别对应了三角函数、平方根和对数运算。
3. 定义操作符枚举
接着需要定义一个操作符枚举类,用来存储各种操作符的运算符号和优先级,代码如下:
public enum Operator {
ADD("+", 1),
SUBTRACT("-", 1),
MULTIPLY("*", 2),
DIVIDE("/", 2);
private String symbol; //运算符号
private int priority; //运算优先级
Operator(String symbol, int priority) {
this.symbol = symbol;
this.priority = priority;
}
public String getSymbol() {
return symbol;
}
public int getPriority() {
return priority;
}
}
以上代码定义了四个操作符:加、减、乘、除,分别对应了基本的算术运算。
4. 定义逆波兰表达式类
接着定义逆波兰表达式类,用来存储表达式的各个元素,包括数字、操作符和函数。代码如下:
public class RPNToken {
private String value; //元素值
private TokenType tokenType; //元素类型
public RPNToken(String value, TokenType tokenType) {
this.value = value;
this.tokenType = tokenType;
}
public String getValue() {
return value;
}
public TokenType getTokenType() {
return tokenType;
}
public enum TokenType {
NUMBER,
OPERATOR,
FUNCTION
}
}
5. 实现逆波兰表达式解析
逆波兰表达式的解析主要分为两步:中缀表达式转化为后缀表达式和后缀表达式的计算。
中缀表达式转化为后缀表达式的思路是通过栈来实现的,对于每个运算符和括号,如果是左括号或者优先级低于栈顶的符号,则直接入栈,如果是优先级高于栈顶的符号,则将栈中的所有符号弹出直到遇到左括号或者优先级低于该符号的符号为止,然后将该符号入栈。如果是右括号,则将栈中的符号弹出直到遇到左括号为止。最后将剩余的符号依次弹出加入后缀表达式中。如下所示:
public static List<RPNToken> toRPN(List<RPNToken> infixTokens) {
List<RPNToken> postfixTokens = new ArrayList<>();
Stack<String> stack = new Stack<>();
for (RPNToken token : infixTokens) {
String value = token.getValue();
switch (token.getTokenType()) {
case NUMBER:
postfixTokens.add(token);
break;
case FUNCTION:
stack.push(value);
break;
case OPERATOR:
Operator op = Operator.valueOf(value);
if (stack.isEmpty() || op == Operator.MULTIPLY || op == Operator.DIVIDE || stack.lastElement().equals("(")) {
stack.push(value);
} else {
while (!stack.isEmpty() && !stack.lastElement().equals("(") && (op == Operator.ADD
|| op == Operator.SUBTRACT || op.getPriority() <= Operator.valueOf(stack.lastElement()).getPriority())) {
postfixTokens.add(new RPNToken(stack.pop(), RPNToken.TokenType.OPERATOR));
}
stack.push(value);
}
break;
default:
throw new IllegalArgumentException("Invalid token type: " + token.getTokenType());
}
}
while (!stack.isEmpty()) {
postfixTokens.add(new RPNToken(stack.pop(), RPNToken.TokenType.OPERATOR));
}
return postfixTokens;
}
逆波兰表达式的计算则是通过栈来实现的,对于每个元素,如果是数字则直接入栈,如果是操作符则弹出栈顶的两个元素,执行相应的计算后将结果入栈,如果是函数则弹出函数需要的参数个数个元素,执行相应的计算后将结果入栈。如下所示:
public static double calculate(List<RPNToken> tokens) {
Stack<Double> stack = new Stack<>();
for (RPNToken token : tokens) {
String value = token.getValue();
switch (token.getTokenType()) {
case NUMBER:
stack.push(Double.valueOf(value));
break;
case OPERATOR:
Operator op = Operator.valueOf(value);
double right = stack.pop();
double left = stack.pop();
switch (op) {
case ADD:
stack.push(left + right);
break;
case SUBTRACT:
stack.push(left - right);
break;
case MULTIPLY:
stack.push(left * right);
break;
case DIVIDE:
stack.push(left / right);
break;
}
break;
case FUNCTION:
Function func = FunctionList.getFunction(value);
List<Double> params = new ArrayList<>();
for (int i = 0; i < func.getNumParams(); i++) {
params.add(stack.pop());
}
Collections.reverse(params);
switch (func.getName()) {
case "sin":
stack.push(Math.sin(params.get(0)));
break;
case "cos":
stack.push(Math.cos(params.get(0)));
break;
case "tan":
stack.push(Math.tan(params.get(0)));
break;
case "sqrt":
stack.push(Math.sqrt(params.get(0)));
break;
case "log":
stack.push(Math.log(params.get(0)) / Math.log(params.get(1)));
break;
}
break;
default:
throw new IllegalArgumentException("Invalid token type: " + token.getTokenType());
}
}
return stack.pop();
}
6. 测试
最后编写一个简单的测试类来验证逆波兰表达式的计算结果,代码如下:
public class RPNEvalTest {
public static void main(String[] args) {
String expr = "sin(PI/2)*10-(2+3)*4/5";
List<RPNToken> infixTokens = InfixToRPN.parse(expr);
System.out.println("Infix: " + infixTokens);
List<RPNToken> postfixTokens = InfixToRPN.toRPN(infixTokens);
System.out.println("Postfix: " + postfixTokens);
double result = InfixToRPN.calculate(postfixTokens);
System.out.println("Result: " + result);
}
}
