结对项目——自动生成小学四则运算题目
作业所属课程 | 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(); List<Number> numberList = new LinkedList<>(expression.getNumberList()); temp.setNumberList(numberList); List<String> optList = new LinkedList<>(expression.getOperatorList()); temp.setOperatorList(optList); List<BracePosition> positionList = 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()) { List<Number> numberList = this.getNumberList(); List<Number> otherNumberList = expression.getNumberList(); if (numberList.containsAll(otherNumberList)) { List<String> operatorList = this.getOperatorList(); List<String> otherOperatorList = 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(); List<String> list = makeupString(); for (String s : list) { stringBuilder.append(s); stringBuilder.append(" "); } stringBuilder.append("= "); return stringBuilder.toString(); } /** * 构建输出字符串 * * @return 字符串列表 */ private List<String> makeupString() { List<String> list = new ArrayList<>(7 + this.getBracePositions().size() * 2); List<Number> numberList = this.getNumberList(); List<String> operatorList = 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 List<String> insertBrackets(List<String> list) { List<BracePosition> bracePositions = 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 List<Expression> generate(int totalNum, int range) { List<Expression> expressionList = new LinkedList<>(); int i = 0; while (i < totalNum) { Expression expression = new Expression(); int totalOperator = randInt(1, 3); List<Number> numberList = generateNumList(totalOperator + 1, range); List<String> optList = generateOptList(totalOperator); List<BracePosition> positionList = 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(List<Expression> expressionList, 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() { List<String> list = new ArrayList<>(4); list.add(PLUS); list.add(SUBTRACT); list.add(MULTIPLY); list.add(DIVIDE); return choice(list); } /** * 随机生成括号位置列表 * * @param optNum 运算符个数 * @return 括号位置列表 */ private List<BracePosition> generateBracketsPos(int optNum) { List<BracePosition> positionList = new LinkedList<>(); if (optNum < 2) { return positionList; } else if (optNum == 2) { BracePosition pos1 = new BracePosition(0, 1); BracePosition pos2 = new BracePosition(1, 2); List<BracePosition> list = 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 List<Number> generateNumList(int totalNum, int range) { List<Number> list = new LinkedList<>(); for (int i = 0; i < totalNum; i++) { list.add(generateNum(range, random.nextBoolean())); } return list; } /** * 随机生成运算符列表 * * @param optNum 运算符数量 * @return 运算符列表 */ private List<String> generateOptList(int optNum) { List<String> list = new LinkedList<>(); for (int i = 0; i < optNum; i++) { list.add(generateOpt()); } return list; } /** * 随机选择列表中的一项 * * @param list 列表 * @param <T> 泛型 * @return 选择的结果 */ private <T> T choice(List<T> 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道题目
七.总结
经过这次的结对项目,我们不再局限于是个人编程,而是作为一个小团队来进行开发,可以充分发挥出各自的长处。首先在做结对编程项目的时候,先要一起讨论题目以及整个项目的需求,再综合两个人的想法,得到最后的执行方案。
编码过程中,各自完成属于自己的部分并且进行测试,这大大提高了我们的效率。并且在开发过程中应及时沟通双方工作进程,当遇到推进困难问题可以双方一起解决,这样就不会落下进度。本次的项目虽然做的并没有那么完美,还有地方值得优化,但是本次的结对项目体验到了小团队开发的经历,学会相互合作,共同进步。