BUAA-OO-第一单元总结

目录

第一次作业

要求

基本思路

预处理

表达式解析

数据储存

表达式处理与输出

UML类图与结构分析

代码规模

复杂度分析

第二次作业

要求

基本思路

UML类图与结构分析

代码规模

复杂度分析

第三次作业

要求

基本思路

UML类图

代码规模

复杂度分析

架构设计体验

bug与优化

心得体会

未来方向


第一次作业

要求

对一个包含加减乘,乘方运算,单变量x,含有单层括号的表达式进行去括号化简。

基本思路

预处理

我对于预处理是比较看重的,在这系列迭代中,预处理可以发挥极大作用。首先当然是去多余空白符,把连续加减号进行合并,*+替换为*等基本操作(注意,不止两个加减号相连),然后便来到了我重视预处理的核心原因,如何区分一个加减号,是正负号还是加减号呢?我在预处理就彻底解决这个问题,预处理后,没有正负号,只有加减号,当我们进行正常的预处理后,会有(-1-1)这样的,对于(替换为(0+,输入字符串开头加上0+,然后再进行加减号合并,可以处理普通的这部分,而*-,可以替换为*(0-1)*,这样处理后,就把所有负号解决,剩下就可以安心进行解析,当然,*-也可以不进行预处理,而是在解析的时候再处理,但可以在预处理把问题都解决,让结构统一,保证没有负号,只有加减,我在解析时候就可以安心保证,+ - 一定是项的分割,* 一定是因子的分割,后续不需要任何担忧,为什么不呢()

当然,预处理处理好,可以让后续解析非常安心方便。但有潜在的危险。首先是对于思维要求较清晰,想让预处理发挥极大作用,就必须有很高的清晰思路,去空白符,加减号合并,各种替换等等操作,有一个顺序颠倒都可能会导致结果出错,而且对于哪里可能有负号,都要考虑到,考虑不全也会挂。其次是时间增加,有人认为像这样加东西,会导致运行时间增加,但经过后续实验,这不会对运行时间造成什么可见影响。因为对于*(0-1),(0+,这样的情况,本来数据也可能出现,我当然优化过程的时候,要连其一起优化,写不写这个预处理,我都会去优化他,顺手的事。而且进行正常的优化后,每次强测运行时间都很快。

表达式解析

我采用的也是递归下降,根据Lexer和Parser类,按照训练中所提供的思路进行解析。加减号分割项,乘号分割因子即可。

数据储存

第一次作业中,我是用一个简单的HashMap来存每个类中的多项式数据,以x的指数和系数分别为key和value,好处是访问与计算,等等,都很方便,运行时间上很快速,缺点是可迭代性低,在第二次作业经历了表达式储存方面的重构。

表达式处理与输出

表达式,项,因子都建造BuildPoly方法,假设组成表达式的各个项的poly都已知,构建表达式的poly,这样同理往下写,直接调用一个顶层表达式的BulidPoly,就会自动递归建造所需表达式,然后进行表达式输出与化解即可。(化简等部分由于三次作业逐次增加,在下面统一说)

UML类图与结构分析

由于第一次作业的时候,代码结构还不大,所以直接放一整个类图即可。类图体现了整体的结构,所以结构分析和类图放在一起。

Lexer生成词根(token),Parser来解析词根,构建表达式树。

那表达式树是什么架构呢,expr为顶层,根据+-分割由一堆Term组成,一个Term根据*分割,由一堆因子组成,因子里,如果是数和x^b,这样,就到底了,如果是表达式因子(也就是遇到'('),那就重新解析表达式,注意,解析完,要再看有没有指数,有的话也储存进这个表达式因子。

表达式树种每一个类,都有一个poly(也就是一个HashMap储存的多项式),在这次作业,他不适合专门的类,所以不在显著的结构里,在树建造好之后,就可以构建多项式了。

代码规模

复杂度分析

类复杂度和方法复杂度以及上面的代码规模都很小,没有复杂爆红的地方,所以第一次作业不进行过多分析。也不放方法复杂度了(方法复杂度也都很低)

第二次作业

要求

新增自定义函数部分以及括号嵌套以及指数函数

基本思路

括号嵌套在第一次作业的架构中已经解决,无需考虑。

关于指数函数,导致第一次的简单的HashMap来储存数据便的困难,所以建立的更改后的unit和poly类,unit为基本单元,在这里就是 a x^bexp(factor),为了与第一次作业的预处理结合(会替换成(0+,而且为了便于统一,我更改为a x^bexp(expr),poly就是一个unit组成的Arraylist,也就是几个unit相加,组成数据储存部分。

自定义函数,使用了一个静态类,Definer来专门处理,遇到fgh,就把实参全取出,通过Definer来进行实参替换掉形参,然后替换后得到的字符串作为一个expr因子来重新解析。(注意替换时候小心exp的x被替换,刚替换进去的x被替换等等问题,危险负号先替换走,之后再替换回来即可),同时,由于自定义函数也有括号,所以在预处理部分把','替换为',0+'这样实参就看做expr即可,带入形参替换的时候,加一个扩号进去即可。

UML类图与结构分析

由于第二次作业开始,类图变得复杂,所以我先列出一个总的简单类图,在分别介绍两个重要的组成部分。从而便于观看与理清结构。

下面就是去除Poly和Unit的类图,可以看到,去除这两个之后,结构就清晰很多了。与第一次结构所变化的不大,只是在因子部分进行了增加ExpFunc因子和Func因子。

下面是这次重构所新增部分,也是我把类图分成这两个小类图的原因。已经介绍过,poly是多项式的集合,所以表达式树的每一个类都有poly,所以poly与其他每个类都有联系,这样将他们放在一起,会导致类图非常复杂,不便于观看。而即使不放在一起,结构也可以理清,因为这里的poly就是第一次架构的HashMap,只是单独储存了,并且其中是用Arraylist,而不是HashMap了,其中由于单独作为类,小心浅克隆导致问题,所以poly和unit里我重写了clone方法(因为poly里有一堆unit,unit里的exp里可能有poly,所以两个clone要互相调用,unit判等时候,也要这样进行才能确保完全相等(我对于里面的多项式判等,采用的是全解析,全相等,再用优化来解决全相等判断导致的时间复杂问题,而没有用字符串判等),当然,经过测试,也不影响运行时间,很快)

代码规模

代码规模变大,不过还可以接受。

复杂度分析

在下面的类图的复杂度分析里,我们可以看到有爆红成分,所以在这里复杂度不是可以略过的部分。主要是poly和unit复杂问题,因为我对于两者加了很多彻底的判等,clone,各种运算方法等,而且还有一些作用不大的方法没有及时删。

可以看到,方法里有如下几个爆红的部分,主要是poly的计算。这是由于我在进行poly运算的时候,加入了很多化简。计算中必须要随时注意,这一步是否需要开始进行化解,不然会导致后续产生对于一堆0+0+0+0与0+0+0+0乘这样简单的部分,我也会挨个乘,耽误时间,所以我在这里加入了很多化简,比如合并同类型,系数为0,就不放入,等方法。

这个爆红其实是可以很容易解决的,只要把化解的那么多相似性代码提出来,作为一个新的方法,这几个计算过程中调用他,即可,而在写的时候为了方便快速,我并没有提出来做出新的方法,只做了一个标记,待之后有时间的时候再进行提出(不过并没有很多时间)

第三次作业

要求

求导因子,自定义函数嵌套

基本思路

自定义函数嵌套根据第二次作业的方法,也已经解决。无需更改。

求导因子,新建一个类,作为导数因子类即可,在unit和poly等,构建相应的求导方法即可。(同样也是unit和poly互相包含时候求导,但由于第二次作业已有,unit和poly互相包含导致的clone,相等等判断的时候遇到过,所以难度不大)

UML类图与结构分析

主要形式同第二次一样,也是一个总体部分和两个主要部分。但是由于没有新增什么,只有一个DxFunc(导数函数因子)新增,所以没有太多要介绍的。

新增部分如下,多了个DxFunc

代码规模

复杂度分析

爆红的还是第二次作业出现的那些问题。

架构设计体验

架构的形成过程如上面三次作业的分析中写到。

关于重构方面,由于对于数据储存方面在第一次到第二次作业中进行了重构,导致对于表达式树中的各个类的数据的储存方式,各种加减乘等运算,都要改,所以大规模改变,虽然这部分内容,更改起来难度不大,但是量非常多,所以很麻烦。体验就是很糟心()

新的迭代场景的话,以加入sin因子为例。只需要在unit进行更改,然后对于计算进行更改,不需要进行大规模重构,就是增量开发即可。

bug与优化

未通过的公测用例和被互测发现的bug:无

在弱测和中测提交的时候,基本也都没出过什么bug(如果风格检测不算bug的话)。

自己在本地刚写完的时候,捏造数据进行测试的时候,发现过一点小bug,比如,如果结果是0,我输出的结果会是空,什么都不输出,所以对此进行了更改,如果表达式是空,那就是0。

对于这种bug也很好避免,因为在尝试捏造数据的时候,自己就想到,这方面好像没做到,就去尝试了。复杂度方面,没有什么严重的bug导致复杂度增加。

发现别人bug所采用的策略:三次互测中,所在房间都是全部0成功率,没法发现bug。尝试用复杂数据卡他TLE也做不到。至于根据代码设计结构,基本不可能把所有人代码看完,除非找到了谁的bug,或者心血来潮,才会把他的代码进行查看,从而定位错误位置。不过过程中发现,看和自己架构不一样的代码的时候,确实很花时间,所以很少做,而且通过这种方法找bug很难,代码太多了,定位bug还可能好一点。

优化方面,对于第一次作业优化到极致,比如,结果多余正负号,1*x^1,结果为x,这样,把每一个基本单元部分化到最简,,然后对于多个基本单元相加减,如果有正数,就把正数提前,避免多一个负号,长度加1。第二次作业开始,提公因式部分以及拆成多个exp相乘,并没有去做,其他部分都做了(只多优化了exp里面,要几层括号这种)。第三次作业也是。

优化方式,对于系数为1,x指数为0,为1,exp指数为0,这样的进行处理,特别注意,如果是1*x^0*exp(1)^0这样,按照简单的对于系数为1,x指数为0,就去掉这部分的方法,要注意,这里结果是1,而不是全去掉,变成空。正数提前,就看系数有没有正的就可以。exp里几层括号,就看他是不是个因子。

能保证正确性,简洁性也还可以。优化的时候,这几个优化是分别在不同方向上进行,互相干涉性没那么大,可以分别进行。而且,比如exp里几层括号,我单独写一个方法判断里面是不是因子,封装起来,就可以让简洁性大很多。

心得体会

第一次作业开始的时候,压力真的非常大,实在是无从下手。而且由于我在提前预想架构的时候,想到了很多可能会出现的问题,所以脑中形成的东西非常复杂,再加上不熟悉递归下降,所以又不会又想的多,更难形成具体架构。(思考的过多,也导致我跟别人讨论架构方向时候,虽然我对于具体如何实现还不了解,但我可能已经能指出他一些bug),好处是,想的更完善,不那么容易出问题,缺点也很严重,不熟悉的情况下,想的越细,越不容易理清,前期很崩。所以有时候,边写边理思路也是有用的。而且在互测看别人代码的时候,发现大家都用poly,mono什么的命名,很好奇这个名字是哪里的(我的poly是让翻译软件翻译“多项式”,得出的,我还以为大家都是这个翻译的),后来知道,都是一个师傅教的,所以第二次作业重构的时候,我也加入了()

而在设计方面,对于面向对象代码的书写,有了更好的掌握。而在设计架构时候,可拓展性的重要,也有一定的体会,不然重构很累,当然,经历重构,也帮助更好的分析自己的架构。而且,能为未来准备多少可拓展性,其实也取决于,目前时间还剩多少,对于快要完不成的人,他想准备更多拓展性也很难,所以要综合考量。

未来方向

这性能优化太容易出现一人刀全系的情况了,一人100分,其余都陪葬,建议平缓一点),实在有的优化策略太离谱了。

而且,第一次作业是从0到1,往往难度比想象的大,尤其对于各方面0基础的学生,往往要艰巨很多,所以希望尽量给更多,更平缓的指导。(虽然大部分人都会去找那相同的师傅),第三次作业确实难度很低,看完题目两小时就结束了,感觉这周没写oo一样(bushi),或许可以把第一次作业的一些难度后移到第三次。这样迭代更平滑,符合oo丝滑迭代的说法()。

  • 17
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值