BUAAOO-第一单元总结

BUAA-OO第一单元总结

第一次作业

  • 需求分析

  • 第一次作业要求我们对一个只由幂函数组成的表达式求导,并输出一个表达式,且输出的表达式长度越短越好。

  • 实现思路

  • 第一次作业难度较小,我将实现拆分成三个步骤,首先是将所有空白符去掉,利用正则表达式对输入的表达式进行解析,利用正则表达式匹配每一个项和项前面的符号,将表达式分解成一个个符号与项的组合。然后是将分解出来的符号与项进行处理,利用HashMap储存各个项,其中key为幂函数的指数,这样可以在处理的过程中就将项进行合并。最后是遍历每一个项并对每一个项进行求导,生成一个新的HashMap,并进行输出。

  • 类图分析

    • 由类图可以看出,在具体的实现上,我建立了两个类,一个Item类用来表示一个,另一个Expression类,通过HashMap保存各个Item组成表达式。在主函数中通过正则表达式对表达式进行解析并在主函数中创建Expression和Item,调用相应的方法进行表达式的求导。
    • 可以看出在第一次作业中由一丝层次架构的味道,只是还是由较多的面向过程的思想在里面,封装性不够好,可拓展性较差。这也导致了我第二次作业直接重写了代码。
  • 度量分析

    属性个数 方法个数 方法规模 分支数目 代码规模
    Expression 1 2 良好 良好 38行
    Item 2 3 良好 良好 32行
    Main 0 1 良好 良好 20行

    • 由度量图可看出,第一次作业的代码由于较为简短,且难度较为简单,类之间的耦合度相对不高,有不算差的内聚性。
  • 优化分析

    • 本次作业由于采用HashMap储存,在正常的求导结束后的输出便已经是优化好的了,便觉得已经算是可以了。但没想到还有把正项提前和x**2换成x*x的方法,导致优化被扣了一些分。
  • bug分析

    • 可能由于本次作业较为简单,在本次中测、强测和互测中都没有发现bug,同room的人中也没有人发现bug。

