结对项目:四则运算题目生成
这个作业属于哪个课程 | 22级计科1班 |
---|---|
这个作业要求在哪里 | 作业要求 |
这个作业的目标 | 自动生成小学四则运算题目 |
github项目链接 | 链接 |
姓名&学号
姓名 | 学号 |
---|---|
万凯毅 | 3122004788 |
周彦安 | 3122004804 |
PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
· Estimate | 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | 1180 | 1180 |
· Analysis | 需求分析(包括学习新技术) | 60 | 60 |
· Design Spec | 生成设计文档 | 60 | 60 |
· Design Review | 设计复审 | 60 | 60 |
· Coding Standard | 代码规范(为当前的开发制定合适的规范) | 60 | 60 |
· Design | 具体设计 | 360 | 360 |
· Coding | 具体编码 | 360 | 360 |
· Code Review | 代码复审 | 100 | 100 |
· Test | 测试(自我测试、修改代码、提交修改) | 120 | 120 |
Reporting | 报告 | 160 | 160 |
· Test Report | 测试报告 | 100 | 100 |
· Size Measurement | 计算工作量 | 30 | 30 |
· Postmortem & Process Improvement Plan | 事后总结,并提出过程改进计划 | 30 | 30 |
Total | 合计 | 1370 | 1370 |
功能
生成题目
输出
控制台输出
题目
答案
效能分析
优化前
每生成一个题目就使用append的形式写入文件
优化后
生成所有题目后,一次性写入文件
分析
优化前后生成10000道题目以及答案的耗时都是700ms左右,性能差别主要在文件io上,优化前相当于对文件做了10000次io,优化后只有一次io
设计实现过程
总体流程
生成单个题目流程
判断题目是否重复
利用多级树型结构逐层判断,如果到最后一层出现相同的,则认为是重复表达式:
代码说明
批量随机生成题目
每次生成题目时校验是不是负数、是否重复,如果不满足要求就不断重新生成
/**
* 批量随机生成题目
*
* @param range 操作数的大小范围
* @param numberOfQuestions 最大题目数
* @return 题目,答案
*/
public static Tuple2<List<String>, List<String>> generateQuiz(int range, int numberOfQuestions) {
int duplicateCount = 0;
int negativeCount = 0;
int totalCount = 0;
List<String> quizzes = new ArrayList<>(numberOfQuestions);
List<String> answers = new ArrayList<>(numberOfQuestions);
for (int i = 1; i <= numberOfQuestions; i++) {
int maxOperators = generateRandomOperatorCounts();
while (true) {
totalCount++;
List<String> operands = new ArrayList<>();
List<String> operators = new ArrayList<>();
for (int j = 0; j < maxOperators + 1; j++) {
String lastOperator = operators.isEmpty() ? "" : operators.get(operators.size() - 1);
Fraction operand = FractionUtils.generateRandomOperand(lastOperator, range);
operands.add(operand.toString());
if (j < maxOperators) {
operators.add(generateRandomOperator());
}
}
StringBuilder quiz = new StringBuilder();
for (int j = 0; j < operands.size(); j++) {
quiz.append(operands.get(j));
if (j < operators.size()) {
quiz.append(" ").append(operators.get(j)).append(" ");
}
}
String expression = quiz.toString();
if (operands.size() > 2 && RANDOM.nextBoolean()) {
expression = addRandomParentheses(expression);
}
String postfix = ExpressionUtils.infixToPostfix(expression);
Fraction result = ExpressionUtils.evaluatePostfix(postfix);
if (Objects.nonNull(result)) {
if (!DuplicateChecker.isDuplicate(result, expression)) {
quizzes.add(i + ". " + expression);
answers.add(i + ". " + result);
break;
} else {
duplicateCount++;
}
} else {
negativeCount++;
}
}
}
System.out.println("生成" + numberOfQuestions + "道题目完成,总次数:" + totalCount + ",重复次数:" + duplicateCount + ",负数次数:" + negativeCount);
return new Tuple2<>(quizzes, answers);
}
题目去重
// key:表达式的结果;value:map(key:表达式的长度,value:map集合(key:表达式中的每一个操作数或操作符;value:该字符串出现的次数))
private static final Map<String, Map<Integer, List<Map<String, Integer>>>> DUMPLICATE_MAP = new HashMap<>();
public static boolean isDuplicate(Fraction result, String expression) {
// 已创建的表达式中,如果有计算结果相同,且表达式中的所有字符和出现的次数都一样,就认为是重复的
String resultStr = result.toString();
expression = expression.replaceAll("[()]", "");
Integer length = expression.length();
// 统计表达式中每个操作数和操作符出现的次数
Map<String, Integer> characterCountMap = Arrays.stream(expression.split("\\s+"))
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.collectingAndThen(Collectors.counting(), Long::intValue))
);
Map<Integer, List<Map<String, Integer>>> expressionLengthMap = DUMPLICATE_MAP.get(resultStr);
if (expressionLengthMap != null) {
// 存在计算结果相同的表达式
List<Map<String, Integer>> characterCountMapList = expressionLengthMap.get(length);
if (characterCountMapList != null) {
// 存在长度相同的表达式
boolean isDuplicate = characterCountMapList.stream()
.anyMatch(map -> map.equals(characterCountMap));
if (isDuplicate) {
// 存在操作数和操作符出现次数相同的表达式
return true;
}
}
} else {
expressionLengthMap = new HashMap<>();
DUMPLICATE_MAP.put(resultStr, expressionLengthMap);
}
List<Map<String, Integer>> characterCountMapList = expressionLengthMap.computeIfAbsent(length, k -> new ArrayList<>());
characterCountMapList.add(characterCountMap);
return false;
}
判题
输出
控制台输出
结果输出
效能分析
分析:对10000道题目和答案进行校验,耗时主要在题目结果的计算上
测试和异常情况
- args参数:
- -n 和 -r 的参数如果没有就是默认值10
- -n 和 -r 的参数必须是整数类型的值
- -e 和 -a 的参数必须同时存在或不存在
- -e 和 -a 参数不满足格式 xxx.txt
- -e 和 -a 参数的文件必须存在
- 校验题目:题目数和答案数必须一致
- 生成题目:-r 给出的参数不支持生成 -n 的题目数
项目小结
本项目开发了一个智能数学题目生成器,能够随机生成包含加法、减法、乘法和除法的数学表达式,并支持题目的有效性校验与答案计算。加强了代码规范,性能优化,设计分析以及团队合作的能力。