结对项目:用Java实现四则运算

发布时间 2023-09-28 22:13:58作者: stopyc
软件工程 计科2班
作业要求 结对编程
作业目标 1、尝试结对编程
2、通过实践和学习来提高自己在每个环节中的技能和经验
3、设计一个能自动生成小学四则运算题并批改题目的程序

GitHub地址:
https://github.com/stopyc/soft-work-operations3

@

一、成员

姓名 学号
鄞灿 3121005018
伊尔凡江·艾合买提 3121005017

二、psp表格

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

三、效能分析

1、改进思路

经过仔细反思,我们意识到了自己在这个问题实现过程可能可以优化。

2、性能分析图

3、消耗最大的函数

为用于去除重复,使用许多逻辑判断和嵌套循环,时间复杂度高,随生成数量提高而呈幂次增长
用于去除重复的函数:Equation.filter()

四、设计实现过程

1、代码设计

  • main
  • po包
    • Equation


  • Operand
  • Operator
  • util包
    • FileUtil
  • 项目结构

五、代码说明

1、关键代码

filter()
对存储表达式的指定数组做筛选,筛除不合格的表达式,返回存放合格表达式的数组。

        for(int i=0;i < list.size();i++){
            Equation equation = list.get(i);
            //如果运算过程含负数,则跳过
            if(equation.isOf()){
                list.remove(equation);
                //remove会整体前移
                i--;
                continue;
            }
            //和整个list比较
            //标签方便下面层层嵌套能直接goto出来
            flag:
            for(int o=0;o< list.size();o++){
                Equation toCompare = list.get(o);
                //删除后有空位,要跳过
                if(toCompare == null){
                    continue;
                }
                //遇到自己就跳过
                if(equation == toCompare){
                    continue;
                }
                //先比较结果
                if(Math.abs(equation.getResult() - toCompare.getResult()) < 0.000001) {
                    //结果相同,看是否完全一样
                    if(equation.equals(toCompare)){
                        list.remove(equation);
                        //remove会整体前移
                        i--;
                        break flag;
                    }
                    //再比较运算符
                    List<Arithmetic> postfix1 = equation.getPostfix();
                    List<Arithmetic> postfix2 = toCompare.getPostfix();
                    List<Operator> operators1 = equation.getOperators();
                    List<Operator> operators2 = toCompare.getOperators();
                    //有不同运算符就保留
                    if(operators1.size() != operators2.size()){
                        break flag;
                    }
                    for(int j=0;j<operators1.size();j++){
                        if(operators1.get(j) != operators2.get(j)){
                            break flag;
                        }
                    }

                    //运算符相同,只比较第一次计算的两数字是否交换位置
                    //找到第一个运算符,取前两个数字
                    List<Operand> operands1 = new ArrayList<>();
                    List<Operand> operands2 = new ArrayList<>();
                    for(int j=0;j<postfix1.size();j++){
                        if(postfix1.get(j) instanceof Operator){
                            operands1.add((Operand) postfix1.get(j-1));
                            operands1.add((Operand) postfix1.get(j-2));
                            break;
                        }
                    }
                    for(int j=0;j<postfix1.size();j++){
                        if(postfix2.get(j) instanceof Operator){
                            operands2.add((Operand) postfix2.get(j-1));
                            operands2.add((Operand) postfix2.get(j-2));
                            break;
                        }
                    }
                    //比较两对数字
                    if((operands1.get(0).equals(operands2.get(0)) || operands1.get(0).equals(operands2.get(1)))
                            && (operands1.get(1).equals(operands2.get(0)) || operands1.get(1).equals(operands2.get(1)))){
                        list.remove(equation);
                        //remove会整体前移
                        i--;
                        break flag;
                    }else{
                        //两对数字不相同,保留
                        break flag;
                    }
                }else{
                    //结果不一样,保留
                    break flag;
                }
            }
 
        }
        return list.stream().toList();
    }

generate()

