结对项目:四则运算题目的命令行程序

结对项目:四则运算题目的命令行程序

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Networkengineering1834
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Networkengineering1834/homework/11148
这个作业的目标 实现一个自动生成的小学四则运算题目的命令行程序

一、项目详情

GitHub项目地址:https://github.com/MANGLULU/pairing-project.git


结对成员:邓发连(3118005321) 方泽凯(3118005322)

二、PSP 表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30 60
· Estimate · 估计这个任务需要多少时间 10 10
Development 开发 120 180
· Analysis · 需求分析 (包括学习新技术) 100 80
· Design Spec · 生成设计文档 30 30
· Design Review · 设计复审 30 30
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 60 60
· Design · 具体设计 60 60
· Coding · 具体编码 200 360
· Code Review · 代码复审 60 60
· Test · 测试(自我测试,修改代码,提交修改) 100 180
Reporting 报告 60 90
· Test Report · 测试报告 20 20
· Size Measurement · 计算工作量 20 20
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 30
· 合计 990 1270

三、接口的设计与实现过程

1.算法原理

逆波兰表达式:

  1、初始化两个栈:运算符栈s1和储存中间结果的栈s2
  2、从左到右扫描中缀表达式;
  3、遇到操作数,将其压s2;
  4、遇到运算符时,比较其与s1栈顶运算符优先级:
      (1)、如果s1为空,或者栈顶运算符为左括号“(”,则直接将此运算符入栈;
      (2)、否则,若优先级比栈比栈顶运算符的高,也将运算符压放s1;
      (3)、否则,将s1栈顶的运算符弹出并压入到s2,再次转到4.1与s1中新的栈顶运算符想比较;
  5、遇到括号时:
      (1)、如果是左括号“(”,则直接压入s1。
      (2)、如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃;
  6、重复步骤2至5,直到表达式的最右边;
  7、将s1中剩余的运算符依次弹出并压入s2;
  8、依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式;

2.接口设计

  FileFunction类:文件写入读出
        void saveFile(String path, StringBuilder context):将context写入路径为path的文件。
        Map<Integer, String> readFile(String path):将path的文件读出储存到Map集合。
  Calculate接口:计算四则运算的结果
        List<Fraction> listSuffix(List<Fraction> list):求运算式转成的后缀表达式。
        int letterPriority(Fraction symbol):获得运算符的优先级
        Fraction calculate(List<Fraction> list):计算后缀表达式的结果。
        Fraction calculateNum(Fraction num1, Fraction num2, String letter):计算四则基本运算的结果
        Fraction getResult(List<Fraction> list):获得算式最后的计算结果。
  ContrastService接口:
        List<List<Integer>> getTitle(Map<Integer, String> map1, Map<Integer, String> map2):获得答对和答错题目的题号。
        void writeResult(String answerFilePath, String actualFilePath):将检查后的结果写入批改文件。
  DigitalProcessingService接口:
        int getCommonMeasure(int element, int denominator):求最大公约数。
        String getProperFraction(Fraction fraction):化简分数。
        Fraction getNumber(int number):获得随机数。
        String getSymbol():获得随机符号。
  Fraction类:
        Fraction ADD(Fraction number1, Fraction number2):加法。
        Fraction Subtract(Fraction number1, Fraction number2):减法。
        Fraction Multiply(Fraction number1, Fraction number2):乘法。
        Fraction Division(Fraction number1, Fraction number2):除法。
        QuestionsController类:
        void getQuestions(int count, int number):调用相关方法生成符合要求的四则运算表达式。

3.程序流程

逆波兰表达式流程图:

项目流程图:

4.项目结构:

4.关键代码分析

定义实体类:将所有数值用分数形式表示,再通过化简成整数,分数,真分数。

  /**
  * @Author DengFaLian
  * @Date 2020/10/2 22:33
  * @Version 1.0
  */
  public class Fraction {

  /*分子*/
  private int element;

  /*分母*/
  private int denominator;

  /*符号*/
  private String symbol;

  /*getter and setter*/

  /**
  * 加法
  * @param number1
  * @param number2
  * @return
  */
  public Fraction ADD(Fraction number1, Fraction number2) {
        Fraction answer = new Fraction();
        answer.setElement(number1.getElement() * 
  number2.getDenominator() + number1.getDenominator() *             
  number2.getElement());
        answer.setDenominator(number1.getDenominator() * 
  number2.getDenominator());
        return answer;
    }


  /**
   * 减法
   * @param number1
   * @param number2
   * @return
   */
  public Fraction Subtract(Fraction number1, Fraction number2)             
  {
      Fraction answer = new Fraction();
      if ((number1.getElement() * number2.getDenominator() - number1.getDenominator() * number2.getElement()) < 0)
        throw  new RuntimeException("运算过程中不能有负数");//如果运算结果是负数,抛出异常。
      answer.setElement(number1.getElement() * number2.getDenominator() - number1.getDenominator() * number2.getElement());
      answer.setDenominator(number1.getDenominator() * number2.getDenominator());
      return answer;
  }

  /**
   * 乘法
   * @param number1
   * @param number2
   * @return
   */
  public Fraction Multiply(Fraction number1, Fraction number2)       
  {
      Fraction answer = new Fraction();
      answer.setElement(number1.getElement() * number2.getElement());
      answer.setDenominator(number1.getDenominator() * number2.getDenominator());
      return answer;
  }

  /**
   * 除法
   * @param number1
   * @param number2
   * @return
   */
  public Fraction Division(Fraction number1, Fraction number2) 
  {
      Fraction answer = new Fraction();
      if (number2.getElement() == 0) throw new RuntimeException("除数不能为0");
      answer.setElement(number1.getElement() * number2.getDenominator());
      answer.setDenominator(number1.getDenominator() * number2.getElement());
      return answer;
  }

获得逆波兰表达式

  @Override
  public List<Fraction> listSuffix(List<Fraction> list) {
    Stack<Fraction> letterStack = new Stack<>();
    List<Fraction> result = new ArrayList<>();
    for (Fraction str:list){
        /*操作数*/
        if(str.getSymbol() == null){
            result.add(str); //加入逆波兰表达式集合
            continue;
        }
        /*左括号*/
        else if(str.getSymbol().equals("(")){
            letterStack.push(str); //入栈
            continue;
        }
        /*右括号*/
        else if(str.getSymbol().equals(")")){
            while(!letterStack.peek().getSymbol().equals("(")){
                result.add(letterStack.pop()); //出栈到逆波兰表达式集合
            }
            letterStack.pop(); //“(”出栈
            continue;
        }
        /*运算符*/
       else if(letterStack.isEmpty() || letterStack.peek().getSymbol().equals("(")){
            letterStack.push(str);  //入栈
            continue;
        }
       else if(letterPriority(str) > letterPriority(letterStack.peek())){
            letterStack.push(str); //优先级高的入栈
        }
       else{
            while(!letterStack.isEmpty() && !letterStack.peek().getSymbol().equals("(") && letterPriority(str)<=letterPriority(letterStack.peek())){
                result.add(letterStack.pop());      //把栈中优先级低的元素出栈到逆波兰表达式
            }
            letterStack.push(str);//优先级高的入栈
        }

    }

    while (!letterStack.isEmpty()){
        result.add(letterStack.pop());      //栈中剩余的元素出栈,加入到逆波兰表达式
    }
    return result;

}

计算逆波兰表达式的结果:

  public Fraction calculate(List<Fraction> list) {
    Stack<Fraction> calculateStack = new Stack<>();
    Fraction num1 = null;
    Fraction num2 = null;
    for (Fraction str:list){ //遍历逆波兰表达式
        if(str.getSymbol() == null){
            calculateStack.push(str);      //将数值入栈
        }else{
            num1 = calculateStack.pop();      //有运算符则出栈两个数值
            num2 = calculateStack.pop();
            calculateStack.push(calculateNum(num2,num1,str.getSymbol()));//进行运算,并且将结果重新入栈
        }
    }
    return  calculateStack.pop();//返回最后结果
}

四、性能分析

.类的内存消耗:从图中可以看出,程序所消耗的内存比较小,程序对内存的要求不高。

.CUP Load

.堆内存:从图中可以看到项目中char和string类型使用的比较多,其中char类型主要用于运算符号的判断和括号的判断,String类型运用与整个程序中,所以占比较大。

.程序中消耗最大的方法:从图中可以看出,程序中消耗最大的方法是getQuestions()方法,,因为大多数的方法都是为生成题目所用。

五、计算模块部分单元测试展示

1.测试代码

测试类结构:

测试方法:






2.生成1w道题目的测试结果

3.部分结果展示

题目:Exercise.txt :题目生成后写入Exercise.txt文件

答案:Answer.txt :题目生成的同时生成答案写入Answer.txt文件。

答题:ExerciseFile.txt :生成题目后,根据题目写出答案写入ExerciseFile.txt文件,方法当前目录下

批改结果:Grade.txt :输入批改命令后生成结果写入Grade.txt文件

4.测试结果展示

全部测试通过

六、总结

邓发连:
这次的结对项目总体来说是比较成功的,首先为了方便,我们先通过沟通选定了实现的方法,通过讨论先把接口写好,每个接口对应相应的功能。然后我写方法的具体实现,方泽凯写方法的测试。测试后的结果反馈给我修改代码。结对编程没有个人编程那么方便,想写什么就写什么,但是它可以让自己认识到自身的不足,一个人的想法还是有限的。这次的结对项目让我学会了如何和别人高效的合作,怎样才能提高合作开发的效率,同时也认识到了结对编程的好处。
方泽凯:
这是我第一次接受结对编程,受益良多。在结队编程时,邓发连同学将需求列出,根据难度不同从而安排开发流程,然后我们一起讨论,有时候自己卡住的时候,别人的建议会让自己思维变得更加清晰,在写代码的过程中,两个人的思维碰撞会产生意外的结果,有一些细节的地方自己没有办法想到,就可以由另外一个人填补,两个人提高了程序的设计与编写的速度。最大的收获是提高了沟通能力,简化开发流程,提高效率。

posted @ 2020-10-12 21:55  瓦中意鲁  阅读(177)  评论(0)    收藏  举报