结对项目——自动生成小学四则运算题目

作业所属课程 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 numberList = new LinkedList<>(expression.getNumberList());
    temp.setNumberList(numberList);
    List optList = new LinkedList<>(expression.getOperatorList());
    temp.setOperatorList(optList);
    List 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 numberList = this.getNumberList();
    List otherNumberList = expression.getNumberList();
    if (numberList.containsAll(otherNumberList)) {
    List operatorList = this.getOperatorList();
    List 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 list = makeupString();
    for (String s : list) {
    stringBuilder.append(s);
    stringBuilder.append(" ");
    }
    stringBuilder.append("= ");
    return stringBuilder.toString();
    }

    /**
    * 构建输出字符串
    *
    * @return 字符串列表
    */
    private List makeupString() {
    List list = new ArrayList<>(7 + this.getBracePositions().size() * 2);
    List numberList = this.getNumberList();
    List 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 insertBrackets(List list) {
    List 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 generate(int totalNum, int range) {
    List expressionList = new LinkedList<>();
    int i = 0;
    while (i < totalNum) {
    Expression expression = new Expression();

    int totalOperator = randInt(1, 3);
    List numberList = generateNumList(totalOperator + 1, range);
    List optList = generateOptList(totalOperator);
    List 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 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 list = new ArrayList<>(4);
    list.add(PLUS);
    list.add(SUBTRACT);
    list.add(MULTIPLY);
    list.add(DIVIDE);
    return choice(list);
    }

    /**
    * 随机生成括号位置列表
    *
    * @param optNum 运算符个数
    * @return 括号位置列表
    */
    private List generateBracketsPos(int optNum) {
    List 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 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 generateNumList(int totalNum, int range) {
    List list = new LinkedList<>();
    for (int i = 0; i < totalNum; i++) {
    list.add(generateNum(range, random.nextBoolean()));
    }
    return list;
    }

    /**
    * 随机生成运算符列表
    *
    * @param optNum 运算符数量
    * @return 运算符列表
    */
    private List generateOptList(int optNum) {
    List list = new LinkedList<>();
    for (int i = 0; i < optNum; i++) {
    list.add(generateOpt());
    }
    return list;
    }

    /**
    * 随机选择列表中的一项
    *
    * @param list 列表
    * @param 泛型
    * @return 选择的结果
    */
    private T 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道题目

七.总结

经过这次的结对项目,我们不再局限于是个人编程,而是作为一个小团队来进行开发,可以充分发挥出各自的长处。首先在做结对编程项目的时候,先要一起讨论题目以及整个项目的需求,再综合两个人的想法,得到最后的执行方案。
编码过程中,各自完成属于自己的部分并且进行测试,这大大提高了我们的效率。并且在开发过程中应及时沟通双方工作进程,当遇到推进困难问题可以双方一起解决,这样就不会落下进度。本次的项目虽然做的并没有那么完美,还有地方值得优化,但是本次的结对项目体验到了小团队开发的经历,学会相互合作,共同进步。

posted @ 2020-10-12 22:25  EmmaJun  阅读(197)  评论(0)    收藏  举报