生成随机中缀表达式,并返回

            , int lowEnd, int upEnd){
        Random r = new Random();
        int scope = upEnd - lowEnd;
        List<Arithmetic> arithmetics = new ArrayList<>();
        List<Operand> operands = new ArrayList<>();
        List<Operator> operators = new ArrayList<>();
        List<Brackets> brackets = new ArrayList<>();
 
        try {
            for (int i = 0; i < operandNo; i++) {
                // 操作数类型 自然数(0),真分数(1)
                int type = r.nextInt(10)%2;
                if(0 == type){
                    //生成随机整数
                    operands.add(new Operand(type, r.nextInt(scope) + lowEnd + ""));
                }else if (1 == type){
                    //生成真分数
                    int denominator = r.nextInt(scope) + lowEnd + 1;
                    // 分子 > 0
                    int numerator = r.nextInt(denominator - 1) + 1;
                    String str = numerator + "/" + denominator;
                    operands.add(new Operand(type, str));
                }
            }
 
            for (int i = 0; i < operatorNo; i++) {
                // 除去等号
                int index = r.nextInt(4) + 1;
                operators.add(Operator.getByIndex(index));
            }
 
            for (int i = 0; i < bracketsNo; i++) {
                brackets.add(Brackets.getByIndex(0));
                brackets.add(Brackets.getByIndex(1));
            }
 
            for (int i = 0; i < operands.size(); i++) {
                if(operands.get(i) != null){
                    arithmetics.add(operands.get(i));
                }
                if(i == operands.size()-1){
                    break;
                }
                if(operators.get(i) != null) {
                    arithmetics.add(operators.get(i));
                }
            }
 
            /*int type = r.nextInt(10);
            switch(operatorNo){
                case 2:
                    switch (bracketsNo){
                        case 0
                    };
                    break;
                case 3:
                    switch (bracketsNo){
                    }
                    break;
            }*/
 
 
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
        return new Equation(arithmetics);
    }

infixToPostfix()

将中缀表达式转换为后缀表达式

public List<Arithmetic> infixToPostfix(){
        Stack<Arithmetic> stack = new Stack<>();
        List<Arithmetic> postfix = new ArrayList<>();
        for(int start = 0; start < infix.size(); start++){
            //如果是运算符
            if(infix.get(start).priority > 0) {
                //栈空 或 "(" 或 符号优先级>栈顶符号 且 不为")" 直接进栈
                if (stack.isEmpty() || infix.get(start).priority == 3 ||
                        ((infix.get(start).priority > stack.peek().priority) && infix.get(start).priority < 4)) {
                    stack.push(infix.get(start));
                } else if (!stack.isEmpty() && infix.get(start).priority <= stack.peek().priority) {
                    //栈非空 且 符号优先级≤栈顶符号, 出栈; 直到 栈为空 或 遇到了"("
                    while (!stack.isEmpty() && infix.get(start).priority <= stack.peek().priority) {
                        if (stack.peek().priority == 3) {
                            stack.pop();
                            break;
                        }
                        postfix.add(stack.pop());
                    }
                    stack.push(infix.get(start));
                } else if (infix.get(start).priority == 4) {
                    //")",依次出栈直到空栈或遇到第一个"(",此时"("出栈
                    while (!stack.isEmpty()) {
                        if (stack.peek().priority == 3) {
                            stack.pop();
                            break;
                        }
                        postfix.add(stack.pop());
                    }
 
                }
            }else if(infix.get(start).priority == -1){
                postfix.add(infix.get(start));
            }
        }
        while(!stack.isEmpty()){
            postfix.add(stack.pop());
        }
        return postfix;
    }

2、思路说明

(1)filter()

  • 说明:用于过滤重复的表达式
  • 思路:按顺序层层筛选,由于转换成后缀表达式,不用考虑括号

1.先去除运算过程含负数的

2.先比较结果

3.比较表达式是否一样

4.再比较包含的运算符是否相同

5.比较第一次运算的两数是否只是交换位置

(2)generate()

  • 说明:用于生成随机表达式
  • 思路:通过传参确定此次生成中包含的操作数数量、运算符数量、括号数量、数的范围,然后随机new出各对象,交替拼接操作数和运算符,最后随机添加括号

(3)infixTOPostfix

  • 说明: 将中缀表达式转换成后缀表达式
  • 思路:
    无括号:
    1.扫描中缀表达式的每一个字符,将数字入列;
    2.遇到运算符,栈空时直接进栈,栈顶非空时,运算符优先级大于栈顶元素才进栈,否则栈顶元素退栈入列,当前运算符再进栈;
    3.依次进行直至所有字符操作完毕
    有括号:
    1.扫描中缀表达式的每一个字符,将数字入列;
    2.遇到运算符,栈空时直接进栈,栈顶非空时,运算符优先级大于栈顶元素才进栈,否则栈顶元素退栈入列,当前运算符再进栈;
    3.遇到左括号,直接进栈,左括号后面的运算符直接进栈,直至遇到右括号;
    4.遇到右括号时,将栈顶元素依次退栈入列,直到遇到左括号,将左括号退栈,符号操作移动下一位
    5.重复以上操作,直至所有字符操作完成。

六、测试运行

1、测试

(1)生成出一万个算式

(2)下载算式

(3)将生成的算式题目和答案下载



(4)统计出结果


七、项目总结

鄞灿:
最初认为这个项目很快就能完成,花了大约20分钟来构思整体思路。然而,实际的具体实现并不像预期那样迅速。此外,在去除重复表达式和添加多个小括号方面,我没有找到特别有效的方法,这导致耗费了很多时间。在结对编程方面,我的搭档能够在某些地方提供提示,帮助我找到更简洁的解决方案,但总的来说,由于需要进行讨论和熟悉项目,这也增加了一些成本。

伊尔凡江·艾合买提:
我正在学习Java语言,但缺乏实际项目来练手。这次的结对编程给了我一个绝佳的机会。一开始接到项目需求时,我有些茫然,不知道该从何处着手。通过查阅资料,我对这个项目的大致思路有了一定了解。我能够成功完成这个任务,在很大程度上得益于我的优秀搭档。他的编程能力比我强很多,在项目编写方面,他能够构思整个项目的编写思路,确定所需的主要函数甚至它们的实现思路。在大约一个下午的时间里,我的搭档向我解释了他的构想,解答了我的疑惑,并帮助我搭建了整体框架。这让我感觉到自己也能够胜任这个任务。最后,我非常感谢我的搭档,他的帮助让我对Java编程有了更深入的认识。