这个作业属于哪个课程 | 广工2023软件工程 |
---|---|
这个作业要求在哪里 | 结队编程:小学四则运算 |
这个作业的目标 | 实现一个四则运算题目的程序 |
其他参考文献 | csdn,百度 |
结对信息:
姓名 | 学号 |
---|---|
龙依婷 | 3221005155 |
方喜加 | 3221005152 |
文章目录
一、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 20 | 30 |
Development | 开发 | 665 | 728 |
· Analysis | · 需求分析 (包括学习新技术) | 80 | 107 |
· Design Spec | · 生成设计文档 | 20 | 25 |
· Design Review | · 设计复审 (和同事审核设计文档) | 10 | 18 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 15 | 6 |
· Design | · 具体设计 | 40 | 57 |
· Coding | · 具体编码 | 360 | 402 |
· Code Review | · 代码复审 | 60 | 50 |
· Test | · 测试(自我测试,修改代码,提交修改) | 80 | 63 |
Reporting | 报告 | 70 | 105 |
· Test Report | · 测试报告 | 40 | 72 |
· Size Measurement | · 计算工作量 | 20 | 16 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 10 | 17 |
合计 | 755 | 863 |
二、效能分析
由图可知,byte[]所占资源最大
三、设计实现过程
项目结构图
主函数流程图
关键函数流程图
生成题目的函数
计算题目的函数
四、代码说明
生成题目
/**
* 生成n道题目,并且操作数在range范围内
* @param n 题目个数
* @param range 数值范围
* @return 返回
*/
public static Map<String, String> getProblem(int n, int range) {
//运算式和结果的集合
Map<String, String> problem_result = new HashMap<>();
//结果集合,用于判断是否重复
Set<String> results = new HashSet<>();
//获取n道题目
for (int i = 0; i < n; i++) {
//随机获取1~3个运算符数量
int operator = (int) (Math.random() * 3) + 1;
//随机获取operator个运算符
Character[] operators = getOperator(operator);
//随机获取operator+1个操作数,且范围在range内
String[] operand = getOperand(operator + 1, range);
//获取运算式表达式
String[] expression = getExpression(operators, operand);
//判断是否为负数
if ((expression == null || expression[1].contains("-")) || results.contains(expression[1])) i--;
else {
results.add(expression[1]);
//expression[0]:运算式;expression[1]:结果
problem_result.put(expression[0], expression[1]);
}
}
return problem_result;
}
检查答案正确
/**
* 验证答案的正确率,并记录到Grade.txt文件中
* @param exerciseUrl 练习题文件路径
* @param answerUrl 答案文件路径
*/
public static void checkAccuracy(String exerciseUrl, String answerUrl) {
File exerciseFile = new File(CheckUtil.fullPath(exerciseUrl));
File answerFile = new File(CheckUtil.fullPath(answerUrl));
File gradeFile = new File(Constants.FILE_ADDRESS, "Grade.txt");
if (exerciseFile.isFile() && answerFile.isFile()) {//当两个文件是标准文件时
BufferedReader exerciseReader = null;
BufferedReader answerReader = null;
OutputStream gradeFileOutputStream = null;
List<Integer> Correct = new ArrayList<>();
List<Integer> Wrong = new ArrayList<>();
try {
exerciseReader = new BufferedReader(new InputStreamReader(Files.newInputStream(exerciseFile.toPath())));
answerReader = new BufferedReader(new InputStreamReader(Files.newInputStream(answerFile.toPath())));
String exercise;
String answer;
String[] exerciseStr;
String[] answerStr;
int line = 0;//记录行数
while (null != (exercise = exerciseReader.readLine()) && null != (answer = answerReader.readLine())) {
exerciseStr = exercise.split("、");
answerStr = answer.split("、");
//获取运算式的正确答案
String correctAnswer = AriUtil.getExpressValue(exerciseStr[1]);
if (correctAnswer.equals(answerStr[1])) {
line++;
Correct.add(line);
} else {
line++;
Wrong.add(line);
}
}
String result = "Correct:" + Correct.size() + Correct + "\r\n" + "Wrong:" + Wrong.size() + Wrong;
//保存成绩文件
gradeFileOutputStream = Files.newOutputStream(gradeFile.toPath());
gradeFileOutputStream.write(result.getBytes());
System.out.println("统计结果在Grade.txt文件中");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (exerciseReader != null) {
try {
exerciseReader.close();
} catch (IOException ignored) {
}
}
if (answerReader != null) {
try {
answerReader.close();
} catch (IOException ignored) {
}
}
if (gradeFileOutputStream != null) {
try {
gradeFileOutputStream.close();
} catch (IOException ignored) {
}
}
}
} else {
System.out.println("该文件不存在!!!");
}
}
计算题目正确答案
/**
* 运用了调度场算法,计算一个题目的初步计算结果
*
* @param question 题目
* @return 初步计算结果
*/
public static String getExpressValue(String question) {
//栈operand放置操作数,包含整数和分数
Stack<Fraction> operand = new Stack<>();
//栈operator放置运算符,包含 +、-、*、÷和括号
Stack<Character> operator = new Stack<>();
char[] problem = question.toCharArray(); //将题目字符串转换为一个新的字符数组。
char ch;
for (int i = 0; i < problem.length; i++) {
//获取当前的字符
ch = problem[i];
//当字符为左括号时,入栈
if (ch == '(') {
operator.push(ch);
} else if (ch == ')') {
//运算符栈顶元素不是‘(’
while (operator.peek() != '(') {
//拿取操作数栈中的两个操作数
Fraction fraction1 = operand.pop();
Fraction fraction2 = operand.pop();
//计算结果
Fraction result = calculate(operator.pop(), fraction1.getNumerator(), fraction1.getDenominator(),
fraction2.getNumerator(), fraction2.getDenominator());
if (result.getNumerator() < 0) return "#";
//将结果压栈
operand.push(result);
}
//将'('出栈
operator.pop();
} else if (ch == '+' || ch == '-' || ch == '*' || ch == '÷') {
//运算符栈不为空,当前运算符小于栈顶运算符优先级
while (!operator.empty() && !priority(ch, operator.peek())) {
//取操作栈中的两个分数
Fraction fraction1 = operand.pop();
Fraction fraction2 = operand.pop();
//得到计算后的值
Fraction result = calculate(operator.pop(), fraction1.getNumerator(), fraction1.getDenominator(),
fraction2.getNumerator(), fraction2.getDenominator());
if (result.getNumerator() < 0) {
return "#";
}
//将结果压栈
operand.push(result);
}
//将运算符入栈
operator.push(ch);
} else {//当字符是操作数的时候
if (ch >= '0' && ch <= '9') {
StringBuilder buf = new StringBuilder();
//取出一个完整的数值 比如 2/3、5、7/39
while (i < problem.length && (problem[i] == '/' || ((problem[i] >= '0') && problem[i] <= '9'))) {
buf.append(problem[i]);
i++;
}
i--;
//buf里面是一个操作数
String val = buf.toString();
//标记‘/’的位置
int flag = val.length();
for (int k = 0; k < val.length(); k++) {
//当得到的数在/标记/的位置,然后生成分数对象
if (val.charAt(k) == '/') {
flag = k;
}
}
//分子
StringBuilder numeratorBuf = new StringBuilder();
//分母
StringBuilder denominatorBuf = new StringBuilder();
for (int j = 0; j < flag; j++) {
numeratorBuf.append(val.charAt(j));
}
//判断是否为分数,如果是分数,则flag不为val.length
if (flag != val.length()) {
for (int q = flag + 1; q < val.length(); q++) {
denominatorBuf.append(val.charAt(q));
}
} else {//不是分数 分母为1
denominatorBuf.append('1');
}
//入栈
operand.push(new Fraction(Integer.parseInt(numeratorBuf.toString()), Integer.parseInt(denominatorBuf.toString())));
}
}
}
while (!operator.empty()) {
Fraction fraction1 = operand.pop();
Fraction fraction2 = operand.pop();
//得到运算后的值
Fraction result = calculate(operator.pop(), fraction1.getNumerator(), fraction1.getDenominator(),
fraction2.getNumerator(), fraction2.getDenominator());
if (result.getNumerator() < 0) {
return "#";
}
//结果压栈
operand.push(result);
}
Fraction result = operand.pop();
//将分数约分
return reduction(result);
}
五、测试运行
测试代码覆盖率
测试用例没有用到main函数,所以Main覆盖率为0
- 生成1万道题和正确答案
- 1万道题的答案比对
- 正常生成2道题
- -n 参数错误 Main.exe -n -2 -r 200
- -r 参数错误 Main.exe -n 2 -r -200
- 题目文件名错误 Main.exe -e Exe.txt -a Answers.txt
- 答案文件名错误 Main.exe -e Exercises.txt -a Answ.txt
- 直接-e Exercises.txt -a Answers.txt
- 使用doc文件类型 Main.exe -e Exercises.txt -a Answer.doc
六、项目小结
在这次结对编程的体验中,可以充分发挥两个人的优点,共同进步,实现1+1 > 2的效果,我负责测试及博客的编写,队友负责写算法及性能分析。我们采用一个人编程,一个人监督并帮忙的模式;达到一定时间角色互换,思维互换,这样既不会思维僵硬,还能学到对方身上的优点,还可以解决不专注问题。如果一个人编程感觉枯燥,进行不下去了,另一个就积极与她沟通,并帮忙编程。一开始,效果并不理想,我们两个的代码思路有分歧,但是在后面的开发过程中,我们不断沟通,借鉴前人的经验,出现了问题,一起想办法解决,包容不同的思想,从中获取经验,开发效率得到很大提高。在这个过程中,我们收获的不仅是编程能力的提高,更是团队协作,沟通能力和表达能力的提高。