结对项目——自动生成小学四则运算题目
| 作业所属课程 | https://edu.cnblogs.com/campus/gdgy/Networkengineering1834 |
|---|---|
| 作业要求 | https://edu.cnblogs.com/campus/gdgy/Networkengineering1834/homework/11148 |
| 作业目标 | 实现一个自动生成小学四则运算题目的命令行程序,结对共同完成一个项目 |
一.项目成员
- 姓名:陈诒祺 学号:3118005318
- 姓名:王烁俊 学号:3118005336
二.GitHub地址
GitHub仓库:https://github.com/EricChanXO/Pair-Item-arithmetic
三.PSP表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 30 | 20 |
| · Estimate | 估计这个任务需要多少时间 | 70 | 60 |
| Development | 开发 | 420 | 600 |
| · Analysis | 需求分析 (包括学习新技术) | 40 | 30 |
| · Design Spec | 生成设计文档 | 20 | 20 |
| · Design Review | 设计复审 | 30 | 30 |
| · Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
| · Design | 具体设计 | 60 | 60 |
| · Coding | 具体编码 | 360 | 300 |
| · Code Review | 代码复审 | 40 | 40 |
| · Test | 测试(自我测试,修改代码,提交修改) | 60 | 50 |
| Reporting | 报告 | 120 | 120 |
| ·Test Report | 测试报告 | 30 | 30 |
| ·Size Measurement | 计算工作量 | 10 | 10 |
| ·Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 50 | 50 |
| Total | 合计 | 1360 | 1440 |
四.性能分析
-
各数据类型占用内存
![]()
-
CPU占用频率
![]()
五.设计实现过程
(一) 文件工程结构

