基于Shunting Yard算法的科学计算器开发技术咨询
科学计算器问题解决方案
问题1:相同优先级运算未遵循从左到右顺序
问题原因
当前实现的hasPrecedence方法仅区分了乘除与加减的优先级差异,未处理相同优先级运算符的左结合规则;同时运算符入栈逻辑仅弹出一次高优先级运算符,未循环处理所有符合条件的栈顶运算符,导致相同优先级运算顺序颠倒(如8/2/2会计算为8/(2/2)=8,而非正确的(8/2)/2=2)。
修复方案
- 新增优先级判断方法,明确各运算符的优先级等级
- 修改
hasPrecedence方法,支持左结合规则(相同优先级时,栈顶运算符先执行) - 调整运算符入栈逻辑,循环弹出所有优先级高于或等于当前运算符的栈顶元素
修改后的关键代码
// 新增:获取运算符优先级 private static int getPrecedence(char op) { return switch(op) { case '+', '-' -> 1; case 'x', '/' -> 2; case '(' -> 0; default -> -1; }; } // 新增:判断运算符是否为左结合 private static boolean isLeftAssociative(char op) { return op == '+' || op == '-' || op == 'x' || op == '/'; } // 修改:优先级判断方法 public static boolean hasPrecedence(char op1, char op2) { int p1 = getPrecedence(op1); int p2 = getPrecedence(op2); // 左结合:优先级相同则栈顶运算符先执行 return p1 > p2 || (p1 == p2 && isLeftAssociative(op1)); } // 修改:运算符入栈逻辑 else if (tokens[i] == 'x' || tokens[i] == '-' || tokens[i] == '/' || tokens[i] == '+') { char currentOp = tokens[i]; // 循环弹出所有符合优先级条件的栈顶运算符 while (!ops.isEmpty() && ops.peek() != '(' && hasPrecedence((char) ops.peek(), currentOp)) { values.add(ops.pop()); } ops.push(currentOp); }
问题2:科学函数的集成与优先级遵循
问题原因
当前基于单个字符的匹配方式无法识别多字符函数名,且未将科学函数纳入BODMAS优先级体系(函数优先级应高于乘除),同时缺少函数调用的计算逻辑。
解决方案
- 通过连续字母收集函数名,替代逐个字符匹配
- 将函数视为高优先级的前缀运算符,调整Shunting Yard算法的优先级判断与栈处理逻辑
- 新增函数计算方法,在后缀表达式求值时处理函数调用
修改后的关键代码
首先,将栈和队列泛型化以支持字符串类型的函数名:
Queue<Object> values = new LinkedList<>(); Stack<Object> ops = new Stack<>();
然后添加函数识别逻辑:
// 在数字处理分支后添加函数识别 else if (Character.isLetter(tokens[i])) { StringBuffer funcBuf = new StringBuffer(); // 收集连续字母作为函数名 while (i < tokens.length && Character.isLetter(tokens[i])) { funcBuf.append(tokens[i]); i++; } i--; // 修正循环后索引偏移 ops.push(funcBuf.toString()); }
修改优先级判断方法以支持函数:
private static int getPrecedence(Object op) { if (op instanceof Character) { char c = (Character) op; return switch(c) { case '+', '-' -> 1; case 'x', '/' -> 2; case '(' -> 0; default -> -1; }; } else if (op instanceof String) { // 科学函数优先级高于乘除 return 3; } return -1; } public static boolean hasPrecedence(Object op1, Object op2) { int p1 = getPrecedence(op1); int p2 = getPrecedence(op2); // 函数为右结合(如sin(cos(x))先算cos),优先级相同时不弹出栈顶函数 if (op1 instanceof String) { return p1 > p2; } else { // 普通运算符左结合 char op = (Character) op1; return p1 > p2 || (p1 == p2 && isLeftAssociative(op)); } }
调整右括号处理逻辑,弹出函数到输出队列:
else if (tokens[i] == ')') { while (!ops.isEmpty() && !((ops.peek() instanceof Character) && (Character) ops.peek() == '(')) { values.add(ops.pop()); } ops.pop(); // 弹出左括号 // 若栈顶是函数,加入输出队列 if (!ops.isEmpty() && ops.peek() instanceof String) { values.add(ops.pop()); } }
新增函数计算方法并修改后缀表达式求值逻辑:
// 新增:科学函数计算 public static double applyFunction(String funcName, double num) { return switch(funcName.toLowerCase()) { case "sin" -> Math.sin(Math.toRadians(num)); // 若输入是角度,转弧度;否则直接用num case "cos" -> Math.cos(Math.toRadians(num)); case "tan" -> Math.tan(Math.toRadians(num)); case "sqrt" -> Math.sqrt(num); default -> throw new IllegalArgumentException("未知函数:" + funcName); }; } // 修改:后缀表达式求值 Stack<Double> numStack = new Stack<>(); while (!values.isEmpty()) { Object val = values.poll(); if (val instanceof Character) { char v = (Character) val; double num2 = numStack.pop(); double num1 = numStack.pop(); double ans = applyOp(v, num1, num2); numStack.push(ans); } else if (val instanceof String) { // 处理函数调用:弹出一个参数计算 double num = numStack.pop(); double ans = applyFunction((String) val, num); numStack.push(ans); } else { numStack.push((Double) val); } }
补充说明
- 函数优先级设为3(高于乘除的2),确保
3*sin(45)先计算sin(45)再做乘法 - 函数采用右结合逻辑,嵌套函数(如
sin(cos(30)))会先计算内层的cos(30),符合数学规则 - 若用户输入的是角度而非弧度,需在
applyFunction中通过Math.toRadians()转换,反之则移除该转换
内容的提问来源于stack exchange,提问作者Komuthu Fernando
相关产品推荐
相关产品推荐