第二次作业

  • 需求分析

  • 相对与第一次作业,本次作业增加了三角函数和表达式因子,难度相对上一次作业增加了不少。

  • 实现思路

  • 本次作业采用了表达式树的方法,还是先将所有的空白符去掉,然后表达式转换成后缀表达式,再通过后缀表达式建立表达式树,通过对树根节点的求导带动整颗表达式树的求导。具体实现见类图的分析。

  • 类图分析

    • 由类图可以看出,本次的复杂度相较于上一次有了指数级的提升,难度确实大了不少。好在理清思路后实现起来并不困难。
    • 首先我建立了一个Factor类用来代表所有的因子、运算符和树的节点,三角函数、幂函数、运算符、带符号数和树节点的类都继承自Factor,并且都实现了各自的求导方法
    • 然后在通过PosExpression类建立后缀表达式的过程中,对表达式进行解析,提取出每一个因子或操作符,利用简易的工厂Factory对提取到的字符进行解析,返回对应的因子或操作符类,成为一个节点放入用来存储后缀表达式的ArrayList<Factory>中,表达式遍历结束后便构建了一个后缀表达式。
    • 接着在ExpTree类中将后缀表达式转换成表达式树,并在ExpTree中实现一个返回根节点的方法,然后在主函数中调用该方法并对该根节点进行求导。由于节点的求导方法即为操作符或因子的求导方法,操作符的求导方法如“+”为调用左边的求导方法获取其返回值加上右边的求导方法的返回值,类似这样,便可从根节点的求导方法开始遍历完整棵表达式树并返回表达式求导的结果。
    • 对于表达式的存储,和第一次作业一样也是利用HashMap<MapKey, Term>进行存储,而HashMap的key值为我写的MapKey,其重载了hashcode()和equals()方法,是一个不可变对象,保存的是三角函数和幂函数的指数
    • 对于该架构的优缺点,优点是实现起来较为容易,表达式树的概念在大一已经接触过而且用C语言写过了。而缺点就是不容易优化,且对树的递归遍历容易出现TLE。在研讨课上已经由一些同学指出自己利用表达式树的架构造成了TLE。
  • 度量分析

    属性个数 方法个数 方法规模 分支数目 代码规模
    PosExpression 11 3 构造函数过大 ifelse过多 91行
    ExpTree 2 2 良好 良好 28行
    Exp 1 7 良好 良好 72行
    MapKey 4 9 良好 ifelse过多 91行
    Term 1 3 良好 良好 19行
    Factor 7 19 良好 ifelse过多 113行
    Coe 0 2 良好 良好 14行
    CosX 0 2 良好 良好 21行
    SinX 0 2 良好 良好 21行
    MiX 0 2 良好 良好 21行
    AddSub 0 4 良好 良好 30行
    Mul 0 4 良好 良好 24行
    Factory 0 1 良好 良好 22行
    Main 0 2 良好 良好 41行
    Method CogC ev(G) iv(G) v(G)
    AddSub.AddSub(String) 0 1 1 1
    AddSub.diff() 1 2 2 2
    AddSub.getType() 0 1 1 1
    AddSub.toExp() 1 2 2 2
    Coe.Coe(String) 0 1 1 1
    Coe.diff() 0 1 1 1
    CosX.CosX(String) 3 1 3 3
    CosX.diff() 0 1 1 1
    Exp.Exp() 0 1 1 1
    Exp.Exp(MapKey) 0 1 1 1
    Exp.add(Exp) 2 1 3 3
    Exp.addTerm(MapKey) 2 1 2 2
    Exp.mul(Exp) 3 1 3 3
    Exp.sub(Exp) 2 1 3 3
    Exp.toString() 1 1 2 2
    ExpTree.ExpTree (ArrayList) 4 1 3 3
    ExpTree.getTop() 0 1 1 1
    Factor.Factor() 0 1 1 1
    Factor.diff() 0 1 1 1
    Factor.getCoe() 0 1 1 1
    Factor.getCosInd() 0 1 1 1
    Factor.getLeft() 0 1 1 1
    Factor.getMiInd() 0 1 1 1
    Factor.getOp() 0 1 1 1
    Factor.getRight() 0 1 1 1
    Factor.getSinInd() 0 1 1 1
    Factor.getType() 0 1 1 1
    Factor.setCoe(BigInteger) 0 1 1 1
    Factor.setCosInd(BigInteger) 0 1 1 1
    Factor.setLeft(Factor) 0 1 1 1
    Factor.setMiInd(BigInteger) 0 1 1 1
    Factor.setOp(String) 0 1 1 1
    Factor.setRight(Factor) 0 1 1 1
    Factor.setSinInd(BigInteger) 0 1 1 1
    Factor.toExp() 0 1 1 1
    Factor.toString() 13 2 9 9
    Factory.getFactor(String) 9 8 7 9
    Main.dealExp(String) 2 1 2 3
    Main.main(String[]) 3 1 3 4
    MapKey.MapKey (BigInteger,BigInteger, BigInteger,BigInteger) 0 1 1 1
    MapKey.equals(Object) 1 1 3 3
    MapKey.getCoe() 0 1 1 1
    MapKey.getCosInd() 0 1 1 1
    MapKey.getMiInd() 0 1 1 1
    MapKey.getSinInd() 0 1 1 1
    MapKey.hashCode() 0 1 1 1
    MapKey.nuMap() 0 1 1 1
    MapKey.toString() 15 6 14 14
    MiX.MiX(String) 3 1 3 3
    MiX.diff() 0 1 1 1
    Mul.Mul() 0 1 1 1
    Mul.diff() 0 1 1 1
    Mul.getType() 0 1 1 1
    Mul.toExp() 0 1 1 1
    PosExpression. PosExpression(String) 36 1 25 26
    PosExpression.getPos() 0 1 1 1
    PosExpression.pr(String) 3 3 3 4
    SinX.SinX(String) 3 1 3 3
    SinX.diff() 0 1 1 1
    Term.Term(BigInteger) 0 1 1 1
    Term.add(Term) 0 1 1 1
    Term.getCoe() 0 1 1 1
    • 由度量分析可知,PosExpression的构造方法PosExpression(String)过于冗长,这是由于我用此构造方法来将表达式转化成后缀表达式,想不到比较好的方法,便用if-else语句进行项的提取,由于情况过于复杂,写了很多的判断语句,导致方法十分冗长和复杂。目前我也还没想到有什么比较好的方法,这点比较遗憾。其他的主要是toString方法较长,由于特判了0、1等多种情况导致toString的方法长度相对长了一些。
  • 优化分析

  • 本次作业由于我将所有项也用HashMap储存了起来,也相当于将同类项进行了合并操作,也算是做了优化。但由于时间和实力实在不够,在保证正确性的前提下便放弃了优化。对于讨论区大佬们的各种化简方法也能是看看了。

  • bug分析

    • 本次作业我在强测、互测中都没被发现bug。对于其他人的bug,通过评测机自动测试找到了几个。其中并没有找到求导性错误,错误为其中一个对于求导结果为0的表达式没有输出,另一个输出了 *-x的错误格式。可见对于这些小细节的地方也是不容忽视的。

