OO第一单元总结

 

OO第一单元总结

一、整体综述和架构分析

在开始动笔第一次作业之前,看了很多学长学姐的博客,整体而言今年题目变化较大,但存储结构大体还是可以沿用的(后续加入自定义函数、求和函数对因子层次扩展)。因此从最开始我便确定了基于递归下降法的架构(乐,没有经历重构)。

  • 项目整体包含以下层次:

    image-20220321230205762

  • UML类图如下:(具体分析在迭代过程中描述)

    image-20220322000731146

  • 代码复杂度分析如下:
MethodCogCev(G)iv(G)v(G)
Lexer.Lexer(String) 0 1 1 1
Lexer.getNumber() 2 1 3 3
Lexer.next() 18 2 11 15
Lexer.peek() 0 1 1 1
MainClass.main(String[]) 1 1 2 2
Parser.Parser(Lexer) 0 1 1 1
Parser.parseExpr() 7 1 7 7
Parser.parsePow() 20 5 13 13
Parser.parseSin() 3 1 3 3
Parser.parseSum() 5 1 6 6
Parser.parseTerm(BigInteger) 1 1 2 2
Simplify.Simplify() 0 1 1 1
Simplify.Simplify(String) 3 1 4 4
Simplify.ansSimplify(String) 3 1 3 3
Simplify.funcReplace(String) 37 12 21 21
Simplify.getName() 0 1 1 1
Simplify.myInput(String, ArrayList<Simplify>) 35 8 8 11
Simplify.myReplace(String) 0 1 1 1
expr.Expr.Expr() 0 1 1 1
expr.Expr.addTerm(Term) 0 1 1 1
expr.Expr.expand() 6 1 4 4
expr.Expr.exprmul(Expr, Expr) 3 1 3 3
expr.Expr.getTerms() 0 1 1 1
expr.Expr.isEqual(Expr) 10 4 4 7
expr.Expr.merge() 20 5 10 10
expr.Expr.simplify(Term, Sin) 53 11 15 16
expr.Expr.toString() 35 2 18 18
expr.Number.Number(BigInteger) 0 1 1 1
expr.Number.getNum() 0 1 1 1
expr.Number.setNum(BigInteger) 0 1 1 1
expr.Number.toString() 0 1 1 1
expr.Pow.Pow(Factor, BigInteger) 0 1 1 1
expr.Pow.expand() 3 1 3 3
expr.Pow.getBase() 0 1 1 1
expr.Pow.getExp() 0 1 1 1
expr.Pow.isEqual(Pow) 23 12 7 17
expr.Pow.replace(Pow, BigInteger) 12 1 9 9
expr.Pow.setBase(Factor) 0 1 1 1
expr.Pow.setExp(BigInteger) 0 1 1 1
expr.Pow.toString() 17 1 9 9
expr.Sin.Sin(Factor, String) 0 1 1 1
expr.Sin.canMerge(Sin) 2 2 2 3
expr.Sin.getFactor() 0 1 1 1
expr.Sin.getId() 0 1 1 1
expr.Sin.setFactor(Factor) 0 1 1 1
expr.Sin.simplify() 6 4 3 4
expr.Sin.toString() 7 1 5 5
expr.Term.Term(BigInteger) 0 1 1 1
expr.Term.addFactor(Pow) 0 1 1 1
expr.Term.expand() 40 1 16 16
expr.Term.getCoe() 0 1 1 1
expr.Term.getPows() 0 1 1 1
expr.Term.isEqual(Term) 9 4 3 6
expr.Term.merge() 35 1 14 15
expr.Term.setCoe(BigInteger) 0 1 1 1
expr.Term.sinMerge(Term) 33 1 13 13
expr.Term.termmult(Term, Term) 2 1 3 3
expr.Term.toString() 1 1 2 2
expr.Var.Var(String) 0 1 1 1
expr.Var.getVar() 0 1 1 1
expr.Var.setVar(String) 0 1 1 1
         
Class OCavg OCmax WMC  
Lexer 4 12 16  
MainClass 2 2 2  
Parser 4.83 11 29  
Simplify 5.14 17 36  
expr.Expr 6 14 54  
expr.Number 1 1 4  
expr.Pow 4.11 12 37  
expr.Sin 2 4 14  
expr.Term 4.73 15 52  
expr.Var 1 1 3  
         
Package v(G)avg v(G)tot    
  5.33 96    
expr 4.35 187    
         
Module v(G)avg v(G)tot    
Unit1_hw1 4.64 283    
         
Project v(G)avg v(G)tot    
project 4.64 283    

 

 

 

 

二、作业分析

