结对项目--实现括号和带分数的四则运算
项目
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Networkengineering1834 | 
|---|---|
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Networkengineering1834/homework/11148 | 
| 这个作业的目标 | 队友互相协作生成设计文档,实现代码编写和代码复审,最终实现一个可以处理括号和带分数的Java四则运算器 | 
结对成员:
- 姓名:刘奕池 学号:3118005285
- 姓名:谢智杰 学号:3118005292
github地址
1. PSP表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) | 
|---|---|---|---|
| Planning | 计划 | 30 | 30 | 
| · Estimate | 估计这个任务需要多少时间 | 10 | 20 | 
| Development | 开发 | 400 | 600 | 
| · Analysis | 需求分析 (包括学习新技术) | 20 | 30 | 
| · Design Spec | 生成设计文档 | 10 | 10 | 
| · Design Review | 设计复审 | 0 | 0 | 
| · Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 10 | 10 | 
| · Design | 具体设计 | 30 | 30 | 
| · Coding | 具体编码 | 260 | 420 | 
| · Code Review | 代码复审 | 30 | 20 | 
| · Test | 测试(自我测试,修改代码,提交修改) | 40 | 80 | 
| Reporting | 报告 | 65 | 55 | 
| · Test Repor | 测试报告 | 20 | 20 | 
| · Size Measurement | 计算工作量 | 5 | 5 | 
| · Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 40 | 30 | 
| 合计 | 495 | 685 | 
2. 计算模块接口的设计
- 
entity包 - 1.1 数值表示类:FractionNum, 该类支持分数和整数的存储。
 