(二)设计流程
- 使用Random类进行随机取值,对每一道题目的运算符个数,括号位置,运算数值进行生成。
- 使用的是Java语言,使用到几个固定的运算符和括号,通过Java随机类的api进行随机获取运算符和数值,在进行计算。
(三)关键代码说明
-
数字
package com.cyqwsj.arithmetic.service.impl;
import com.cyqwsj.arithmetic.service.AbstractNumber;
/**
* 数字
*
* @author cyq
* @date 2020/10/9 22:27
*/
public class Number extends AbstractNumber {protected Number() {
super();
}public Number(int numerator) {
super(numerator);
}public Number(int numerator, int denominator) {
super(numerator, denominator);
}@Override
public AbstractNumber convertToTrueFraction(int numerator, int denominator) {
Number number = new Number();
if (denominator == 0 || numerator == 0) {
number.setNumerator(numerator);
number.setDenominator(denominator);
number.setNaN(true);
return number;
}
//获取最大公因数
int maxCommonFactor = getMaxCommonFactor(Math.abs(numerator), Math.abs(denominator));
numerator = numerator / maxCommonFactor;
denominator = denominator / maxCommonFactor;
if (numerator < 0 && denominator < 0) {
numerator = -numerator;
denominator = -denominator;
}
number.setNumerator(numerator);
number.setDenominator(denominator);
if (denominator == 1) {
number.setFraction(false);
} else {
number.setFraction(true);
}
return number;
}@Override
public AbstractNumber plus(AbstractNumber other) {
int thisMolecule = this.getNumerator();
int thisDenominator = this.getDenominator();int otherMolecule = other.getNumerator();
//通分
thisMolecule = thisMolecule * other.getDenominator();
thisDenominator = thisDenominator * other.getDenominator();
otherMolecule = otherMolecule * this.getDenominator();int numerator = thisMolecule + otherMolecule;
return new Number(numerator, thisDenominator);
}@Override
public AbstractNumber subtract(AbstractNumber other) {
int thisMolecule = this.getNumerator();
int thisDenominator = this.getDenominator();int otherMolecule = other.getNumerator();
//通分
thisMolecule = thisMolecule * other.getDenominator();
thisDenominator = thisDenominator * other.getDenominator();
otherMolecule = otherMolecule * this.getDenominator();int numerator = thisMolecule - otherMolecule;
return new Number(numerator, thisDenominator);
}@Override
public AbstractNumber multiply(AbstractNumber other) {
int numerator = this.getNumerator() * other.getNumerator();
int denominator = this.getDenominator() * other.getDenominator();
return new Number(numerator, denominator);
}@Override
public AbstractNumber divide(AbstractNumber other) {
int numerator = this.getNumerator() * other.getDenominator();
int denominator = this.getDenominator() * other.getNumerator();
return new Number(numerator, denominator);
}/**
* 获取最大公因数
*
* @param a 一个数
* @param b 另一个数
* @return 最大公因数
*/
private int getMaxCommonFactor(int a, int b) {
if (a < b) {
int c = a;
a = b;
b = c;
}
int r = a % b;
while (r != 0) {
a = b;
b = r;
r = a % b;
}
return b;
}
} -
表达式(题目)
package com.cyqwsj.arithmetic.service.impl;
import com.cyqwsj.arithmetic.po.BracePosition;
import com.cyqwsj.arithmetic.service.AbstractExpression;import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;import static com.cyqwsj.arithmetic.constant.OperationalConstant.*;
/**
* 表达式(题目)
*
* @author cyq
* @date 2020/10/9 22:28
*/
public class Expression extends AbstractExpression {@Override
public void calculate() {
Expression temp = copy(this);
while (temp.getOperatorList().size() > 0) {
int index = getPrioritizedOperation(temp);
if (index == -1) {
throw new RuntimeException("error");
}
Number number1 = temp.getNumberList().get(index);
Number number2 = temp.getNumberList().get(index + 1);
String opt = temp.getOperatorList().get(index);
Number result = operate(number1, number2, opt);temp.getNumberList().set(index, result);
temp.getNumberList().remove(index + 1);
temp.getOperatorList().remove(index);}
this.setAnswer(temp.getNumberList().get(0));
}/**
* 复制一个表达式
*
* @param expression 要复制的表达式
* @return 复制好的表达式
*/
private Expression copy(Expression expression) {
Expression temp = new Expression();
ListnumberList = new LinkedList<>(expression.getNumberList());
temp.setNumberList(numberList);
ListoptList = new LinkedList<>(expression.getOperatorList());
temp.setOperatorList(optList);
ListpositionList = new LinkedList<>();
for (BracePosition p : expression.getBracePositions()) {
BracePosition position = new BracePosition(p.getBegin(), p.getEnd());
positionList.add(position);
}
temp.setBracePositions(positionList);
return temp;
}/**
* 做运算
*
* @param number1 操作数
* @param number2 操作数
* @param opt 运算符
* @return 运算结果
*/
private Number operate(Number number1, Number number2, String opt) {
if (PLUS.equals(opt)) {
return (Number) number1.plus(number2);
} else if (SUBTRACT.equals(opt)) {
Number result = (Number) number1.subtract(number2);
if (result.getNumerator() < 0 || result.getDenominator() <= 0) {
this.setExistNegative(true);
}
return result;
} else if (MULTIPLY.equals(opt)) {
return (Number) number1.multiply(number2);
} else if (DIVIDE.equals(opt)) {if (number2.getNumerator() == 0) {
this.setExistDivideZero(true);
return new Number(0);
}
return (Number) number1.divide(number2);
}
return null;
}/**
* 获取当前最高优先级的操作符
*
* @param expression 表达式
* @return 最高优先级的操作符的下标
*/
private int getPrioritizedOperation(Expression expression) {
if (expression.getBracePositions().size() > 0) {
//两个括号
if (expression.getBracePositions().size() > 1) {
BracePosition pos1 = expression.getBracePositions().get(0);
BracePosition pos2 = expression.getBracePositions().get(1);
if (pos1.range() == pos2.range()) {
pos2.setBegin(pos2.getBegin() - 1);
pos2.setEnd(pos2.getEnd() - 1);
expression.getBracePositions().set(1, pos2);
expression.getBracePositions().remove(0);
return pos1.getBegin();
} else if (pos1.range() < pos2.range()) {
pos2.setEnd(pos2.getEnd() - 1);
expression.getBracePositions().set(1, pos2);
expression.getBracePositions().remove(0);
return pos1.getBegin();
} else if (pos1.range() > pos2.range()) {
pos1.setEnd(pos1.getEnd() - 1);
expression.getBracePositions().set(0, pos1);
expression.getBracePositions().remove(1);
return pos2.getBegin();
}//一个括号
} else if (expression.getBracePositions().size() == 1) {
BracePosition pos = expression.getBracePositions().get(0);
//括号包括一个操作符
if (pos.range() == 1) {
expression.getBracePositions().remove(0);
return pos.getBegin();
//括号包括2个操作符
} else {
String opt1 = expression.getOperatorList().get(pos.getBegin());
String opt2 = expression.getOperatorList().get(pos.getBegin() + 1);
//取第一个操作符
if (isPrioritizedOperation(opt1, opt2)) {
pos.setEnd(pos.getEnd() - 1);
expression.getBracePositions().set(0, pos);
return pos.getBegin();
//取第二个操作符
} else {
pos.setEnd(pos.getEnd() - 1);
expression.getBracePositions().set(0, pos);
return pos.getBegin() + 1;
}
}
}
} else {
if (expression.getOperatorList().size() == 1) {
return 0;
}
int index = 0;
String prioritizedOpt = expression.getOperatorList().get(0);
String opt;
for (int i = 1; i < expression.getOperatorList().size(); i++) {
opt = expression.getOperatorList().get(i);
if (!isPrioritizedOperation(prioritizedOpt, opt)) {
prioritizedOpt = opt;
index = i;
}
}
return index;
}
return -1;
}/**
* 比较两个运算符的优先级
*
* @param opt1 左边的运算符
* @param opt2 右边的运算符
* @return 左边优先级高返回true
*/
private boolean isPrioritizedOperation(String opt1, String opt2) {
if (opt1.equals(MULTIPLY) || opt1.equals(DIVIDE)) {
return true;
} else {
if (opt2.equals(PLUS) || opt2.equals(SUBTRACT)) {
return true;
}
}
return false;
}@Override
public boolean isRepeat(AbstractExpression expression) {
if (this.getTotalOperator() == expression.getTotalOperator()) {
ListnumberList = this.getNumberList();
ListotherNumberList = expression.getNumberList();
if (numberList.containsAll(otherNumberList)) {
ListoperatorList = this.getOperatorList();
ListotherOperatorList = expression.getOperatorList();
if (operatorList.containsAll(otherOperatorList)) {
return compareExpression(this, (Expression) expression);
}
}
}
return false;
}/**
* 比较表达式是否重复
*
* @param expression 表达式
* @param otherExpression 另一个表达式
* @return true为重复
*/
private boolean compareExpression(Expression expression, Expression otherExpression) {
Expression temp1 = expression.copy(expression);
Expression temp2 = expression.copy(otherExpression);
while (temp1.getOperatorList().size() > 0) {
int index1 = getPrioritizedOperation(temp1);
int index2 = getPrioritizedOperation(temp2);Number number1 = temp1.getNumberList().get(index1);
Number number2 = temp1.getNumberList().get(index1 + 1);
String opt1 = temp1.getOperatorList().get(index1);
Number result1 = operate(number1, number2, opt1);Number number3 = temp2.getNumberList().get(index2);
Number number4 = temp2.getNumberList().get(index2 + 1);
String opt2 = temp2.getOperatorList().get(index2);
Number result2 = operate(number3, number4, opt2);if (PLUS.equals(opt1) && PLUS.equals(opt2) || MULTIPLY.equals(opt1) || MULTIPLY.equals(opt2)) {
if (!((number1.equals(number3) || number1.equals(number4))
&& (number2.equals(number3) || number2.equals(number4)))) {
return false;
}
}
temp1.getNumberList().set(index1, result1);
temp1.getNumberList().remove(index1 + 1);
temp1.getOperatorList().remove(index1);temp2.getNumberList().set(index2, result2);
temp2.getNumberList().remove(index2 + 1);
temp2.getOperatorList().remove(index2);}
return true;
}@Override
public boolean checkNegative() {
return this.isExistNegative();
}@Override
public boolean checkDivideZero() {
return this.isExistDivideZero();
}@Override
public boolean checkLegitimacy() {
this.calculate();
return checkNegative() || checkDivideZero();
}@Override
public String printQuestion() {
StringBuilder stringBuilder = new StringBuilder();
Listlist = makeupString();
for (String s : list) {
stringBuilder.append(s);
stringBuilder.append(" ");
}
stringBuilder.append("= ");
return stringBuilder.toString();
}/**
* 构建输出字符串
*
* @return 字符串列表
*/
private ListmakeupString() {
Listlist = new ArrayList<>(7 + this.getBracePositions().size() * 2);
ListnumberList = this.getNumberList();
ListoperatorList = this.getOperatorList();
list.add(numberList.get(0).toString());
for (int i = 0; i < operatorList.size(); i++) {
list.add(operatorList.get(i));
list.add(numberList.get(i + 1).toString());
}
insertBrackets(list);
return list;}
/**
* 插入括号
*
* @param list 有操作数和运算符的字符串列表
* @return 完整的运算表达式字符串
*/
private ListinsertBrackets(List list) {
ListbracePositions = this.getBracePositions();
if (bracePositions == null || bracePositions.size() == 0) {
return list;
}
BracePosition pos = bracePositions.get(0);
//只有一个括号
if (bracePositions.size() == 1) {
//括号括着两个运算符
if (pos.range() == 2) {
if (pos.getBegin() == 0) {
list.add(0, LEFT_BRACKET);
list.add(6, RIGHT_BRACKET);
} else {
list.add(2, LEFT_BRACKET);
list.add(8, RIGHT_BRACKET);
}
} else {
int leftBegin = pos.getBegin() * 2;
list.add(leftBegin, LEFT_BRACKET);
list.add(leftBegin + 4, RIGHT_BRACKET);
}
} else {
BracePosition pos2 = bracePositions.get(1);
if (pos.range() == pos2.range()) {
int begin = 0;
list.add(begin, LEFT_BRACKET);
list.add(begin + 4, RIGHT_BRACKET);
list.add(begin + 6, LEFT_BRACKET);
list.add(10, RIGHT_BRACKET);
} else {
if (pos.range() < pos2.range()) {
int leftBegin = pos.getBegin() * 2;
list.add(leftBegin, LEFT_BRACKET);
list.add(leftBegin + 4, RIGHT_BRACKET);leftBegin = pos2.getBegin() * 2;
list.add(leftBegin, LEFT_BRACKET);
list.add(leftBegin + 6, RIGHT_BRACKET);
} else if (pos.range() > pos2.range()) {
int leftBegin = pos2.getBegin() * 2;
list.add(leftBegin, LEFT_BRACKET);
list.add(leftBegin + 4, RIGHT_BRACKET);leftBegin = pos.getBegin() * 2;
list.add(leftBegin, LEFT_BRACKET);
list.add(leftBegin + 6, RIGHT_BRACKET);
}
}
}
return list;
}
} -
题目生成器
package com.cyqwsj.arithmetic.service;
import com.cyqwsj.arithmetic.po.BracePosition;
import com.cyqwsj.arithmetic.service.impl.Expression;
import com.cyqwsj.arithmetic.service.impl.Number;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;import static com.cyqwsj.arithmetic.constant.OperationalConstant.*;
/**
* 题目生成器
*
* @author wsj
* @date 2020/10/9 22:47
*/
@Component
public class TopicGenerator {private Random random = new Random();
/**
* 生成问题列表
*
* @param totalNum 问题个数
* @param range 数字范围
* @return 问题列表
*/
public Listgenerate(int totalNum, int range) {
ListexpressionList = new LinkedList<>();
int i = 0;
while (i < totalNum) {
Expression expression = new Expression();int totalOperator = randInt(1, 3);
ListnumberList = generateNumList(totalOperator + 1, range);
ListoptList = generateOptList(totalOperator);
ListpositionList = generateBracketsPos(totalOperator); expression.setTotalOperator(totalOperator);
expression.setNumberList(numberList);
expression.setOperatorList(optList);
expression.setBracePositions(positionList);//产生的表达式有问题
if (expression.checkLegitimacy()) {
continue;
}
if (isRepeat(expressionList, expression)) {
continue;
}
expressionList.add(expression);
i++;}
return expressionList;
}/**
* 判断是否重复出题
*
* @param expressionList 题目列表
* @param expression 要判断的列表
* @return 是否重复
*/
private boolean isRepeat(ListexpressionList, Expression expression) {
for (Expression s : expressionList) {
if (expression.isRepeat(s)) {
return true;
}
}
return false;
}/**
* 随机生成一个分数
*
* @param range 范围
* @param isFraction 要分数还是自然数
* @return 生成的数
*/
private Number generateNum(int range, boolean isFraction) {
if (isFraction) {
return new Number(randInt(1, range), randInt(1, range));
} else {
return new Number(random.nextInt(range + 1));
}
}/**
* 随机生成运算符
*
* @return 运算符
*/
private String generateOpt() {
Listlist = new ArrayList<>(4);
list.add(PLUS);
list.add(SUBTRACT);
list.add(MULTIPLY);
list.add(DIVIDE);
return choice(list);
}/**
* 随机生成括号位置列表
*
* @param optNum 运算符个数
* @return 括号位置列表
*/
private ListgenerateBracketsPos(int optNum) {
ListpositionList = new LinkedList<>();
if (optNum < 2) {
return positionList;
} else if (optNum == 2) {
BracePosition pos1 = new BracePosition(0, 1);
BracePosition pos2 = new BracePosition(1, 2);
Listlist = new LinkedList<>();
list.add(pos1);
list.add(pos2);
positionList.add(choice(list));
return positionList;
} else if (optNum == 3) {
int begin;
int end;
//一个括号
if (random.nextBoolean()) {
do {
begin = random.nextInt(3);
end = randInt(begin + 1, 3);
} while (end - begin >= 3);
positionList.add(new BracePosition(begin, end));
return positionList;
//两个括号
} else {
//两个没有嵌套的括号
if (random.nextBoolean()) {
positionList.add(new BracePosition(0, 1));
positionList.add(new BracePosition(2, 3));
return positionList;
} else {
BracePosition pos = random.nextBoolean() ? new BracePosition(0, 2) : new BracePosition(1, 3);
begin = randInt(pos.getBegin(), pos.getEnd() - 1);
end = randInt(begin, pos.getEnd());
BracePosition pos1 = new BracePosition(begin, end);
positionList.add(pos);
positionList.add(pos1);
return positionList;
}
}
}
return null;
}/**
* 随机生成数字列表
*
* @param totalNum 要生成的数量
* @param range 范围
* @return 数字列表
*/
private ListgenerateNumList(int totalNum, int range) {
Listlist = new LinkedList<>();
for (int i = 0; i < totalNum; i++) {
list.add(generateNum(range, random.nextBoolean()));
}
return list;
}/**
* 随机生成运算符列表
*
* @param optNum 运算符数量
* @return 运算符列表
*/
private ListgenerateOptList(int optNum) {
Listlist = new LinkedList<>();
for (int i = 0; i < optNum; i++) {
list.add(generateOpt());
}
return list;
}/**
* 随机选择列表中的一项
*
* @param list 列表
* @param泛型
* @return 选择的结果
*/
privateT choice(List list) {
int i = random.nextInt(list.size());
return list.get(i);}
/**
* 返回范围内的随机数
*
* @param min 最小值
* @param max 最大值
* @return 随机数
*/
private int randInt(int min, int max) {
return random.nextInt(max) % (max - min + 1) + min;
}
}
六.运行测试
-
测试 -n -r 参数
![]()
-
运行结果
Exercise.txt
![]()
![]()
Answers.txt

- 测试 -e -a 参数
测试用例:Myapp -e D:\eda\Pair-Item-arithmetic\Exercises.txt -a D:\eda\Pair-Item-arithmetic\Answers.txt
预期结果:正确 7 错误 3
-
运行结果
![]()
-
生成10000道题目
![]()
七.总结
经过这次的结对项目,我们不再局限于是个人编程,而是作为一个小团队来进行开发,可以充分发挥出各自的长处。首先在做结对编程项目的时候,先要一起讨论题目以及整个项目的需求,再综合两个人的想法,得到最后的执行方案。
编码过程中,各自完成属于自己的部分并且进行测试,这大大提高了我们的效率。并且在开发过程中应及时沟通双方工作进程,当遇到推进困难问题可以双方一起解决,这样就不会落下进度。本次的项目虽然做的并没有那么完美,还有地方值得优化,但是本次的结对项目体验到了小团队开发的经历,学会相互合作,共同进步。








浙公网安备 33010602011771号