1. 第一次作业

  • 第一次作业可以说是写的最艰辛的,一方面仅仅凭借pre的知识还不足以写出具备一定规模的代码,还需要继续深入学习,另一方面对于递归下降法缺乏了解,不知道从何处下手。在完成了课程组布置的训练环节后才对递归下降法有初步的认识,也照葫芦画瓢的仿照着完成了LexerParser部分的书写。

  • 在第一次作业中我建立了Factor接口,Expr类,Term类,Number类,Var类以及Pow类,其中PowFactor Base以及BigInteger exp组成,Base可以是NumberVarExpr类,exp为他们的幂次。至此表达式树的整体架构完成,通过LexerParser的解析,我们已经构建出一棵表达式树,剩余的工作在于如何进行去括号以及化简。

  • 去括号的过程是第一次作业中面临的最严峻的挑战,由于递归下降适合处理嵌套的特性,虽然第一次作业并未要求对嵌套括号进行处理,但本着开发的迭代性和扩展性,在这一过程我决定一步处理到位。在仔细思考后想到表达式树的递归性,也基于递归实现了去括号的过程。

    Expr层次:对每一项进行去括号,然后将新项加入Hashset,得到新表达式

    Term层次:由于Term由因子组成,而数字因子和幂函数因子可以直接合并,所以在这一层次我把这两种因子先处理好,并将数字因子乘到项的系数中(方便处理) ,同时如果遍历时遇到表达式因子就进入因子层次的化简,最后项由数字因子,幂函数和表达式因子组成,此时尚未化简结束,需要将这些因子乘开,在这里我又写了表达式乘法和项乘法,注意此时Term化简的结果会变成表达式,在这里我是把这一表达式中的每一项加回了上一层的表达式中,这样也就实现了表达式树由深度广度的转化

    Pow层次:在这一层次实现了对表达式因子的化简,首先对其Base调用Expr的去括 号方法(构成递归的循环),然后对高次项进行降次。

    通过上述三层次的循环调用,最终实现了表达式去括号操作,我也更深刻的理解如何构建出正确的递归循环,而更复杂的过程则交给程序递归调用去完成。我欣喜的发现最终的表达式树结构十分简洁,仅由表达式,项以及因子三层次构成,也再次验证了方法选择的正确性,为后续迭代开发打下了良好的基础(指不需要重构

  • 最后是同类项的合并,由于此时表达式树结构极其简单仅有三个层次,第三层次仅由数字和幂函数构成因此,只需要在表达式层次调用项合并方法,项层次只需要把因子数化到最简即可。表达式层次把每次合并后的新项与当前已有项对比,如果可以合并则系数合并,无法合并则在表达式中添加新项。至此表达式化简完成。

  • 第一次作业历时几天,在缓慢的构思中艰难地完成了,同时为后续开发留下了足够的扩展空间。

2. 第二次作业

  • 第二次作业相比第一次作业增加了嵌套括号的处理,同时新增了三角函数因子,自定义函数因子,以及求和函数因子。由于第一次作业中已经实现了嵌套括号的处理,所以只需要对新增因子进行增量开发;

  • 因子的增量开发:

    • 对于三角函数因子,我建立了三角函数类,并通过标识符区分sin和cos,从而降低重复的代码量;

    • 对于求和函数,把求和因子读入以后我选择了递归下降法来替换,即递归的遍历每一层次,如果当前层次是需要替换的i(已设置成Var类),便将其替换,最终返回一个表达式类型,并封装成表达式因子。

    • 对于自定义函数,最初我也想类似于求和函数来处理,但实现难度明显提升,因此最终我选择了用字符串替换的方式来实现。虽然字符串替换可能出现各种问题,但是如果填上足量的括号就可以避免这一问题,不过当然这会牺牲一些空间和性能。

  • 化简:

    化简和第一次的思路一样,但这次明显更加复杂,得益于良好架构,第一次作业的每个叶节点只会是数字或幂函数,因此十分容易判断是否为同类项,而本次作业中每一项的因子个数显著增加,由于使用Hashset作为容器,判断相等有两种方式,一种是重写equalshashcode方法(各因子层也都重写)使其能够自动判断,但会引发x**2与x**2的hashcode值相等,无法加入到Hashset中,由于不想过多改动(想偷懒)于是便手写了判断是否为同类项的方法。在本次作业中并未实现三角函数的化简。

  • 第二次作业实现过程中也为第三次作业预留好了迭代空间,只需要简单从字符串层面处理嵌套函数即可。

3. 第三次作业

  • 第三周是相对而言比较轻松的一周,仅在顶层完善了自定义函数的嵌套替换功能便完成了作业 的增量要求,因此在本次作业中我添加了一些简单的三角函数化简,不过仅针对了二次项,并可以识别替换sin(x)**2和替换cos(x)**2那种情况更优。但没有对于高次项的裂次优化,也没有加二倍角优化,部分点性能分可能会受影响。

三、Bug分析与修复

1. 自己遇到的Bug

  • 第一次作业在处理连续正负号时考虑不周,由于因子也可以有正负,没有解析

    解决:替换掉连续的正负号

  • 第二次作业忘记了sum上下限的前导+号,导致RE

  • 第三次作业则是化简出现问题,正项放在最前面的优化我直接找到了第一个正项进行字符串替换,结果出现了这样的问题:

                  -1+cos((1+x))——>x))-x+cos((1

 

2. Hack

  • 使用过程中hack到自己的数据

  • 测试的边界条件

  • 嫖到的自动测试程序

四、心得体会

对于oo的学习大概从假期的pre作业就开始了,简单总结来说算得上是学习过程是"痛苦"的,但进步同样也是飞快的。从最初由于不了解getter和setter用法,不知道多态机制和接口用法,代码的书写进行不下去,到如今已完成初具规模的表达式化简工程,回头来看也仅仅过去了一个月的时间,或许正是在这种“痛苦”中才会收获迅速的成长。

从第一次作业到第三次作业一次次迭代,体验了代码的增量开发,也意识到了好的架构以及代码良好可拓展性的重要作用。也渐渐体会到学长们说的写代码不只是脑力活,还是体力活的含义。每周一次作业一次作业写一周,开学的第一个月便是这样每天伴随着oo度过的。希望在未来的几个月里能够继续努力,继续进步,好好培养代码能力和素养。

posted @ 2022-03-24 20:22  warriors2001  阅读(54)  评论(3编辑  收藏  举报