ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

结对项目

2020-10-12 20:00:58  阅读:156  来源: 互联网

标签:结对 项目 getRchild 结点 else 运算符 str 表达式


结对项目

软件工程 班级网址
作业要求 作业要求代码
作业目标 论文查重、代码测试、git管理、博客总结

合作者

黄博晓 信息安全 (1) 班 学号 : 3118005363

凌文宇 信息安全 (1) 班 学号 : 3118005372

作业代码链接

github 网址 : https://github.com/bxxiao/bxxiao/tree/master/ArithmeticGeneratorProject

PSP 表格

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

1. 设计实现过程

包结构:

image-20201012120622839
  • expression包:一个Expression对象表示一个四则表达式,其中提供了随机创建表达式、检验表达式、获取表达式运算结果和返回表达式字符串形式等方法。一个四则表达式封装为一个二叉树,TreeNode是二叉树的结点,每个结点保存一个运算符或运算数。

  • util包:

    • Calculator:主要提供了针对整数、分数的加减乘除运算、以及获取分数的最简形式的静态方法。运算结果以字符串形式返回。返回形式有整数和分数(如1/3,2'5/6)形式。

    • Ran:用于生成随机值。包括随机生成运算符、随机生成运算数(整数和分数)以及随机生成运算符在二叉树中的位置。

    • CommandResolver:命令解析器。解析main方法的args参数,获取其中的有用参数。若输入形式不符合要求,会打印提示信息。若输入形式符合要求,则获取其中指定的题目数和最大值并保存。

    • FilteUtil:用于将生成的题目跟答案写入文件。写入的格式如下:

      1. exp1 = or answer1
      2. exp2 = or answer2
      3. exp3 = or answer3
      ...
      
  • generator包:

    • Generator:提供了一个generate()方法,使用CommandResolver解析输入的命令,获取题目数和最大值,根据这两个值创建表达式和对应的答案,并使用FilteUtil将题目和答案写入当前目录下的expFile.txt和answers.txt文件(若不存在会创建)。在创建每个表达式之前,先随机生成一个1-3的整数,作为运算符个数。
    • Main:包含main方法。
  • test包:包含一个测试类。(使用Junit)

2. 代码说明

2.1 表达式的字符串形式

一个表达式对应的二叉树是一个完全二叉树,其中,叶子结点都是运算数,父结点都是运算符。对这样的二叉树进行中序遍历可得到一个运算表达式,且每次返回父结点都会带上括号,如:

image-20201012193135414

遍历结果是((1+2)-(3x4))

再通过以下原则去除多余的括号

假设待去括号的表达式为(m1 op1 n1) op (m2 op2 n2)这里m1、n1、m2、m2可能自身就是个表达式,也可能是数字,op、op1、op2为运算符

1、若op为'÷',则 (m2 op2 n2)的括号必须保留;

2、若op为'x'或'-',如果op2为'+'或'-',则(m2 op2 n2)的括号必须保留;

3、若op为'x'或'÷',如果op1为'+'或'-',则(m1 op1 n1)的括号必须保留;

4、 除此之外,去掉括号不影响表达式的计算顺序。

如上面例子去掉多余括号后变为:1+2-3x4

将当前二叉树按以上规则转换为字符串形式的方法在TreeNode中:

public String toString(){
    String leftExp = "";//左表达式
    String rightExp = "";//右表达式
    String result = "";//运算符

    //若当前结点有孩子
    if(hasChild()){
        //右子树如果有孩子,说明右子树是一个表达式,而不是数字节点。
        if(getRchild().hasChild()){
            //判断左邻括号的运算符是否为'/'
            if(str.equals("÷")){
                //获取右子树的表达式,保留括号
                rightExp = getRchild().toString();
            }
            //判断左邻括号的运算符是否为'x'或'-'
            else if(str.equals("x") || str.equals("-")){
                //判断op是否为'+'或'-'
                if(getRchild().str.equals("+") || getRchild().str.equals("-")){
                    rightExp = getRchild().toString();
                }
                else{
                    //获取右子树的表达式,并且去括号
                    rightExp = getRchild().toString().substring(1, getRchild().toString().length()-1);
                }
            }
            else{
                //右子树除此之外都是可以去括号的。
                rightExp = getRchild().toString().substring(1, getRchild().toString().length()-1);
            }
        }
        else{
            rightExp = getRchild().str;
        }

        //左子树的情况同右子树类似
        if(getLchild().hasChild()){
            if(str.equals("x") || str.equals("÷")){
                if(getLchild().str.equals("+") || getLchild().str.equals("-")){
                    leftExp = getLchild().toString();
                }
                else{
                    leftExp = getLchild().toString().substring(1, getLchild().toString().length()-1);
                }
            }
            else{
                leftExp = getLchild().toString().substring(1, getLchild().toString().length()-1);
            }
        }
        else{
            leftExp = getLchild().str;
        }
        //获取当前的运算式,并加上括号
        result = "(" + leftExp + " " + this.str + " " + rightExp + ")";
    }
    else{
        //若没有孩子,说明是数字节点,直接返回数字
        result = this.str;
    }
    return result;
}

2.2 随机生成表达式

2.2.1 随机创建表达式 

随机生成二叉树的思路:

  • 先随机生成一个运算符作为根结点。若只有一个运算符,直接生成两个运算数作为根节点的左右孩子。
  • 若有2或3个运算符:
    • 先根据运算符个数随机获取运算符结点位置的boolean数组(见下文getChildPlace())。
    • 遍历该boolean数组,若为true,则要创建一个表达式子树(运算符为父结点,运算数为其左右孩子);若为false,则创建一个运算数子树(只有一个结点)。
    • 在上述遍历中设置一个遍历索引i,若i为偶数,则创建的子树作为"当前结点"的左孩子,否则为右孩子。且每创建一个作为左孩子的表达式子树,则该子树的根节点作为“当前结点”(当前结点从第一步生成的根节点开始)...
    • 这种方式创建子树能保证最后生成的二叉树是完全二叉树。
  • 创建完表达式,调用CalAndVal()对表达式校验。
public void createExpression(){
    TreeNode lchild, rchild, lnode, rnode;

    //只有一个运算符
    if(opCounts == 1){
        lchild = new TreeNode(Ran.getNumber(max), null, null);
        rchild = new TreeNode(Ran.getNumber(max), null, null);
        root = new TreeNode(Ran.getOperator(), lchild, rchild);
    }
    else{
        int num1 = 0;
        boolean[] place = Ran.getChildPlace(opCounts);
        root = new TreeNode(Ran.getOperator(), null, null);
        opeList.add(root);

        for(int i = 0; i < place.length; i++){
            //place[i]为true,生成一个运算符,并为该运算符生成两个左右孩子运算数
            if(place[i]){
                lnode  = new TreeNode(Ran.getNumber(max), null, null);
                rnode  = new TreeNode(Ran.getNumber(max), null, null);
                //i为偶数(从0开始的),将新创建的运算符作为当前结点(运算符)的左孩子;为奇,则作为右孩子
                if(i%2 == 0){
                    lchild = new TreeNode(Ran.getOperator(), lnode, rnode);
                    opeList.add(lchild);
                    opeList.get(num1).setLchild(lchild);
                }
                else{
                    rchild = new TreeNode(Ran.getOperator(), lnode, rnode);
                    opeList.add(rchild);
                    opeList.get(num1).setRchild(rchild);
                }
            }
            //place[i]为false,则只生成一个运算数,组装到当前运算符的左右孩子
            else{
                if(i%2 == 0){
                    lchild = new TreeNode(Ran.getNumber(max), null, null);
                    opeList.get(num1).setLchild(lchild);
                }
                else{

                    rchild = new TreeNode(Ran.getNumber(max), null, null);
                    opeList.get(num1).setRchild(rchild);
                }
            }
            //num1为偶数,说明当前结点还未生成有孩子,num1不变;若是奇数,则+1
            num1 = num1 + i%2;
        }
    }
    CalAndVal();//生成完二叉树进行校验
}

2.2.2 随机生成运算符结点位置

Ran类中包含一个根据运算符个数随机生成运算符结点位置的方法,其实现思路:

  • 方法返回返回一个长度为2的boolean数组,true元素的个数表示要新创建的运算符个数。
    • 在随机生成表达式的方法中会使用到该方法,true表示在当前结点中,创建一个以运算符为父结点,两个运算数为叶子结点的子二叉树,作为当前结点的左或右孩子;false则表示创建一个运算数,作为当前结点的左或右孩子。
  • 若运算符个数为2,则需要再创建一个运算符,所以true,false各一个。
  • 运算符个数为3,则需要再创建两个运算符。元素都是true。
public static boolean[] getChildPlace(int num){
    boolean[] result = new boolean[]{true, true};

    if(num==2){
        Random random = new Random();
        if(random.nextBoolean()){
            result[0] = false;
        }else {
            result[1] = false;
        }
    }

    return result;
}

2.2.3 校验表达式

校验表达式包括对除数为0,减法运算结果为负的情况的处理。

  • 若除数为0,替换除号为其他运算符。
  • 若减法结果为负,交换被减数和减数的位置。
  • CalAndVal()实际调用了getResult()方法,该方法获取当前结点对应的运算结果并按以上规则校验。

public String getResult(){
    //若有子节点,说明当前结点是运算符
    if(hasChild()){
        switch(str){
            case "+":
                return Calculator.add(getLchild().getResult(), getRchild().getResult());
            case "-":
                String subResult = Calculator.sub(getLchild().getResult(), getRchild().getResult());
                //若返回值为-1,交换左右孩子
                if(subResult.contains("-")){
                    swapChild();
                    return Calculator.sub(getLchild().getResult(), getRchild().getResult());
                }
                else {
                    return subResult;
                }
            case "x":
                return Calculator.mul(getLchild().getResult(), getRchild().getResult());
            case "÷":
                String divResult = Calculator.division(getLchild().getResult(), getRchild().getResult());
                //返回结果为-1,表示除数为0,替换运算符
                if(divResult.contains("-")){
                    while(str.equals("÷")){
                        //将当前运算符转换为其他运算符
                        str = Ran.getOperator();
                    }
                    return this.getResult();
                }
                else {
                    return divResult;
                }
        }
    }
    //无子节点,说明当前结点是叶子结点,直接返回结点值
    return str;
}

3. 测试

输入命令-n 6 -r 10(生成6道题目,最大值为10(不包括10)),生成的题目和答案如下:

题目:(在当前目录下的expFile.txt中)

image-20201012164901696

答案:(在当前目录下的answer.txt中)

image-20201012164924487

4. 小结

4.1 黄博晓

  • 跟别人合作开发要注意沟通好。

  • 数据结构很重要。

  • 使用搜索引擎有时可以极大的提高效率...

4.2 凌文宇

  • 基础真的很重要,以后要继续努力才能更好的和队友合作

  • 全程大佬带飞,以后要继续加油,争取不拖后腿

  • 事先的沟通与安排是必不可少的

参考链接

https://www.cnblogs.com/echoing/p/7878954.html

https://blog.csdn.net/kuku713/article/details/12684509

标签:结对,项目,getRchild,结点,else,运算符,str,表达式
来源: https://www.cnblogs.com/ltfls/p/13804899.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有