这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/SoftwareEngineering2024 |
---|---|
这个给作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/SoftwareEngineering2024/homework/13137 |
这个作业的目标 | 实现一个自动生成小学四则运算题目的命令行程序 |
合作人员
姓名 | 学号 |
---|---|
黄冬炫 | 3122004570 |
盘伟铖 | 3122004579 |
一、github链接:https://github.com/sunwu12/project2.git
二、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 25 |
-Estimate | -估计这个任务需要多少时间 | 20 | 20 |
Development | 开发 | 785 | 847 |
-Analysis | -需求分析 (包括学习新技术) | 300 | 280 |
-Design Spec | -生成设计文档 | 20 | 20 |
-Design Review | -设计复审 | 20 | 22 |
-Coding Standard | -代码规范 (为目前的开发制定合适的规范) | 5 | 5 |
-Design | -具体设计 | 150 | 180 |
-Coding | -具体编码 | 220 | 260 |
-Code Review | -代码复审 | 40 | 45 |
-Test | -测试(自我测试,修改代码,提交修改) | 30 | 35 |
Reporting | 报告 | 43 | 55 |
-Test Repor | -测试报告 | 15 | 20 |
-Size Measurement | -计算工作量 | 10 | 15 |
-Postmortem & Process Improvement Plan | -事后总结, 并提出过程改进计划 | 18 | 20 |
All | 合计 | 848 | 927 |
三、效能分析
四、设计实现过程
类:
- Expression:表达式对象类,包含表达式字符串、表达式的值、表达式的主运算符等属性,用于构造复杂的且带有括号的表达式
- splicing:拼接表达式。将两个表达式对象拼接成一个新的表达式对象。
- addBrackets:生成括号。为表达式添加括号
- getRandomValue:生成随机数。生成没有运算符的一个随机表达式对象
- Fraction:(真)分数对象类,包含分子、分母、分数数值等属性,用于构造一个可自动约分、化简的真分数
- fractionCalculate:分数计算。计算两个分数的运算结果
- transformValue:分数转换。根据输入的分子分母转换成Fraction对象
- fractionSimplify:分数化简。化简约分分数对象
- ExpGeneration:表达式生成类,生成随机表达式
- getExpression:生成一个随机的表达式(0<运算符个数<=3)
- getAllExpression:生成指定数量的表达式集合
- ExpHandle:表达式处理类
- calExpressionString:表达式计算。根据表达式字符串计算表达式的值
- getInfixExpression:将表达式字符串转成中缀表达式
- getPostfixExpression:将中缀表达式转换成后缀表达式
- getSignPriority:获取运算符的优先级
- handleList:将后缀表达式中的两个数值与一个运算符进行合并运算
- checkDuplicate:判断两个表达式字符串是否重复,借助字符串的后缀表达式进行判断
- TxtHandle:文件操作类
- txtRecord:将生成的随机表达式集合存入到题目文件和答案文件中
- txtJudge:对题目文件和答案文件中的每一行题目和答案进行结果比对,并将结果写入Grade.txt文件中
五、代码说明
-
表达式拼接
//拼接两个表达式成一个 public static Expression splicing(Expression leftE,Expression rightE,char sign){ if(leftE==null||rightE==null)return null; int newNum=leftE.num+rightE.num+1; Expression newE=new Expression(); //运算符个数超过3个 if(newNum> newE.maxNum)return null; if((newE.value=Fraction.fractionCalculate(leftE.value,rightE.value,sign))==null)return null; //添加括号 if(sign=='×'||sign=='÷'){ if(leftE.keySign=='+'||leftE.keySign=='-')addBrackets(leftE); if(rightE.keySign=='+'||rightE.keySign=='-')addBrackets(rightE); } if(sign=='÷'&&(rightE.keySign=='÷'||rightE.keySign=='×'))addBrackets(rightE); if(sign=='-'&&(rightE.keySign=='+'||rightE.keySign=='-'))addBrackets(rightE); newE.keySign=sign; newE.num=newNum; newE.expression=leftE.expression+' '+sign+' '+rightE.expression; return newE; }
将两个表达式和一个运算符拼接成一个新的表达式,过程中会计算两个表达式的运算结果存入到新表达式的value中,并根据左右表达式的运算符添加括号
-
判断表达式是否重复
public static Boolean checkDuplicate(String expression1, String expression2) { List<String> e1 = getPostfixExpression(expression1); List<String> e2 = getPostfixExpression(expression2); if (e1.size() != e2.size()) return false; if (!Objects.equals(calExpressionString(expression1), calExpressionString(expression2))) return false; String[] sinList1 = new String[2]; String[] sinList2 = new String[2]; do { e1 = handleList(e1, sinList1); e2 = handleList(e2, sinList2); if (!Arrays.equals(sinList1, sinList2)) return false; if (e1 == null || e2 == null) { break; } } while (e1.size() != 1); return true; } private static List<String> handleList(List<String> list, String[] sinList) { int i = 0; Pattern pattern = Pattern.compile("[+×÷-]"); //每进行一次,去除一个运算符,合并两个数值 while (i < list.size()) { String str = list.get(i); Matcher matcher = pattern.matcher(str); if (matcher.find()) { char sign = str.charAt(0); List<String> newList = new ArrayList<>(list); String newVal = Fraction.fractionCalculate(newList.get(i - 2), newList.get(i - 1), sign); if (newVal == null) return null; sinList[0] = newList.get(i - 2); sinList[1] = newList.get(i - 1); Arrays.sort(sinList); newList.set(i - 2, newVal); newList.remove(i - 1); newList.remove(i - 1); return newList; } i++; } return null; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass()!= o.getClass()) return false; Expression exp = (Expression) o; return ExpHandle.checkDuplicate(this.expression,exp.expression); }
先将两个表达式字符串都转成后缀表达式数组,再多次调用handleList方法将后缀表达式中的两个数值和一个运算符合成一个结果数值,其中sinList是由每次计算的两个值组合的数组,如果每次调用得到两个表达式的sinList有一次不相同,则视为两表达式不重复。最后重写Expression的equals方法,即可实现生成的随机表达式集合中没有重复的表达式
-
判断表达式结果对错
public static void txtJudge(String subjectPath,String answerPath) {
List<String> subjectList=FileUtil.readUtf8Lines(subjectPath);
List<String> anwerList=FileUtil.readUtf8Lines(answerPath);
//将读取的表达式中的等于号去掉
List<String> expList=subjectList.stream().map(s->s.replace(" =","")
.split("^\\d+\\.")[1]).toList();
List<String> valList=anwerList.stream().map(s->s.replace(" =","")
.split("^\\d+\\.")[1].trim()).toList();
int[] gradeList=new int[subjectList.size()];
for(int i=0;i<subjectList.size();i++){
//计算正确则为1,反之为0
gradeList[i]= Objects.equals(ExpHandle.calExpressionString(expList.get(i)), valList.get(i)) ?1:0;
}
StringBuilder sb1=new StringBuilder();
StringBuilder sb2=new StringBuilder();
//根据1,0的个数判断题目正确个数
long corNum=Arrays.stream(gradeList).filter(i->i==1).count();
long wroNum=Arrays.stream(gradeList).filter(i->i==0).count();
sb1.append("Correct: ").append(corNum).append("(");
sb2.append("Wrong: ").append(wroNum).append("(");
for(int i=0;i<subjectList.size();i++){
if(gradeList[i]==1) {
sb1.append(i + 1).append(",");
}
else sb2.append(i+1).append(",");
}
if(sb1.charAt(sb1.length()-1)==',')sb1.deleteCharAt(sb1.length()-1);
if(sb2.charAt(sb2.length()-1)==',')sb2.deleteCharAt(sb2.length()-1);
sb1.append(")");
sb2.append(")");
FileUtil.writeUtf8Lines(new ArrayList<>(List.of(new String[]{sb1.toString(),sb2.toString()})), GRADE);
}
先读取题目文件中的表达式集合,并去除等于号,再根据calExpressionString方法逐条计算表达式字符串的结果是否与答案文件中的结果相同,相同记为1,不相同记为0;接着根据1,0数量计算题目的正确、错误个数并写入到grade.txt文件中
六、测试运行
@Test
public void testMain(){
//获取随机表达式集合
List<Expression> es= ExpGeneration.getAllExpression(30,20);
for(Expression e : es){
System.out.println(e);
}
//将表达式集合写入题目文件和答案文件中
TxtHandle.txtRecord(es);
//测试文件正确表达式个数
try {
TxtHandle.txtJudge("src/resources/Exercises.txt",
"src/resources/Answers.txt");
}catch (Exception e){
System.out.println("文件格式不正确");
}
}
其中2、5、8、15条数据人为修改过答案,故出现4个错误数
同时该项目可生成10000+不重复的表达式
七、项目小结
关于设计:原本是想着用后缀表达式来实现生成随机的表达式,但是过程中发现这样子不好生成括号,于是专门写了一个对象类用于实现生成带括号的表达式。
关于查重,因为是用对象来实现表达式,所以重写了对象的equals方法来实现判断俩个表达式是否重复,以确保生成的随机表达式当中不会出现重复的。
关于结对合作:在该结对项目中,我们学习了如何高效沟通,共同分析项目中的算法