- 
controller包 - 2.1 分数运算器:FraCalculator, 主要进行四则运算表达式的计算并返回一个FractionNum对象
 package com.my.controller; import com.my.entity.FractionNum; import java.util.Stack; /** * 计算器 */ public class FracCalculator { final public static FracCalculator calculator = new FracCalculator(); /** * 符号栈:用于存储运算符和括号 */ private Stack<Character> symbolStack = null; /** * 解析并计算四则运算表达式(含括号),返回计算结果 * * @param numStr 算术表达式(含括号) */ public FractionNum calculate(String numStr) { // System.out.println(new Throwable().getStackTrace()[0]+":"+numStr); numStr = removeStrSpace(numStr); // 去除空格 // 如果算术表达式尾部没有‘=’号,则在尾部添加‘=’,表示结束符 if (numStr.length() >= 1 && !"=".equals(numStr.charAt(numStr.length() - 1) + "")) { numStr += "="; } // 检查表达式是否合法 if (!isStandard(numStr)) { System.out.println("错误:算术表达式有误!"); return new FractionNum(0); } // 初始化栈 /* 数字栈:用于存储表达式中的各个数字 */ Stack<FractionNum> numberStack = new Stack<>(); symbolStack = new Stack<>(); // 用于缓存数字,因为数字可能是多位的 StringBuffer temp = new StringBuffer(); // 从表达式的第一个字符开始处理 for (int i = 0; i < numStr.length(); i++) { // 获取一个字符 char ch = numStr.charAt(i); if (isNumber(ch)) { // 若当前字符是数字 temp.append(ch); // 加入到数字缓存中 } else { // 非数字的情况 String tempStr = temp.toString(); // 将数字缓存转为字符串 if (!tempStr.isEmpty()) { int num = Integer.parseInt(tempStr); // 将数字字符串转为长整型数 numberStack.push(new FractionNum(num)); // 将数字压栈 temp = new StringBuffer(); // 重置数字缓存 } // 判断运算符的优先级,若当前优先级低于栈顶的优先级,则先把计算前面计算出来 while (!comparePri(ch) && !symbolStack.empty()) { FractionNum b = numberStack.pop(); // 出栈,取出数字,后进先出 FractionNum a = numberStack.pop(); // 取出运算符进行相应运算,并把结果压栈进行下一次运算 switch (symbolStack.pop()) { case '’': numberStack.push(FractionNum.with(a, b)); break; case '+': numberStack.push(FractionNum.add(a, b)); break; case '-': numberStack.push(FractionNum.sub(a, b)); break; case '×': case '*': numberStack.push(FractionNum.mul(a, b)); break; case '/': case '÷': numberStack.push(FractionNum.div(a, b)); break; default: break; } } // while循环结束 if (ch != '=') { symbolStack.push(ch); // 符号入栈 if (ch == ')') { // 去括号 symbolStack.pop(); symbolStack.pop(); } } } } // for循环结束 return numberStack.pop(); // 返回计算结果 } /** * 去除字符串中的所有空格 */ private String removeStrSpace(String str) { return str != null ? str.replaceAll(" ", "") : ""; } /** * 检查算术表达式的基本合法性,符合返回true,否则false */ private boolean isStandard(String numStr) { if (numStr == null || numStr.isEmpty()) // 表达式不能为空 return false; Stack<Character> stack = new Stack<>(); // 用来保存括号,检查左右括号是否匹配 boolean b = false; // 用来标记'='符号是否存在多个 for (int i = 0; i < numStr.length(); i++) { char n = numStr.charAt(i); // 判断字符是否合法 if (!(isNumber(n) || "(".equals(n + "") || ")".equals(n + "") || "+".equals(n + "") || "-".equals(n + "") || "*".equals(n + "") || "/".equals(n + "") || "=".equals(n + "") || "÷".equals(n + "") || "×".equals(n + "") || "’".equals(n + ""))) { return false; } // 将左括号压栈,用来给后面的右括号进行匹配 if ("(".equals(n + "")) { stack.push(n); } if (")".equals(n + "")) { // 匹配括号 if (stack.isEmpty() || !"(".equals((char) stack.pop() + "")) // 括号是否匹配 return false; } // 检查是否有多个'='号 if ("=".equals(n + "")) { if (b) return false; b = true; } } // 可能会有缺少右括号的情况 if (!stack.isEmpty()) return false; // 检查'='号是否不在末尾 return "=".equals(numStr.charAt(numStr.length() - 1) + ""); } /** * 判断字符是否是0-9的数字 */ private boolean isNumber(char num) { return num >= '0' && num <= '9'; } /** * 比较优先级:如果当前运算符比栈顶元素运算符优先级高则返回true,否则返回false */ private boolean comparePri(char symbol) { if (symbolStack.empty()) { // 空栈返回ture return true; } // 符号优先级说明(从高到低): // 第1级: ( // 第2级: * / // 第3级: + - // 第4级: ) char top = symbolStack.peek(); // 查看堆栈顶部的对象,注意不是出栈 if (top == '(') { return true; } // 比较优先级 switch (symbol) { case '(': // 优先级最高 return true; case '/': return true; case '’': // 优先级比+和-高 return top == '+' || top == '-' || top == '×' || top == '÷' || top == '*'; case '×': case '*': case '÷': { // 优先级比+和-高 return top == '+' || top == '-'; } case '+': case '-': return false; case ')': // 优先级最低 return false; case '=': // 结束符 return false; default: break; } return true; } }- 2.2 数值运算器:Calculator, 一个简单的数值运算器,主要用于对分数运算器返回的FractionNum结果进行运算,判断结果的正负。
- FractionNumCalculate(fractionNum1,fractionNum2)
- calculate(String str)方法
 
 package com.my.controller; import com.my.entity.FractionNum; import java.util.Stack; public class Calculator { private static final Stack<Character> stack;//后缀表达式 private static final Stack<Character> stack_1;//符号栈 private static final Stack<Character> stack_2;//临时栈 static { stack = new Stack<>(); stack_1 = new Stack<>(); stack_2 = new Stack<>(); } //运算 public static Double calculate(String str) throws Exception { char[] arr = str.toCharArray(); //转化为后缀表达式 for (char c : arr) { if (Character.isDigit(c)) {//判断是否为数字 stack.push(c); } else if (c == '*' || c == '/') { while (!stack_1.empty()) { char ch = stack_1.pop(); if (ch == '(') { stack_1.push(ch); break; } else if (ch == '*' || ch == '/') { stack.push(ch); } else { stack_2.push(ch); } } while (!stack_2.empty()) { stack_1.push(stack_2.pop()); } stack_1.push(c); } else if (c == '+' || c == '-') { while (!stack_1.empty()) { char ch = stack_1.pop(); if (ch == '(') { stack_1.push(ch); break; } else if (ch == '*' || ch == '/' || ch == '+' || ch == '-') { stack.push(ch); } else { stack_2.push(ch); } } while (!stack_2.empty()) { stack_1.push(stack_2.pop()); } stack_1.push(c); } else if (c == '(') { stack_1.push(c); } else if (c == ')') { char ch = stack_1.peek(); while (ch != '(') { ch = stack_1.pop(); stack.push(ch); } stack.pop(); } else { throw new Exception(); } } while (!stack_1.empty()) { stack.push(stack_1.pop()); } //进行运算 int index = 0; while (!stack.empty()) { index++; stack_2.push(stack.pop()); } Stack<Double> s = new Stack<>();//用于最后计算的栈 while (!stack_2.empty()) { char ch = stack_2.pop(); if (index > 1) { if (ch == '*' || ch == '/' || ch == '+' || ch == '-') { double sum = 0; double num1 = s.pop(); double num2 = s.pop(); switch (ch) { case '*': sum = num2 * num1; break; case '/': sum = num2 / num1; break; case '+': sum = num2 + num1; break; case '-': sum = num2 - num1; break; } s.push(sum); } else if (Character.isDigit(ch)) { s.push((double) Character.getNumericValue(ch)); } else { throw new Exception(); } } else { s.push((double) Character.getNumericValue(ch)); break; } } return s.pop(); } /** * 带分数比较 * @param num1 带分数1 * @param num2 带分数2 * @return false表示前一个值比另一个值大,true表示后者比前者大 */ public static boolean FractionNumCalculate(FractionNum num1, FractionNum num2) throws Exception { //false表示前一个值比另一个值大,true表示后者比前者大 int num1Index1 = num1.toString().indexOf('’'); int num2Index1 = num2.toString().indexOf('’'); if (num1Index1 != -1 || num2Index1 != -1) { if (num1Index1 == -1) {//3和2’1/3 Double answer1 = Calculator.calculate(num1.toString()); Double answer2 = Calculator.calculate(num2.toString().substring(0, num2Index1)); return answer1 <= answer2; } else if (num2Index1 == -1) {//2’1/3和3 Double answer1 = Calculator.calculate(num1.toString().substring(0, num1Index1)); Double answer2 = Calculator.calculate(num2.toString()); return answer1 <= answer2; } else {//2’1/3和3‘1/3 if (num1Index1 >= num2Index1) { Double answer1 = Calculator.calculate(num1.toString().substring(0, num1Index1)); Double answer2 = Calculator.calculate(num2.toString().substring(0, num2Index1)); if (answer1 > answer2) {//分数前的数值比较,前者大返回false return false; } if (answer1 < answer2) {//分数前的数值比较,前者大返回true return true; } else { answer1 = Calculator.calculate(num1.toString().substring(num1Index1 + 1)); answer2 = Calculator.calculate(num2.toString().substring(num2Index1 + 1)); return answer1 <= answer2; } } else { //后一个分数的数值更大 return true; } } } else { try { Double answer1 = calculate(num1.toString()); Double answer2 = calculate(num2.toString()); return answer1 <= answer2; } catch (Exception e) { e.printStackTrace(); } return false; } } }- 2.3 式子生成器:CreatFormula, 主要用于生成四则运算式子并检验式子是否符合要求,
- String createProblem(int maxNum) 生成四则运算式子
- int[] index(int n) 产生操作符的下标数组
- String check(String str, int operatorNum, int[] arr, FractionNum[] number, String[] operator, int flag) 检验式子是否正确
- String compare(String str1, String str2) 式子前后的数值做比较
 
 package com.my.controller; import com.my.entity.FractionNum; import java.util.Random; public class CreateFormula { public static final String True = "true"; public static final String False = "false"; public static final String Error = "error"; /** * 生成四则运算式子 * @param maxNum 式子中的数值的最大值 * @return 返回一个不超三个运算符的四则运算式子 * @throws Exception 抛出异常 */ public static String createProblem(int maxNum) throws Exception { //产生整数式子 Random r = new Random(); // 操作符数值 String[] operator = {"+", "-", "*", "÷"}; // 随机生成操作符的个数1, 2 int operatorNum = 1 + r.nextInt(2); // 新建数组来保存操作数 FractionNum[] number = new FractionNum[operatorNum + 1]; //操作符的下标 int[] arr = index(operatorNum); String s = ""; /* 通过循环和判断获取四则运算式子的所有操作数 */ for (int j = 0; j < operatorNum + 1; j++) { int num1 = r.nextInt(maxNum); // int num2 = 10 - num1; int num2 ; int flag = r.nextInt(3);//0为整数, 1为分数, 2为带分数 FractionNum num; if (flag == 0) { // 整数处理 if(num1 > maxNum){ j--; continue; } num = new FractionNum(num1); } else { if(flag == 1){ // 分数处理 num2 =1 + r.nextInt(maxNum - 1); } else { // 带分数处理 num2 = maxNum + r.nextInt(maxNum); } if((num1/num2) >= maxNum ){ // 保证数值不超过范围 j--; continue; } num = new FractionNum(num1, num2); } number[j] = num; } //如果flag=0,则该式子加左括号,如果flag=1,则该式子加右括号,2为无括号 int flag = r.nextInt(3); // int flag = 1; switch (operatorNum) { case 1: { s = number[0] + operator[arr[0]] + number[1]; break; } case 2: { if (flag == 0) { s = "(" + number[0] + operator[arr[0]] + number[1] + ")" + operator[arr[1]] + number[2]; } else if (flag == 1) { s = number[0] + operator[arr[0]] + "(" + number[1] + operator[arr[1]] + number[2] + ")"; } else { s = number[0] + operator[arr[0]] + number[1] + operator[arr[1]] + number[2]; } break; } default: break; } s = CreateFormula.check(s, operatorNum, arr, number, operator, flag); if ("The formula is error".equals(s)) { return createProblem(maxNum); } FractionNum num = FracCalculator.calculator.calculate(s); int answer = num.getNumerator(); if (answer >= 0) { s = s + "="; } else { return createProblem(maxNum); } return s; } /** * 产生操作符的下标数组 * * @param n 下标数组的位数 * @return 返回下标数组 */ public static int[] index(int n) { //产生操作符的下标数组 Random random = new Random(); int[] a = new int[n]; for (int j = 0; j < n; j++) { a[j] = random.nextInt(4); } return a; } /** * 检验式子是否正确 * * @param str 第一次生成式子 * @param operatorNum 获取相应的操作符个数1-2 * @param arr 操作符下标,0表示+,1表示-,2表示*,3表示÷ * @param number 数字数组 * @param operator +-*÷数组 * @return 经过判断和处理后,如果式子内容没有问题,则返回更改后的式子,式子有问题则返回"The formula is error" */ public static String check(String str, int operatorNum, int[] arr, FractionNum[] number, String[] operator, int flag) throws Exception { for (int i = 0; i < operatorNum; i++) { // 检测出当前操作符为-号 if (arr[i] == 1) { // 操作符的个数不唯一 if (operatorNum != 1) { String str1; String str2; // -号为第一个运算符 if (i == 0) { // -号为第一个运算符且为左括号的情况 if (flag == 0) { // 其中一个操作数出现负号 if ("error".equals(compare(number[0].toString(), number[1].toString()))) { str = "The formula is error"; } // 后操作数比前操作数大 if ("true".equals(compare(number[0].toString(), number[1].toString()))) { FractionNum temp = number[0]; number[0] = number[1]; number[1] = temp; str = "(" + number[0] + operator[arr[0]] + number[1] + ")" + operator[arr[1]] + number[2]; } } // -号为第一个运算符且为右括号的情况 if (flag == 1) { str1 = "" + number[0]; str2 = "(" + number[1] + operator[arr[1]] + number[2] + ")"; // 其中一个操作数出现负号 if ("error".equals(compare(str1, str2))) { str = "The formula is error"; } // 后操作数比前操作数大 if ("true".equals(compare(str1, str2))) { str = str2 + operator[arr[i]] + str1; } } else { str1 = "" + number[0]; str2 = number[1] + operator[arr[1]] + number[2]; // 其中一个操作数出现负号 if ("error".equals(compare(str1, str2))) { str = "The formula is error"; } // 后操作数比前操作数大 if ("true".equals(compare(str1, str2))) { str = str2 + operator[arr[i]] + str1; } } } else {//第二个运算符为-号 // 第二个操作符为-号, 且为左括号情况 if (flag == 0) { str1 = "(" + number[0] + operator[arr[0]] + number[1] + ")"; str2 = "" + number[2]; // 其中一个操作数出现负号 if ("error".equals(compare(str1, str2))) { str = "The formula is error"; } // 后操作数比前操作数大 if ("true".equals(compare(str1, str2))) { str = str2 + operator[arr[i]] + str1; } } // 第二个操作符为-号, 且为右括号情况 if (flag == 1) { // 其中一个操作数出现负号 if ("error".equals(compare(number[1] + "", number[2] + ""))) { str = "The formula is error"; } // 后操作数比前操作数大 if ("true".equals(compare(number[1] + "", number[2] + ""))) { FractionNum temp = number[1]; number[1] = number[2]; number[2] = temp; str = number[0] + operator[arr[0]] + "(" + number[1] + operator[arr[1]] + number[2] + ")"; } } else { str1 = number[0] + operator[arr[0]] + number[1]; str2 = "" + number[2]; // 其中一个操作数出现负号 if ("error".equals(compare(str1, str2))) { str = "The formula is error"; } // 后操作数比前操作数大 if ("true".equals(compare(str1, str2))) { str = str2 + operator[arr[i]] + str1; } } } } else { FractionNum temp; /* 式子只有一个操作符且操作符为-号, 先比较-号前后的两个数字, 若后者比前者大则数字的顺序颠倒 */ if (Calculator.FractionNumCalculate(number[0], number[1])) { temp = number[0]; number[0] = number[1]; number[1] = temp; str = number[0] + "-" + number[1]; } } } // 检测到操作符为÷号 if (arr[i] == 3) { // 操作符为÷号且只有一个操作符 if (operatorNum == 1) { /* 判断÷号后面的数字是否为0, 如果为0就把其改为1 */ number[1] = (number[1].getNumerator() == 0) ? new FractionNum(1) : number[1]; str = number[0] + operator[arr[0]] + number[1]; } else { /* 判断÷号后面的数字是否为0, 如果为0就把其改为1 */ number[i + 1] = (number[i + 1].getNumerator() == 0) ? new FractionNum(1) : number[i + 1]; if (flag == 0) { // 判断式子是否为左括号情况 str = "(" + number[0] + operator[arr[0]] + number[1] + ")" + operator[arr[1]] + number[2]; } else { // 式子为右括号或者无括号 str = number[0] + operator[arr[0]] + number[1] + operator[arr[1]] + number[2]; } } } } // System.out.println(str); return str; } /** * 式子前后的数值做比较 * @param str1 -号式子的左半部 * @param str2 -号式子的右半部 * @return 返回一个boolean类型的值,false表示左半部值比右半部值大,true表示后者比前者大 */ public static String compare(String str1, String str2) { //false表示前一个值比另一个值大,true表示后者比前者大 try { /*调用CalculatorT.calculator.calculate()方法实现带分数的运算器计算,返回一个FractionNum带分数类型*/ FractionNum num1 = FracCalculator.calculator.calculate(str1); FractionNum num2 = FracCalculator.calculator.calculate(str2); if (isNormal(num1) && isNormal(num2)) { if (Calculator.FractionNumCalculate(num1, num2)) { return True; } } else { return Error; } } catch (Exception e) { e.printStackTrace(); } return False; } /** * 判断分数内容是否正常,即是否出现负数的分子分母 * @param num 带分数 * @return 如果带分数的分子分母存在负数则返回false, 否则返回true */ public static boolean isNormal(FractionNum num) { return num.getDenominator() >= 0 && num.getNumerator() >= 0; } }- 2.4 四则运算平台:CalculatePlatform, 运行四则运算平台, 调用其余三个controller包下的方法实现完整的运算流程,  内含运行四则计算器平台方法runPlatform()。
- void runPlatform(int num,int maxNum) 运行四则计算器平台
 
 package com.my.controller; import com.my.entity.FractionNum; import com.my.util.ReadTxt; import java.util.Scanner; public class CalculatePlatform { /** * 运行四则计算器平台 * @param num 生成四则运算式子的个数 * @param maxNum 数值允许范围的最大值 * @throws Exception 抛出异常 */ public static void runPlatform(int num,int maxNum) throws Exception { Scanner scanner = new Scanner(System.in); StringBuilder exercisesBuffer = new StringBuilder(); // 练习题目buffer StringBuilder answerBuffer = new StringBuilder(); // 练习答案buff StringBuilder correctBuffer = new StringBuilder(); // 正确答案buff StringBuilder wrongBuffer = new StringBuilder(); // 错误答案buff String[] officialAnswer = new String[num]; // 练习答案, 用于逐个与用户输入答案做比较 int[] userAnswerIndex = new int[num]; // int数组, 0表示答案有误, 1表示答案正确 String userAnswer; // 暂存用户输入答案 int correctNum = 0; // 回答正确题目个数 int wrongNum = 0; // 回答错误题目个数 for (int i = 0; i < num; i++) { // 生成式子 String str = CreateFormula.createProblem(maxNum); System.out.println(str); // 把每一次生成的式子存入exercisesBuffer exercisesBuffer.append("第").append(i + 1).append("题:").append(str).append("\r\n--------------------\r\n"); // 获取式子运算结果 FractionNum answerNum = FracCalculator.calculator.calculate(str); // 保存结果答案 officialAnswer[i] = answerNum.toString(); /* 用户逐题输入式子的结果 */ userAnswer = scanner.next(); if(officialAnswer[i].equals(userAnswer)){ userAnswerIndex[i] = 1; correctNum++; } else { userAnswerIndex[i] = 0; wrongNum++; } // 把每一次生成的答案存入answerBuffer answerBuffer.append("第").append(i + 1).append("题答案:").append(answerNum.toString()).append("\n").append("--------------------\n"); System.out.println("--------------------"); // 最后一个式子生成完毕, 把exercisesBuffer和answerBuffer内容存入相应文档 if(i == num -1){ ReadTxt.writeTxt("C:\\Users\\10973\\Desktop\\test\\Exercises.txt",exercisesBuffer.toString()); ReadTxt.writeTxt("C:\\Users\\10973\\Desktop\\test\\Answers.txt",answerBuffer.toString()); } } // 统计用户的答题情况 correctBuffer.append("Correct:").append(correctNum).append("("); wrongBuffer.append("Wrong:").append(wrongNum).append("("); for (int i = 0; i < num; i++){ if(userAnswerIndex[i] == 1){ correctBuffer.append((i+1)).append(", "); } else { wrongBuffer.append((i+1)).append(", "); } if(i == (num - 1)){ correctBuffer.append(")"); wrongBuffer.append(")"); } } // 把用户答题情况保存到文档中 ReadTxt.writeTxt("C:\\Users\\10973\\Desktop\\test\\Grade.txt", correctBuffer.toString() + "\r\n" + wrongBuffer.toString()); } }
- 
** util包** - 3.1 文件读取工具:ReadTxt, 主要功能是实现文件的读取和写入.
- void readTxt(String txtPath) 读取路径
- void writeTxt(String txtPath,String content) 把内容写入文件
 
 
- 3.1 文件读取工具:ReadTxt, 主要功能是实现文件的读取和写入.
- 
程序类图 
 ![]() 
- 
部分代码流程图 
 ![]() 
3. 程序性能分析
- 
性能分析截图 
 ![]() 
- 
内存空间占用 
 ![]() 
4. 计算模块部分单元测试展示
- 
测试生成10000条式子 
 ![]() 
- 
部分结果展示: 
 ![]() 
- 
文本内容展示: 
 ![]() 
 
                    
                     
                    
                 
                    
                







 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号