第三次作业

  • 需求分析

  • 本次作业相对于上次,增加了嵌套函数的处理,并且需要进行格式判断。难度进一步提高。

  • 实现思路

    • 对于求导,沿用第二次作业的框架,在判断三角函数时,在三角函数内部再生成一棵表达式树,调用三角函数的求导函数时,在调用一次表达式树的求导。
    • 对于格式判断,采用了先判断空白字符的合法性。空白字符只有在指数的之间、sin和cos之间带符号整数之间才会不合法,因此对于空白字符的判断就集中于这三个地方。合法之后将空白字符删去,利用递归下降**对表达式进行格式判断。
    • 对于递归下降判断合法性,是从表达式到项到因子的层层递进又来回循环。将在类图分析里细讲。
  • 类图分析

    • 由类图可以看出,本次作业架构于第二次作业大致相当,设计思路也和第二次作业一样,增加了格式检查类CheckPoly、CheckSpace,优化类Simplify。由于上次作业对于乘法的手写比较简单,这次对我来说过于困难,便删去了Exp类和对应的MapKey类和Term类,而通过遍历表达式树来代表一个表达式
    • CosX和SinX类内部也创建了PosExpression和ExpTree,用来储存内部嵌套的表达式
    • 对于格式检查类,着重介绍一下CheckPoly,CheckPoly类里面主要有parseExp(),parseTerm(),parseFactor(),parseSin(),parseCos()五个比较重要的方法和一个int型的指示器in指向当前字符在表达式字符串中的位置,随着判断的进行in不断前进,当表达式传入CheckPoly后,调用parseExp()开始进行判断,根据形式化表述,当当前字符符合或者当前指向的字符没有超过字符串长度时,调用parseTerm(),在parseTerm()根据形式化表述的规则继续进行相应的判断,符合条件则继续调用parseFactor(),在parseFactor()里对当前的因子进行判断,看是否符合幂函数、带符号整数的因子定义,判断成功返回true回到parseTerm()继续parseTerm()的判断。若因子时表达式因子和三角函数因子,则先对格式进行判断,没问题的话则对括号里的表达式调用parseExp(),对三角函数因子调用parseSin()或parseCos()进行判断,在parseSin()和parseCos()里也是进行格式判断并对三角函数括号里的表达式调用parseExp()。如此循环嵌套,若中间有一处有问题则返回false,若成功遍历完整个表达式而没有报错,则表示该表达式格式正确。
    • 该架构优缺点分析和第二次作业一样,新增的类也有不想去破坏原架构的原因,但也造成了代码规模的过多扩大。
  • 度量分析

    属性个数 方法个数 方法规模 分支数目 代码规模
    PosExpression 11 4 构造函数过大 ifelse过多 124行
    ExpTree 2 2 良好 良好 28
    Factor 7 19 良好 ifelse过多 108行
    Coe 0 4 良好 良好 21行
    CosX 0 4 良好 良好 62行
    SinX 0 4 良好 良好 61行
    MiX 0 4 良好 良好 43行
    AddSub 0 5 良好 良好 36行
    Mul 0 5 良好 良好 33行
    Factory 0 1 良好 良好 22行
    CheckPoly 2 10 良好 ifelse过多 280行
    CheckSpace 1 4 良好 ifelse过多 92行
    Simplify 1 2 良好 良好 46行
    Main 0 2 良好 良好 54行
    Method CogC ev(G) iv(G) v(G)
    AddSub.AddSub (Factor,Factor,String) 0 1 1 1
    AddSub.AddSub(String) 0 1 1 1
    AddSub.diff() 1 2 2 2
    AddSub.getType() 0 1 1 1
    AddSub.toString() 0 1 1 1
    CheckPoly.CheckPoly(String) 0 1 1 1
    CheckPoly.bracket() 7 5 3 5
    CheckPoly.doingCheck() 2 2 1 3
    CheckPoly.init() 5 3 2 4
    CheckPoly.parseCos() 21 10 9 16
    CheckPoly.parseExp() 24 9 13 20
    CheckPoly.parseFactor() 42 17 17 27
    CheckPoly.parseSin() 21 10 9 16
    CheckPoly.parseTerm() 10 7 5 9
    CheckPoly.sincos() 2 2 5 6
    CheckSpace. CheckSpace(String) 0 1 1 1
    CheckSpace.doCheck() 48 13 9 17
    CheckSpace.init() 7 8 1 8
    CheckSpace.isRight() 1 1 2 2
    Coe.Coe(BigInteger) 0 1 1 1
    Coe.Coe(String) 0 1 1 1
    Coe.diff() 0 1 1 1
    Coe.toString() 0 1 1 1
    CosX.CosX(Factor,BigInteger) 0 1 1 1
    CosX.CosX(String) 6 4 5 5
    CosX.diff() 3 3 3 3
    CosX.toString() 2 2 3 3
    ExpTree.ExpTree (ArrayList) 4 1 3 3
    ExpTree.getTop() 0 1 1 1
    Factor.Factor() 0 1 1 1
    Factor.diff() 0 1 1 1
    Factor.getCoe() 0 1 1 1
    Factor.getCosInd() 0 1 1 1
    Factor.getLeft() 0 1 1 1
    Factor.getMiInd() 0 1 1 1
    Factor.getOp() 0 1 1 1
    Factor.getRight() 0 1 1 1
    Factor.getSinInd() 0 1 1 1
    Factor.getType() 0 1 1 1
    Factor.setCoe(BigInteger) 0 1 1 1
    Factor.setCosInd(BigInteger) 0 1 1 1
    Factor.setLeft(Factor) 0 1 1 1
    Factor.setMiInd(BigInteger) 0 1 1 1
    Factor.setOp(String) 0 1 1 1
    Factor.setRight(Factor) 0 1 1 1
    Factor.setSinInd(BigInteger) 0 1 1 1
    Factor.toString() 13 2 9 9
    Factory.getFactor(String) 9 8 7 9
    Main.dealExp(String) 4 1 3 5
    Main.main(String[]) 8 1 5 6
    MiX.MiX(BigInteger) 0 1 1 1
    MiX.MiX(String) 3 1 3 3
    MiX.diff() 3 3 3 3
    MiX.toString() 2 2 3 3
    Mul.Mul() 0 1 1 1
    Mul.Mul(Factor,Factor) 0 1 1 1
    Mul.diff() 0 1 1 1
    Mul.getType() 0 1 1 1
    Mul.toString() 0 1 1 1
    PosExpression. PosExpression(String) 36 1 25 26
    PosExpression.getInd(String) 16 4 6 11
    PosExpression.getPos() 0 1 1 1
    PosExpression.pr(String) 3 3 3 4
    Simplify.Simplify(String) 0 1 1 1
    Simplify.todo() 4 3 3 4
    SinX.SinX(Factor,BigInteger) 0 1 1 1
    SinX.SinX(String) 6 4 5 5
    SinX.diff() 3 3 3 3
    SinX.toString() 2 2 3 3
    • 可以看出本次的代码结构特别是CheckPoly类和CheckSpace部分有些惨不忍睹。主要原因是在判断空白字符合法性的CheckSpace类里用了大量的ifelse判断空白字符的合法性,在CheckPoly类里的parse方法里也用了大量的ifelse判断合法性和指示器的越界判断。通过研讨课同学们的分享了解到了在字符串的末尾加入一个无关的字符可以比较有效地避免越界判断带来的复杂性。对于其他的代码问题由于沿用了第二次作业的框架,也和第二次作业一样。
  • bug分析

  • 本次作业在强测和互测中均未发现bug。对于room中其他同学的bug,采用的仍是评测机评测。在发现的bug中,仍然存在了和第二次作业我在互测中hack到的bug,即导数为0不输出和输出了错误格式-*sin(x),大概率是因为优化过头了。新增加的bug有错误的判断成了WRONG FORMAT,这是因为判断了类似+++0的错误。其他的通过评测机测到了求导错误的bug,即在存在+-(的形式时会将其判断为+而不是-

  • 优化分析

    • 本次作业仅仅采用了去除多余的1*和括号等,没有进行过多的优化,这也是因为表达式树的优化对我来说实在太难,没有很好的想法去优化。这是我十分不足的地方。

重构分析

​ 本次作业由于第一次作业较为简单,没有认真的去思考架构,其实当时并不知道架构的概念,于是导致了第二次作业直接重写。第二次作业中以表达式树的构建为目的,利用工厂模式辅助节点的存储,每个因子的类都继承自Factor类,按照这样的思想,在加入格式判断和三角函数嵌套时,也不需要太大的改动,只需增加格式判断类,三角函数内增加一个表达式树对象就可以,使得在第三次作业的实现较为顺利,也体会到了良好架构的重要性。


心得体会

​ 虽然通过了寒假的预习训练,但刚接触面向对象的思想,就要开始写一个对我来说较为庞大的项目(第二次作业开始),还是非常的有挑战性的。不过通过和同学们的讨论与各种资料的查阅,也慢慢的完成了第一单元的作业,自我感觉还是挺好的。在第一单元的作业中,我从一开始的直接写代码,到后面先设计出一个整体的思路与架构,画好各个需要的类与关系,在进行代码的书写,感觉有了较大的进步,也慢慢培养了面向对象的思想,相信在接下来的作业中,这样的思想会越来越深刻,代码风格设设计能力也会越来越良好。

posted @ 2021-03-28 11:57  19lcw  阅读(113)  评论(1)    收藏  举报