今天的习题是
1.用键盘接收一个数学公式字符串,包括数字、括号、加减乘除四个运算符号。
2.解析字符串,对数学公式进行计算。
在网上搜索到了篇博客,讲述解题方法和代码
但是这个没有关于小数的运算,在修改过程中也遇到了挺多问题。
里面讲述了解题思路,我大概梳理一下:
- 先把你输入的数学运算公式(即中缀表达式)存到一个list中,方便转换为后缀表达式
- 再把存在list中的中缀表达式进行遍历,进行变为后缀表达式的转换,思路如下:
1、建一个stack栈用于存操作符,建一个list用于存储后缀表达式
2、遍历第一个list的中缀表达式,遇到数字,就放到新list中;遇到操作符的话,要分情况讨论:
a)当运算符栈为空或者栈顶操作符的优先级小于当前遍历到的运算符优先级时(如+和-的优先级低于 * 和 /),直接入栈
b)当运算符栈不为空时且栈顶操作符的优先级大于或等于当前遍历到的运算符优先级时,循环执行出栈操作并加入list中,直到遇到优先级小于当前运算符的元素为止。循环执行完后再将当前运算符压栈。
3、遍历到括号时:如果是左括号,直接压栈,如果遇到右括号,循环出栈,直到遇到左括号为止。注:只有遇到右括号,左括号才会出栈
4、遍历完第一个list后,把stack中剩余的依次弹出到新list中,就成了后缀表达式。 - 此时新的list里面存有转换好的后缀表达式,接下来就是对后缀表达式求值了:
1、建一个新栈
2、遍历后缀表达式,遇到数字直接压栈;遇到操作符,则将栈顶和次栈顶的两个数进行该操作符的运算。并将结果压入栈中,最后得到仅剩的一个栈元素即为答案。
下面先po出来完整代码(含小数的),然后再细讲在哪里对代码进行了包含小数的操作。
package calculation;
/*import java.util.Scanner;
import java.util.List;
import java.util.ArrayList;
import java.util.Stack;*/
import java.util.*;
public class calculation {
private static List<String> parseToSuffixExpression(List<String> expressionList) {
//创建一个栈用于保存操作符
Stack<String> opStack = new Stack<>();
//创建一个list用于保存后缀表达式
List<String> suffixList = new ArrayList<>();
for(String item : expressionList){
//得到数或操作符
if(isOperator(item)){
//是操作符 判断操作符栈是否为空
if(opStack.isEmpty() || "(".equals(opStack.peek()) || priority(item) > priority(opStack.peek())){
//为空或者栈顶元素为左括号或者当前操作符大于栈顶操作符直接压栈
opStack.push(item);
}else {
//否则将栈中元素出栈如队,直到遇到大于当前操作符或者遇到左括号时
while (!opStack.isEmpty() && !"(".equals(opStack.peek())){
if(priority(item) <= priority(opStack.peek())){
suffixList.add(opStack.pop());
}
}
//当前操作符压栈
opStack.push(item);
}
}else if(isNumber(item)){
//是数字则直接入队
suffixList.add(item);
}else if("(".equals(item)){
//是左括号,压栈
opStack.push(item);
}else if(")".equals(item)){
//是右括号 ,将栈中元素弹出入队,直到遇到左括号,左括号出栈,但不入队
while (!opStack.isEmpty()){
if("(".equals(opStack.peek())){
opStack.pop();
break;
}else {
suffixList.add(opStack.pop());
}
}
}else if(".".equals(item)){
//System.out.print('a');
suffixList.add(item);
}else {
throw new RuntimeException("有非法字符!");
}
}
//循环完毕,如果操作符栈中元素不为空,将栈中元素出栈入队
while (!opStack.isEmpty()){
suffixList.add(opStack.pop());
}
return suffixList;
}
/**
* 判断字符串是否为操作符
* @param op
* @return
*/
public static boolean isOperator(String op){
return op.equals("+") || op.equals("-") || op.equals("*") || op.equals("/");
}
/**
* 判断是否为数字
* @param num
* @return
*/
public static boolean isNumber(String num){
return num.matches("^([0-9]{1,}[.][0-9]*)$") || num.matches("^([0-9]{1,})$");
}
/**
* 获取操作符的优先级
* @param op
* @return
*/
public static int priority(String op){
if(op.equals("*") || op.equals("/")){
return 1;
}else if(op.equals("+") || op.equals("-")){
return 0;
}
return -1;
}
/**
* 将表达式转为list
* @param expression
* @return
*/
private static List<String> expressionToList(String expression) {
int index = 0;
List<String> list = new ArrayList<>();
do{
char ch = expression.charAt(index);
if(ch!=46 && (ch <= 47 || ch >= 58)){
//是操作符,直接添加至list中
index ++ ;
list.add(ch+"");
}else{
//是数字,判断多位数的情况
String str = "";
while (index < expression.length() && (expression.charAt(index) >47 && expression.charAt(index) < 58 || expression.charAt(index)==46)){
str += expression.charAt(index);
index ++;
}
list.add(str);
//System.out.println(str);
}
}while (index < expression.length());
return list;
}
/**
* 根据后缀表达式list计算结果
* @param list
* @return
*/
private static double calculate(List<String> list) {
Stack<Double> stack = new Stack<>();
for(int i=0; i<list.size(); i++){
String item = list.get(i);
if(item.matches("^([0-9]{1,}[.][0-9]*)$") || item.matches("^([0-9]{1,})$")){
//是数字
stack.push(Double.parseDouble(item));
}else {
//是操作符,取出栈顶两个元素
double num2 = stack.pop();
//System.out.print(num2);
double num1 = stack.pop();
double res = 0;
if(item.equals("+")){
res = num1 + num2;
}else if(item.equals("-")){
res = num1 - num2;
}else if(item.equals("*")){
res = num1 * num2;
}else if(item.equals("/")){
res = num1 / num2;
}else {
throw new RuntimeException("运算符错误!");
}
stack.push(res);
}
}
return stack.pop();
}
public static boolean isInt(double calculateResult){
double n = calculateResult - (int)calculateResult;
if(n!=0){
return true;
}else{
return false;
}
}
public static void main(String []args){
Scanner sc = new Scanner(System.in);
String expression = sc.nextLine();
List<String> expressionList = expressionToList(expression);
//System.out.println("中缀表达式转为list结构="+expressionList);
//将中缀表达式转换为后缀表达式
List<String> suffixList = parseToSuffixExpression(expressionList);
//System.out.println("对应的后缀表达式列表结构="+suffixList);
//根据后缀表达式计算结果
double calculateResult = calculate(suffixList);
if(isInt(calculateResult)){
System.out.printf(expression+"=%.2f\n",calculateResult);
}else{
System.out.printf(expression+"="+ (int)calculateResult);
}
sc.close();
}
}
这个代码对之前po出的网页里面的代码进行优化的地方有:
- 可以计算小数
- 和手机上计算机一样,如果最后计算结果是整数,输出的答案也不包含小数点位;计算结果是小数,输出答案就是小数。
具体修改的地方在:
- 75行判断第一个list中的值是否为数字的时候,加入了能匹配小数的正则表达式
- 102和109行(写第一个list)添加了判断小数点的情况,如果是小数点,则也加入str中
- 129行加入了小数的计算
- 155-162行加入了判断结果为整数还是带小数的。
运行结果如下: