OO第一单元总结博客

陈思潮-22373112 学生 2024-03-23 12:00:53

OO第一单元总结

 

前言

  • OO第一单元的任务是表达式展开,通过三次迭代的开发,去满足逐渐增加的需求,在每一次的开发中我们除了要满足当前的需求,同时还要考虑程序是否做到了高内聚、低耦合,方便我们进行下一次的拓展。

基于度量的程序结构分析

代码规模

 

代码复杂度

 

指标解释

  • OCavg : 每个类中所有非抽象方法的平均圈复杂度(继承的方法不计算在内)。

  • OCmax : 每个类中非抽象方法的最大圈复杂度(继承的方法不计算在内)。

  • WMC : 每个类中方法的总圈复杂度.

复杂度原因分析

  • Mono的复杂度很高,是因为它承担了很多的任务,比如toString(),derivation()(求导),其中toString()的任务尤为的复杂,因为它需要考虑化简的问题如:exp内的括号消不消去,某些情况下有些地方是不是可以换一个表述形式,该不该提公因子……

    这些方法里面包含了大量的条件判断语句,所以复杂度会很高

  • Poly的复杂度的问题是,它作为Mono的上一层,需要配合Mono的toString()derivation(),除此之外Poly还负责了表达式的加减乘

最终UML图

 

 

  • 从图中我们可以看出,各部分功能清楚,关系之间很简单,从输入到输出有明显的路径,方便我们在其中进行拓展

  • Factor作为一个统一的接口,很好的满足了程序中优先factor需要储存其他的factor的这一需求,不需要我们再去重复的定义一个

架构设计体验

 

第一次作业

  • 本次作业我们是对一个包含加、减、乘、乘方以及括号(其中括号的深度至多为 1 层)的单变量表达式,输出恒等变形展开所有括号后的表达式。

    对于这样的任务,我们可以把它作如下的分解:

    • 建立预处理类Processor:题设中给的字符串是含有空格和连续加减号的,我们首先要做的是去掉空格和合并连续加减号,对于空格我们可以直接采用java中的replaceAll()的函数来解决,而对于合并连续的加减号,我采用的方法是遍历这个字符串,对于非加减号的部分,直接添加进新字符串中,遇到加减号,用一个值记录正负号,向后遍历直至遇到非加减号,根据正负号向新字符串中添加加减号,并添加非加减号的字符

    • 建立Exper,Term,Factor,Number,Power存储类来存储数据:其中Factor为接口,由Exper,Number,Power来实现它。在这些类中我们按照题意,Exper中存储着TermTerm中存储着Factor,此外我们还在这些类中设置了toPoly()函数,用来生成符合要求的多项式。

    • 建立解析类Praser,lexer:我们选择将解析这个过程单独建类,而不是在存储类中作为函数,是为了将数据结构和行为结构分开来。其中Lexer类作为词法解析,用来提取字符串中一个个token,Praser则是解析类,通过praseExper,praseTerm,praseFactor三个函数的递归调用来实现字符串的解析

      • Praser展式

        public class Parser {
          public Parser(Lexer lexer) 
          
          public Expr parseExpr()
            
          public Term parseTerm()
            
          public Factor parseFactor(int sign) { //解析表达式因子
                if (lexer.peek().equals("(")) {
                    return parseExperFactor(sign);
                } else if (lexer.peek().equals("+") || lexer.peek().equals("-")) {
                    Factor newOne;
                    if (lexer.peek().equals("+")) {…} else {
                        lexer.next();
                        newOne = parseFactor(-1);
                    }
                    newOne.changeSign(sign);
                    return newOne;
                } else if (lexer.peek().equals("x")) { //解析power因子
                    return parsePowerFactor(sign);
                } else if (lexer.peek().equals("exp")) { //解析ExpFactor
                    return parseExpFactor(sign);
                } else if (lexer.peek().equals("f") ||
                        lexer.peek().equals("g") || lexer.peek().equals("h")) {
                    return parseFunc(sign);
                } else if (lexer.peek().equals("dx")) {
                    return parseDerFunc(sign);
                } else {
                    return parseNumFactor(sign);
                }
            }
          
           public Factor parseExperFactor(int sign)
             
           public Factor parsePowerFactor(int sign)
             
           public Factor parseNumFactor(int sign)
             
           public Factor parseExpFactor(int sign)
           
           
        }

         

    • 建立多项式类Poly,Mono:如果我们直接由存储类来输出最终的字符串这是一个有难度的事,因为Exper因子需要被展开和合并Term需要做乘法,很明显我们需要新的结构来存储这些东西,其中Poly类存储着Mono类, Poly类中内置着多项式的加减乘除运算,存储类中都有toPoly()来实现向Poly类的转换,很明显toPoly()也是递归调用的,可以把toPoly()看成逆解析过程

      • poly展示

        public class Poly {
           private HashMap<String, Mono> monos;
          
           public Poly() {
                this.monos = new HashMap<>();
            }
          
           public HashMap<String, Mono> getHashMap() {
                return this.monos;
            }
          
           public void addMonoToPoly(Mono mono) {
                this.monos.put(mono.toString(), mono);
            }
          
           public Poly addPoly(Poly another) {…}
          
           public Poly polyMulMono(Mono item) {…}
          
           public Poly mulPoly(Poly another) {…}
          
           public Poly powPoly(int index) {…}
          
           public String toString() {…}
          
        }

         

