GitHub地址:https://github.com/soperfect/SE_work3_arithmetic
结对编程小伙伴:古梓欣 学号:3118004991
结对编程方式:远程计算机控制
一、项目相关要求
-
使用 -n 参数控制生成题目的个数,例如
Myapp.exe -n 10
,将生成10个题目 -
使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如
Myapp.exe -r 10
,将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。 -
生成的题目中计算过程不能产生负数
-
生成的题目中如果存在形如
e1÷ e2
的子表达式,那么其结果应是真分数。 -
每道题目中出现的运算符个数不超过3个。
-
程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目,即运算的过程不能一样。
-
生成的题目存入执行程序的当前目录下的
Exercises.txt
文件,格式如下-
四则运算题目1
-
四则运算题目2
-
-
在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的
Answers.txt
文件,格式如下:-
答案1
-
答案2
-
-
程序应能支持一万道题目的生成。
-
程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下:
Myapp.exe -e <exercisefile>.txt -a <answerfile>.txt
。Myapp.exe -e <exercisefile>.txt -a <answerfile>.txt
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
其中“:”后面的数字5表示对/错的题目的数量,括号内的是对/错题目的编号。为简单起见,假设输入的题目都是按照顺序编号的符合规范的题目。
二、解题思路
-
把分数和整数合并一起表示,整数为分母为1的分数,方便分数的计算
-
运算符不超过3个,则最多的运算数有4个,分多种情况讨论,1个运算符下有分数的和没分数的,简化需要讨论的情况,不对哪个运算的数讨论是否是分数,只讨论式子中有几个分数。
-
重复的式子也可看成运算顺序和参与运算的数值相同的式子,记录下每则式子的运算过程,与新生成的式子的运算过程进行对比,便可做到去重
三、设计实现过程
-
将整个项目分为三个模块,
main
、entity
、service
,model
函数中为实体类,用于表示分数的类,用于表示运算式子和运算结果的类,service
模块中主要是本项目所需要的一些功能函数,随机生成题目的类,检查题目是否重复的类,检查用户输入的结果和答案有哪些异同的类,main
模块中有main函数,调用service
模块中的函数实现本项目的功能。
四、代码说明
用于存放算式及算式结果的类
/** * 用于存放算术表达式以及结果 * @author Red Date. * @date 2020/3/27 23:17 */ public class ResultMap { //表达式 private String expression; //结果 private String result; public String getExpression() { return expression; } public void setExpression(String expression) { this.expression = expression; } public String getResult() { return result; } public void setResult(String result) { this.result = result; } public ResultMap(String expression, String result) { this.expression = expression; this.result = result; } public ResultMap(){ } }
用于表示数的实体类
/** * 分数类,用于表示整数和分数 * @author Red Date. * @date 2020/3/28 22:14 */ public class Fraction { private int numerator; //分子 private int denominator; //分母 public Fraction(int numerator,int denominator){ this.numerator = numerator; this.denominator = denominator; } public Fraction(int numerator){ this.denominator = 1; this.numerator = numerator; } //生成一个分数或整数,在给定的范围内 public Fraction(boolean isFraction,int bound){ Random random = new Random(); int numerator = random.nextInt(bound); while (numerator==0 ){ numerator = random.nextInt(bound); } //生成一个分数 if(isFraction){ int denominator = random.nextInt(bound); //分母不能为零,分子也不能为零 while (denominator==0 ){ denominator = random.nextInt(bound); } this.numerator = numerator; this.denominator = denominator; }else{ this.numerator = numerator; this.denominator = 1; } } public Fraction(){ } //加法运算 public Fraction addition(Fraction fraction){ int numerator = fraction.getNumerator(); int denominator = fraction.getDenominator(); //新的分子 int newNumerator = this.numerator * denominator + this.denominator * numerator; //新的分母 int newDenominator = this.denominator * denominator; Fraction result = new Fraction(newNumerator,newDenominator); return result; } //减法运算 public Fraction subtraction(Fraction fraction){ int numerator = fraction.getNumerator(); int denominator = fraction.getDenominator(); //新的分子 int newNumerator = this.numerator * denominator - this.denominator * numerator; //新的分母 int newDenominator = this.denominator * denominator; Fraction result = new Fraction(newNumerator,newDenominator); return result; } //除法运算 public Fraction division(Fraction fraction){ int numerator = fraction.getNumerator(); int denominator = fraction.getDenominator(); //新的分子 int newNumerator = this.numerator * denominator ; //新的分母 int newDenominator = this.denominator * numerator; Fraction result = new Fraction(newNumerator,newDenominator); return result; } public Fraction multiplication(Fraction fraction){ int numerator = fraction.getNumerator(); int denominator = fraction.getDenominator(); //新的分子 int newNumerator = this.numerator * numerator ; //新的分母 int newDenominator = this.denominator * denominator; Fraction result = new Fraction(newNumerator,newDenominator); return result; } // 用辗转相除法求最大公约数 private static int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); } // 对分数进行约分 public void appointment() { // 如果分子是0或分母是1就不用约分了 if (numerator == 0 || denominator == 1) return; int gcd = gcd(numerator, denominator); this.numerator /= gcd; this.denominator /= gcd; } //判断该数是否为负数 public boolean isNegative(){ return (this.getDenominator()<0 || this.getNumerator()<0); } //重写toString,返回约分完的结果 @Override public String toString(){ appointment(); //分子为0 if(numerator == 0) return ""+ numerator; //真分数 else if(numerator > denominator){ if(numerator % denominator == 0) return "" + numerator/denominator; return "" + numerator/denominator + "'" + numerator%denominator +"/" + denominator; }else if(numerator == denominator){ return "" + numerator; } return "" + numerator + "/" + denominator; } public int getNumerator() { return numerator; } public void setNumerator(int numerator) { this.numerator = numerator; } public int getDenominator() { return denominator; } public void setDenominator(int denominator) { this.denominator = denominator; } }
//采用递归的方式计算结果 public static Fraction calculate(List list){ int place = whetherMulDiv(list); String flag = null; if(place != -1){ flag = addSubMulDiv(list,place,1); }else { flag = addSub(list); } if(flag.equals("error")){ return null; } if(list.size() == 1) return (Fraction)list.get(0); return calculate(list); } //将运算顺序添加到集合中 public static List<String> operationsOrder(List list){ List<String> result = new ArrayList<String>(); String flag = null; while (list.size()!=1){ int place = whetherMulDiv(list); if(place != -1){ result.add(list.get(place-1).toString()); result.add((String) list.get(place)); result.add(list.get(place+1).toString()); flag = addSubMulDiv(list,place,1); }else { int place1 = 0; for (int i=0;i<list.size();i++){ if(list.get(i).equals("+")||list.get(i).equals("-")){ place1 = i; break; } } result.add(list.get(place1-1).toString()); result.add((String) list.get(place1)); result.add(list.get(place1+1).toString()); flag = addSubMulDiv(list,place1,0); } } if("error".equals(flag)) return null; return result; } //判断集合中是否有乘除,返回乘除的位置 private static int whetherMulDiv(List list){ for (int i=0;i<list.size();i++){ if(list.get(i).equals("*")||list.get(i).equals("÷")) return i; } return -1; } //一次加法减法或乘法除法运算,0为加减,1为乘除 private static String addSubMulDiv(List list , int place,int flag){ String operator1 = null; String operator2 = null; if (flag == 0){ //实现加法或者减法 operator1 = "+"; operator2 = "-"; }else if(flag == 1){ operator1 = "*"; operator2 = "÷"; } //获取运算前后的数 Fraction fraction1 = (Fraction) list.get(place-1); Fraction fraction2 = (Fraction) list.get(place+1); String symbol = (String) list.remove(place); //移除后一个数 list.remove(place); if(symbol.equals(operator1)){ Fraction result = new Fraction(); if(operator1.equals("*")){ result = fraction1.multiplication(fraction2); }else result = fraction1.addition(fraction2); if(result.isNegative()){ return "error"; } list.set(place-1,result); }else if(symbol.equals(operator2)){ Fraction result = new Fraction(); if(operator2.equals("÷")){ result = fraction1.division(fraction2); }else result = fraction1.subtraction(fraction2); if(result.isNegative()){ return "error"; } list.set(place-1,result); } return "right"; } //加法和减法运算,直接算出结果 private static String addSub(List list){ for (int i =0;i<list.size();i++){ if(list.get(i).equals("+")){ Fraction fraction1 = (Fraction)list.get(i-1); list.remove(i); Fraction fraction2 = (Fraction)list.get(i); list.remove(i); Fraction result = fraction1.addition(fraction2); if(result.isNegative()){ return "error"; } list.set(i-1,result); i--; }else if(list.get(i).equals("-")){ Fraction fraction1 = (Fraction)list.get(i-1); list.remove(i); Fraction fraction2 = (Fraction)list.get(i); list.remove(i); Fraction result = fraction1.subtraction(fraction2); if(result.isNegative()){ return "error"; } list.set(i-1,result); i--; } } return "right"; }
检查是否重复
/** * 判断是否重复,不重复则将表达式加入已有集合中 * * @param list 要判断的运算式拆分后的list集合 * @param expressionList 已经生成的运算式的集合 * @return boolean,true为重复 */ public static boolean isRepeat(List<String> list, List<List<String>> expressionList) { for (List<String> expression : expressionList) { if (expression.size() == list.size()) { int length = list.size(); int num = -2; //用于累计符合条件的运算符 int index1 = 0; //运算符和数字一致,判断运算顺序是否一致 for (int index = 1; index < length; index += 3) { if (expression.get(index).equals(list.get(index))) { //运算符相同时,前后数字是否一致 if ((expression.get(index - 1).equals(list.get(index - 1)) && expression.get(index + 1).equals(list.get(index + 1))) || (expression.get(index - 1).equals(list.get(index + 1)) && expression.get(index + 1).equals(list.get(index - 1)))) { num += 3; } } index1 = index; } if (num == index1) { return true; } else { expressionList.add(list); return false; } } } expressionList.add(list); return false; }
检查答案
/** * 检查答案是否正确 * @param checkFile 自己填的答案的文件名 * @param answersFile 标准答案文件名 * @throws IOException */ public static void check(String checkFile ,String answersFile) throws IOException { BufferedReader readerCheck = new BufferedReader(new InputStreamReader(new FileInputStream(checkFile))); BufferedReader answerCheck = new BufferedReader(new InputStreamReader(new FileInputStream(answersFile))); String checkLine =""; String answerLine =""; String correct = ""; String wrong = ""; int correctNum = 0; int wrongNum = 0; while ((checkLine = readerCheck.readLine()) != null && (answerLine = answerCheck.readLine()) != null){ String[] checkString = checkLine.split("\\."); String[] answerString = answerLine.split("\\."); if(checkString[1].equals(answerString[1])){ if (correct.equals("")){ correct = answerString[0]; correctNum++; } else{ correct = correct+","+answerString[0]; correctNum++; } }else{ if (wrong.equals("")){ wrong = answerString[0]; wrongNum++; } else{ wrong = wrong+","+answerString[0]; wrongNum++; } } } readerCheck.close(); answerCheck.close(); OutputStreamWriter resultWriter = new OutputStreamWriter(new FileOutputStream(new File("Grade.txt")), "UTF-8"); resultWriter.append("Correct:"+correctNum+"("+correct+")"); resultWriter.append("\n"); resultWriter.append("Wrong:"+wrongNum+"("+wrong+")"); resultWriter.close(); System.out.println("Correct:"+correctNum+"("+correct+")"); System.out.println("Wrong:"+wrongNum+"("+wrong+")"); }
五、测试运行
1、生成10000道题目
2、检查答案,圈出来的地方的答案专门改成错误的
3、参数错误测试
4、代码覆盖率
六、PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 20 |
· Estimate | · 估计这个任务需要多少时间 | 350 | 771 |
Development | 开发 | 420 | 691 |
· Analysis | · 需求分析 (包括学习新技术) | 60 | 160 |
· Design Spec | · 生成设计文档 | 20 | 25 |
· Design Review | · 设计复审 (和同事审核设计文档) | 20 | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
· Design | · 具体设计 | 40 | 60 |
· Coding | · 具体编码 | 150 | 200 |
· Code Review | · 代码复审 | 20 | 30 |
· Test | · 测试(自我测试,修改代码,提交修改) | 100 | 176 |
Reporting | 报告 | 45 | 60 |
· Test Report | · 测试报告 | 15 | 15 |
· Size Measurement | · 计算工作量 | 10 | 15 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 30 |
合计 | 485 | 771 |
七、项目小结
-
吸取了上一个作业个人项目的经验,这次在动手写代码之前,先仔细的设计了各个类和函数,捋清楚各个类之间的关系之后再开始着手写代码,避免了在实现功能的同时考虑各个功能的逻辑关系,提高了效率。
-
本次是结对编程项目,通过远程计算机控制实现两个人共用一台电脑进行编程,由于网络延迟以及线上沟通的不便性,降低了开发的效率,同时有结对编程的小伙伴相互监督,避免了一些低级错误和一些逻辑上判断的死角的发生。
-
本次项目的难点在于分数的表示、如何去重、如何计算出正确的结果,解决了这几个问题,剩下的东西便不会很难