软件工程 | 班级链接 |
---|---|
作业要求 | 结对项目 |
github地址 | Github |
作业目标 | 实现一个自动生成小学四则运算题目的程序 |
姓名 | 学号 |
---|---|
邓梓荣 | 3121005121 |
蔡嘉睿 | 3121005159 |
一、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
Estimate | 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | 300 | 360 |
Analysis | 需求分析 (包括学习新技术) | 60 | 30 |
Design Spec | 生成设计文档 | 60 | 30 |
Design Review | 设计复审 | 60 | 60 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 60 | 90 |
Design | 具体设计 | 60 | 90 |
Coding | 具体编码 | 240 | 200 |
Code Review | 代码复审 | 60 | 80 |
Test | 测试(自我测试,修改代码,提交修改) | 120 | 140 |
Reporting | 报告 | 30 | 30 |
Test Repor | 测试报告 | 60 | 80 |
Size Measurement | 计算工作量 | 30 | 30 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 60 | 30 |
合计 | 1160 | 1210 |
二、需求分析
1.使用 -n 参数控制生成题目的个数,例如:Myapp.exe -n 10,将生成10个题目。
2.使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如:Myapp.exe -r 10, 将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。
3.生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1− e2的子表达式,那么e1≥ e2。
4.生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数。
5.每道题目中出现的运算符个数不超过3个。
6.程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。例如,23 + 45 = 和45 + 23 = 是重复的题目,6 × 8 = 和8 × 6 = 也是重复的题目。3+(2+1)和1+2+3这两个题目是重复的,由于+是左结合的,1+2+3等价于(1+2)+3,也就是3+(1+2),也就是3+(2+1)。但是1+2+3和3+2+1是不重复的两道题,因为1+2+3等价于(1+2)+3,而3+2+1等价于(3+2)+1,它们之间不能通过有限次交换变成同一个题目。
三、性能分析
性能分析图:
四、设计实现过程
项目架构:
类和方法
1.ArithmeticUtil
描述:四则运算算法的生成和检测。
方法:buildArithmetic
描述:生成小学四则运算题目并写入Exercises.txt文件。
方法:checkExercise
描述:检查小学四则运算题目答案并将结果写入Grade.txt文件。
2.DataGenerator
描述:封装了生成随机数据的方法。
3.Fraction
描述:分数类,含有分数的约分,加减乘除等方法。
4.RPN
描述:使用逆波兰算法计算中缀表达式的值。
方法:calculateInfixExpression
描述:传入一个中缀表达式,返回计算结果。
流程图
五、代码说明
生成四则运算式子
/**
* 生成小学四则运算题目并写入Exercises.txt文件
*
* @param exerciseNumber 生成个数
* @param numberRange 题目中数值(自然数、真分数和真分数分母)的范围
*/
public static void buildArithmetic(int exerciseNumber, int numberRange) {
Random random = new Random();
List<String> arithmeticList = new LinkedList<>();
DataGenerator dataGenerator = new DataGenerator();
for (int i = 1; i <= exerciseNumber; i++) {
//限制每道题目中出现的运算符个数不超过3个
int operatorNumber = random.nextInt(3)+1;
StringBuilder arithmetic = new StringBuilder();
switch (operatorNumber) {
case 1:
// 9 + 5 arithmetic.append(dataGenerator.generateNumber(numberRange))
.append(dataGenerator.randomOperator())
.append(dataGenerator.generateNumber(numberRange))
.append(" =");
break;
case 2:
//5 × 3 -2
arithmetic.append(dataGenerator.generateLeftParenthesis(1))
.append(dataGenerator.generateNumber(numberRange))
.append(dataGenerator.randomOperator())
.append(dataGenerator.generateLeftParenthesis(3))
.append(dataGenerator.generateNumber(numberRange))
.append(dataGenerator.generateRightParenthesis(4, 6))
.append(dataGenerator.randomOperator())
.append(dataGenerator.generateNumber(numberRange))
.append(dataGenerator.generateRightParenthesis(6, 6))
.append(" =");
break;
case 3:
// 5 × 3 -2 ÷ 8
arithmetic.append(dataGenerator.generateLeftParenthesis(1))
.append(dataGenerator.generateNumber(numberRange))
.append(dataGenerator.randomOperator())
.append(dataGenerator.generateLeftParenthesis(3))
.append(dataGenerator.generateNumber(numberRange))
.append(dataGenerator.generateRightParenthesis(4, 8))
.append(dataGenerator.randomOperator())
.append(dataGenerator.generateLeftParenthesis(5))
.append(dataGenerator.generateNumber(numberRange))
.append(dataGenerator.generateRightParenthesis(6, 8))
.append(dataGenerator.randomOperator())
.append(dataGenerator.generateNumber(numberRange))
.append(dataGenerator.generateRightParenthesis(8, 8))
.append(" =");
break;
default:
break;
}
try {
RPN.calculateInfixExpression(arithmetic.toString());
arithmeticList.add(i + ". " + arithmetic);
} catch (Exception e) {
i--;
}
}
FileUtil.writeLines(arithmeticList,SubmissionParams.GENERATE_EXERCISE_FILE_NAME,"utf-8");
}
这里使用了链式结构,不断往四则运算中添加符号,括号和数字,并对生成的结果进行验证,排除负数的情况,dataGenerator中封装了各种生成随机数据的方法,如下面生成真分数的方法:
/**
* 生成真分数 (真分数:1/2, 1/3, 2/3, 1/4, 1’1/2, …)
*
* @param numberRange
* @return
*/
public String randProperFraction(int numberRange) {
int denominator = random.nextInt(numberRange - 1) + 1;
int molecule = random.nextInt(numberRange * denominator - 1) + 1;
if (molecule >= denominator) {
int N = molecule / denominator;
molecule %= denominator;
return molecule == 0 ? String.valueOf(N) : N + "’" + molecule + "/" + denominator;
}
return molecule + "/" + denominator;
}
最后把生成的四则运算写入到文件中。
计算四则运算式子并校对答案
/**
* 检查小学四则运算题目答案并将结果写入Grade.txt文件
*
* @param exerciseFileName 待检测题目文件名
* @param answerFileName 待检测题目答案文件名
*/
public static void checkExercise(String exerciseFileName, String answerFileName) {
List<String> exercises = FileUtil.readLines(exerciseFileName, StandardCharsets.UTF_8);
Map<String, String> answerMap = new HashMap<>();
List<String> answers = FileUtil.readLines(answerFileName, StandardCharsets.UTF_8);
String pattern = "(^[0-9]+). (.*)";
Pattern r = Pattern.compile(pattern);
answers.forEach(a -> {
Matcher m = r.matcher(a);
if (m.find()) {
answerMap.put(m.group(1), m.group(2));
}
});
List<String> trueExercise = new LinkedList<>();
List<String> wrongExercise = new LinkedList<>();
for (int i = 0; i < exercises.size(); i++) {
Matcher m = r.matcher(exercises.get(i));
if (m.find()) {
String index = m.group(1);
String arithmetic = m.group(2);
String answer = RPN.calculateInfixExpression(arithmetic);
if (answer.equals(answerMap.get(index))) {
trueExercise.add(index);
} else {
wrongExercise.add(index);
}
}
}
StringBuilder fileContent = new StringBuilder().append("Correct: ").append(trueExercise.size()).append(" (");
for (int i = 0; i < trueExercise.size(); i++) {
fileContent.append(trueExercise.get(i)).append(i == trueExercise.size() - 1 ? "" : ",");
}
fileContent.append(")\n");
fileContent.append("Wrong: ").append(wrongExercise.size()).append(" (");
for (int i = 0; i < wrongExercise.size(); i++) {
fileContent.append(wrongExercise.get(i)).append(i == wrongExercise.size() - 1 ? "" : ",");
}
fileContent.append(")");
FileUtil.writeString(fileContent.toString(), SubmissionParams.CHECK_RESULT_FILE_NAME, "utf-8");
}
读取文件获得四则运算式子和答案后,通过正则表达式获取真正的数据(序列号和式子/答案),然后通过逆波兰表达式计算出结果,并进行字符串对比,如果则计入trueExercise,否则计入wrongExercise,逆波兰计算后缀表达式过程如下:
private static String calculate(List<String> ls) {
// 创建一个栈,只需要一个栈即可
Stack<String> stack = new Stack<>();
// 遍历 ls
for (String item : ls) {
if (isNumber(item)) {
stack.push(item);
} else {
// pop 出两个数并运算,在入栈
String num2 = stack.pop();
String num1 = stack.pop();
String res;
Fraction fraction1 = new Fraction(num1);
Fraction fraction2 = new Fraction(num2);
if ("+".equals(item)) {
res = fraction1.add(fraction2).toString();
} else if ("-".equals(item)) {
res = fraction1.subtract(fraction2).toString();
} else if ("×".equals(item)) {
res = fraction1.multiply(fraction2).toString();
} else if ("÷".equals(item)) {
res = fraction1.divide(fraction2).toString();
} else {
throw new RuntimeException("符号有问题");
}
stack.push(res);
}
}
// 最后留在stack的数据是运算结果
return stack.pop();
}
六、测试运行
对程序进行测试生成至少10个测试用例:
判断答案对错:
七、项目小结
在这个四则运算编程项目中,我与我的合作伙伴先共同明确了项目的目标和要求,并进行了详细的讨论和规划。我们讨论了任务的分工和时间表,并确保每个人都理解自己的角色和责任。建立良好的沟通渠道是重中之重,我们一起讨论项目进展、及时解决遇到的问题,并共享想法和建议。我们互相支持,共同解决遇到的技术问题和挑战。我们充分利用了彼此的技能和专长,共同努力克服困难。通过此次项目我们学会了更好地协调和合作,提高了沟通和解决问题的能力,相互学习和成长。
标签:结对,题目,String,项目,四则运算,生成,new,append From: https://www.cnblogs.com/zrDeng/p/17734244.html