第二次作业

  • 第二次作业比之前增加了自定义函数和指数函数,自定义函数通过先建立模版类,然后向其传递实参,来替换形参进行解析

  • 而exp的解析就比较复杂了,我们需要把括号内的内容作为一个poly存在exp,这就涉及到一个问题是,如果我们要比较俩个exp的大小,那么我们就需要比较poly,比较poly有三种方案

    • 一是我们用arraylist存mono,双重for循环,比较mono,但这设计到一个问题是,mono里面也是含有poly,也就是我们需要递归的去比较,这样感觉十分复杂

    • 二是我们如果用hashmap存mono的话,我们可以直接去比较hashmap是否相等,但这个就涉及了我们需要重写hashmap的equal的方法

    • 三是我们在将mono加入到poly时就调用它的tostring方法,来作为mono的键值,之后,我们poly的tostring,我们只需要把键值按照字典序加起来,就可以得到poly的最简string,我们只需要比较两个poly的string即可

    • poly的展示

    public class Poly {
       private HashMap<String, Mono> monos;
      
       public Poly() {
            this.monos = new HashMap<>();
        }
      
       public HashMap<String, Mono> getHashMap() {
            return this.monos;
        }
      
       public void addMonoToPoly(Mono mono) {
            this.monos.put(mono.toString(), mono);
        }
      
          public boolean isEqual(Poly poly) {
            String thisOne = this.toString();
            String anoOne = poly.toString();
            if (thisOne.equals(anoOne)) {
                return true;
            } else {
                return false;
            }
        }
      
      
      
       public String toString() {…}
      
    }

     

第三次作业

  • 第三次作业的任务是求导,初看时还感觉挺复杂的,但是仔细想一想,我们把求导的任务交给mono来作的话事情就会变得简单多了,因为每一个因子最后都会转化成一个mono,所以实际上我们只用写一次的求导,而不需要对每一个因子都写它的求导方法

  • 求导展示

    public class Poly {
      public Poly derivation() {
            Poly newOne = new Poly();
            for (Mono one : this.getHashMap().values()) {
                Poly mmp = one.derivation();
                newOne = newOne.addPoly(mmp);
            }
            return newOne;
        }
      
    }
    ​
    public class Mono {
        Poly newOne = new Poly();
        BigInteger polyCoe = this.coe;
        BigInteger polyIndex = this.index;
        if (!poly.toString().equals("0")) {…}
        BigInteger thisCoe = this.coe.multiply(this.index);
        BigInteger thisIndex = this.index.add(BigInteger.ONE.negate());
        if (!this.index.equals(BigInteger.ZERO)) {
             Mono coeMono = new Mono(thisCoe, thisIndex, this.poly);
             Poly coePoly = new Poly();
             coePoly.addMonoToPoly(coeMono);
             newOne = newOne.addPoly(coePoly);
        } else {
             Mono ccMono = new Mono(thisCoe, BigInteger.ZERO, this.poly);
             Poly ccPoly = new Poly();
             ccPoly.addMonoToPoly(ccMono);
             newOne = newOne.addPoly(ccPoly);
        }
        return newOne;
    }

     

重构与拓展

  • 本次实验我未经历重构,如果考虑拓展的话,我认为有如下两点

  • 首先是像上一届学长一样,添加三角函数,我只需要添加三角函数因子,在mono中再添几个变量来存三角函数内的内容,三角函数的计算我只需要修改poly中的加减乘方法,而两个poly是否相等比较,依然可以沿用之前的方法,我们可以直接打印poly的string来比较

  • 其次是函数定义中可以定义导数,这里我修改的地方就比较多了,因为我需要对函数模版求偏导,这意味着我需要解析函数模版,而不是仅用简单的字符串替换来处理

bug与测试

  • 本单元,我于第二单元被强测测出了一个bug,互测测出一个bug,两个bug十分神奇,强测的bug评测机的结果与本地结果不一样,互测的bug是调试的结果与运行的结果不同,我幸运的修复了这两个bug,虽然并不清楚背后的原因,我的bug修复十分简单,在我原来的程序中,如果我的两个mono运算后如果为0,那么我就不将其加入到作为结果的poly中,我仅仅将这个条件注释掉,就成功修复了bug,我猜测背后的原因是,我原本的行为可能导致hashmap为空,,从而触发什么异常

  • 测试:本单元我的测试分为两个部分

    • 单元测试:我用单元测试是来测我的优化功能的,本单元我们需要对输出的结果进行优化,而我的负责输出字符串和优化的模块恰好都是mono模块,所以我就对mono写了单元测试,来看我的程序是否会按照我所预料的那样优化

    • 普通的测试,我于第一次作业时按照学长的博客建了自己的评测机,后两次用的是同学的评测机,我自己搭建评测机的思路是,用python内置的解析字符串的函数来解析生成的表达式,和我程序化简的表达式,来看看他们是否相同

  • 对于互测,我的方法就是用评测机不断的去跑别人的程序,看看是否会问题,有问题就去化简一下评测机的数据来提交

优化

  • 指数为0是输出1

  • 系数为+-1时,不输出系数

  • 尽量让第一项不为负

  • 计算指数函数内的最大公因子,并将其提出,与不化简的长度作对比,再决定是否输出

每一次优化我都会做好单元测试,保证优化的正确性

心得体会

我觉得本次任务,让我明白了,我面对一个任务时,首先要想好这个任务的需求,根据需求设计合适的数据结构与方法结构,想好操作的对象进行编程。

同时在这里也感谢,往届学长们写的博客,让我面对这个任务时,不至于茫然无措,能对我有个思路上的引导

未来的方向

希望互测的cost再扩大些,也希望强测的结果能想我展示一个最简的优化1形式

...全文
39 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