结对项目

1、结对作业

这个作业属于哪个课程 班级链接
这个作业要求在哪里 作业链接
这个作业的目标 四则运算生成器+生成应用程序+结对
项目成员1 3119005418蓝泽凯(GitHub地址
项目成员2 3119005425刘原维(GitHub地址

2、PSP表格

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

3、效能分析


4、设计实现过程

4.1 主要类

  • Calculator类:存放执行生成题目和检测题目的方法
  • CalculatorUtils类:执行主要计算过程
  • ExpressionUtils类:生成题目
  • IOUtils类:读写文件
  • Window类:程序界面

4.2 类和方法关系图

5、代码说明

  1. 生成表达式
    利用栈配合随机数随即放入数字或符号,检验合法性
     //获取完整表达式,合法的数字、符号、括号顺序
     public static List<String> getExpression() {
         int numCount = random.nextInt(maxNumCount - 1) + 2;
         int opCount = numCount - 1;
         Stack<String> numStack = new Stack<>();
         Stack<Character> opStack = new Stack<>();
         Stack<String> numStack1 = (Stack<String>) numStack.clone();
         Stack<Character> opStack1 = (Stack<Character>) opStack.clone();
         //接收随机数和随机运算符;
         String[] nums = MathUtils.getNum(numCount);
         char[] operations = MathUtils.getOperation(opCount);
         //分别将数字和符号入栈
         for (String num : nums) {
             numStack.push(num);
         }
         for (Character op : operations) {
             opStack.push(op);
         }
    
         List<String> expressionList = new ArrayList<>();
         int leftC = 0;
         int leftNum = 0;
         while (!opStack.empty()) {
             expressionList.add(numStack.pop());
             leftNum++;
             if ((int) (Math.random() * 10) % 3 == 0 && leftC > 0 && leftNum > 1) {
                 expressionList.add(")");
                 leftC--;
                 expressionList.add(String.valueOf(opStack.pop()));
                 leftNum = 0;
                 break;
             }
             expressionList.add(String.valueOf(opStack.pop()));
             if ((int) (Math.random() * 10) % 4 == 0 && numStack.size() > 2) {
                 expressionList.add("(");
                 leftC++;
                 leftNum = 0;
             }
         }
         expressionList.add(numStack.pop());
         if (leftC > 0) {
             for (int j = 0; j < leftC; j++) {
                 expressionList.add(")");
             }
         }
         expressionList.add("=");
    
    
         return expressionList;
     }
    
    
  2. 进行计算
    为了保证按照题目需求生成的分数形式,算法模拟人处理计算分数的方法(即遇上分母不一样的分数时进行通分运算,最后用递归式辗转相除法进行约分化简)
     public static Stack<String> calculate(String expression) throws ZeroDivException, NegativeNumberException {
         String newException = expression.replace("=","");
         String[] parts = newException.split(" ");
         Stack<String> numStack= new Stack<>();
         Stack<String> opStack =new Stack<>();
         Stack<String> progressStack =new Stack<>();//过程栈
         for (String part : parts) {
             if (ExpressionUtils.isNum(part)){numStack.push(part);continue;}//如果是数字,直接入栈;
             if (ExpressionUtils.isOp(part)){
                 if (")".equals(part)){
                     while (!"(".equals(opStack.peek())){
                         String op = opStack.pop();//弹出运算符
                         String b = numStack.pop();//先弹出的是b
                         String a = numStack.pop();//后弹出的是a
                         String result = calculate(a,b,op);
                         numStack.push(result);
                         progressStack.push(result);//中间结果入栈
                     }
                     opStack.pop();
                     continue;
                 }//如果当前符号是右括号),一直弹栈直到遇到第一个左括号,把左括号弹出并结束循环
                 if (opStack.empty()){opStack.push(part);continue;}//如果运算符栈为空直接入栈
                 if ("(".equals(part)){opStack.push(part);continue;}
                 if (ExpressionUtils.priority(part)>ExpressionUtils.priority(opStack.peek())){//如果运算符栈不为空,判断优先级
                     opStack.push(part);//如果优先级高于当前栈顶
                 }else {//如果优先级不高于栈顶
                     while (!opStack.empty()){
                         if (ExpressionUtils.priority(opStack.peek())<ExpressionUtils.priority(part)){break;}//直到运算符栈顶运算符优先级低于当前的,或者
                         if ("(".equals(opStack.peek())){break;}//如果栈顶是左括号,将运算符入栈并跳出循环
                         String op = opStack.pop();//弹出运算符
                         String b = numStack.pop();//先弹出的是b
                         String a = numStack.pop();//后弹出的是a
                         String result = calculate(a,b,op);
                         numStack.push(result);//结果入栈
                         progressStack.push(result);//中间结果入栈
                     }
                     opStack.push(part);
                 }
             }
         }//将表达式全数处理完,入栈
         //将栈中剩余的内容计算完成
         while (!opStack.empty()){
             String op = opStack.pop();//弹出运算符
             String b = numStack.pop();//先弹出的是b
             String a = numStack.pop();//后弹出的是a
             String result = calculate(a,b,op);
             numStack.push(result);
             progressStack.push(result);//中间结果入栈
         }
     //        System.out.println("答案:"+numStack.pop());
    
         return progressStack;//过程栈记录了中间过程,栈顶是最终结果
     }
    
     public static String calculate(String a,String b,String op) throws ZeroDivException, NegativeNumberException {
         String result = "";
         switch (op){
             case "+" : result = add(a,b);break;
             case "-" : result = sub(a,b);break;
             case "×" : result = mul(a,b);break;
             case "÷" : result = div(a,b);break;
             default:result="中间值";
         }
         return result;
     }
    
  3. 查询重复
    根据每条表达式的运算符数量分成三条队列,把每一条表达式的每一步的运算结果用链表的形式存储,每一次进行运算都把链头入对应的队列,然后根据优化的二分法进行排序。新进来的链头查找队列里是否有第一步运算结果和自己一样的,有的话查看第二步运算结果,以此类推。若是查完发现表达式没有重复则在最后重复前的那一步结果那里开一个分支,将不重复的运算结果插进去并排序,以便后面的表达式进行查重。
    (优化的二分法原理:利用二分法加递归的原理,每次取出数组中的中间位置的值作为参照,然后再借助两个额外的数组。遍历原数组中的每个元素跟中间值(参照值)进行比较,把小于中间值的元素放在一个新数组中,相反把大于中间值的元素放在另一个新的数组中。这样一来其中一个新数组中的所有元素肯定都是小于中间值的,而另外一个新数组中的元素也必然都是大于中间值的。以此类推在把两个新的数组以同样的方式(可以采用递归)进行拆解,直到数组中只剩下一个元素为止,最后再把所有的小数组加所有的中间数再加上所有的大数组拼接在一起,就能得到结果了。)
    核心代码:
    public static int checkRepeat(List<MyBean> list, String result) {
         if (list == null || list.size() == 0) {
             return -1;
         }
         int left = 0;//左索引
         int right = list.size() - 1;//右索引
         String low = list.get(left).result;
         String high = list.get(right).result;
         try {
             String resultStr = CalculatorUtils.sub(result, low);
             int lowInt = CalculatorUtils.getInt(low);
             int highInt = CalculatorUtils.getInt(high);
             int gap = CalculatorUtils.getInt(resultStr);
             if (left == right) {
                 return list.get(left).result.equals(result) ? left : -1;
             }
             int mid = left + gap * (right - left) / (highInt - lowInt);
             return select(list, left, right, mid, result);
         } catch (Throwable e) {
             return -1;
         }
     }
    

6、测试用例

  1. 测试计算过程有负数出现

    public void testCalculate1() throws ZeroDivException, NegativeNumberException {
         MathUtils.setNumRange(10);
             String expression = "9’2/9 - 10’4/5 + 9 - 2’2/3 =";
             try {
                 System.out.print(expression);
                 System.out.print(CalculatorUtils.calculate(expression));
                 System.out.println();
             }catch (NegativeNumberException e){
                 System.out.println(e.getMessage());
             }
     }
    

  2. 测试是否为一个合法数字

    public void testIsNum() {
        System.out.println(ExpressionUtils.isNum("12#/2"));
        System.out.println(ExpressionUtils.isNum("12/2"));
        System.out.println(ExpressionUtils.isNum("1’2/82"));
        System.out.println(ExpressionUtils.isNum("12/2’3"));
        System.out.println(ExpressionUtils.isNum("12/2/3"));
        System.out.println(ExpressionUtils.isNum("1’22’3"));
        System.out.println(ExpressionUtils.isNum("1’2/2’3"));
        System.out.println(ExpressionUtils.isNum(""));
        System.out.println(ExpressionUtils.isNum("1’/2"));
        System.out.println(ExpressionUtils.isNum("1"));
        System.out.println(ExpressionUtils.isNum("33"));
        System.out.println(ExpressionUtils.isNum("/1"));
        System.out.println(ExpressionUtils.isNum("1’’//"));
    }
    

  3. 测试加法

    public void testAdd() {
        try {
            System.out.println(CalculatorUtils.add("4’6/3","3’2/3"));
        } catch (ZeroDivException e) {
            e.printStackTrace();
        }
    }
    

  4. 测试减法

    public void testSub() {
        try {
            System.out.println(CalculatorUtils.sub("3’1/2","3’3/5"));
        } catch (ZeroDivException | NegativeNumberException e) {
            e.printStackTrace();
        }
        try {
            System.out.println(CalculatorUtils.sub("4’1/2","3’3/5"));
        } catch (ZeroDivException | NegativeNumberException e) {
            e.printStackTrace();
        }
    }
    

  5. 测试乘法

    public void testMul() {
        try {
            System.out.println(CalculatorUtils.mul("2’3/5","3/5"));
        } catch (ZeroDivException e) {
            e.printStackTrace();
        }
    }
    

  6. 测试除法

    public void testDiv(){
        try {
            System.out.println(CalculatorUtils.div("3/5","2/5"));
        } catch (ZeroDivException e) {
            e.printStackTrace();
        }
    }
    

  7. 测试生成合法的随机数

    public void testGetNum() {
        MathUtils.setNumRange(10);
        for (int i = 0;i<10;i++){
            System.out.println(MathUtils.getNum());
        }
    }
    

  8. 测试生成的符号

    public void testGetOperation() {
        for (int i = 0;i<10;i++){
            System.out.println(MathUtils.getOperation());
        }
    }
    

  9. 测试生成的表达式

    public void testGetExpression() {
        MathUtils.setNumRange(10);
        for (int i = 0;i<10;i++) {
            System.out.println(ExpressionUtils.getExpression());
        }
    }
    

  10. 测试表达式是否合法

    public void testCheck() {
        boolean flag;
        flag = ExpressionUtils.check("1 ÷ 2’9/10 = ");
        System.out.println(flag);
    }
    

7. 项目小结

  1. 蓝泽凯小结
    本次项目没有合理规划结构,造成了结构冗余(使用了大量静态方法),对内存分配的理解不够深刻,算法知识储备不足,生成大量题目时效率大大下降,总之有待提升。
  2. 刘原维小结
    由于自身水平原因在本次结对项目中起到的作用不大,但在整个项目过程里面对于代码的编写、设计思路等都有很大的提高,学习到了非常多的东西。
posted @ 2021-10-25 23:22  .Soaring  阅读(39)  评论(0编辑  收藏  举报