目录
HW1
由于我之前没有了解过递归下降的思想,所以我在刚拿到本次题目时还是有点不知所措的,以至于我在周五左右才开始写,周六才开始提交。
UML类图
作业分析
第一次作业主要是要求我们读入一个包含加、减、乘、乘方以及括号的单变量表达式,将其括号展开之后输出。
本次作业的主要就是对于递归下降的思想的应用。
架构分析
本次作业的架构我主要参考了训练中的架构。
由于输入的表达式中含有空格符、tab、多余的+-等,所以我首先对输入表达式去掉多余的这些符号,在这处理的时候,我单独创建了一个类:HandleIn ,在这里还要说一下,我在这里没有去掉前导0,因为我后面读取数字时,将字符串转换成整数的过程中会将前导零去掉。
然后我将处理好的表达式传入lexer中,即词法分析器,该类可以实时获得当前最小的单元整体,即语法中最小的可识别的单元。
然后创建parse类,对表达式进行语法分析,该类可实现由表达式的解析到项的解析再到因子的解析,同时因子中除了常数因子、幂函数之外还含有表达式因子,对于表达式因子,就需要再去调用对于表达式的解析的方法,而对于常数因子和幂函数因子,它们就到了递归的尽头,这时只需要创建类即可,这样就形成了递归下降的思想,这样看来,递归下降也就是递归中构建了一棵AST树,实现了对于字符串形式的表达式的解析,形成了便于后续处理的形式。
本次作业中,我觉得最麻烦的地方就是对于表达式的计算,即括号的展开,以及常数之间的加减乘的计算,在此我设计了一个类:Polynomial,该类主要是处理因子与因子之间的计算,首先我是传入一个字符串,该字符串的形式是ax^b相加减的形式,因此我用HashMap存储了该字符串的每一项的系数和自变量的指数。然后对两个多项式(有可能是单项式)进行相乘,结果也用HashMap存储,同时由于HashMap的键的不可重复性,要在字符串的处理和相乘的过程中调用merge方法对值进行合并。然后将相乘的结果进行输出,在输出的过程中其实也进行了一部分的化简,即系数为0 的或指数为0的项的处理。该类只会在项中含有表达式因子时进行调用,而如果项中只含有常数和幂函数时,相乘我直接在项处理之后进行了计算。然后对项中的括号进行展开之后,我重新创建了一个语法分析器和词法分析器,然后将该字符串进行处理,然后返回一个Expression对象。
最后化简的部分,我将计算之后的表达式按项进行拆分,将其存入HashMap之中,然后对于自变量指数相同的项进行合并,然后进行输出即可。
输出化简
本次作业我的化简主要是对于指数是0或1,系数是0、-1、1的项进行化简,同时使第一项前面的符号非负。
复杂度分析
- ev(G)基本复杂度是用来衡量程序非结构化程度的。
- iv(G)模块设计复杂度是用来衡量模块判定结构,即模块和其他模块的调用关系。
- v(G)是用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数。
- CogC是认知复杂度
由表中来看,主要是 Simplify 和 Polynomial 两个类中的将表达式拆分成基本项从而存储在HashMap中,在该部分中,我使用了大量循环和条件判断语句,从而增加了程序的复杂度,因此CogC和iv(G)的复杂度较高。
HW2
第二次作业可以说是对我的毁灭性打击,我又是把作业拖到了接近于截止时间,我的思维极其混乱,以至于我在通过中测之后我不太想再去看我的代码,我也没进行深入的测试,导致我在强测中炸了一片,但是事后回头来想,其实当时我多测试一下其实这些问题也可以避免。
UML类图
作业分析
第二次作业相对于第一次作业就是增加了自定义函数和指数函数。
自定义函数相对来说还是比较好处理的,只需要在表达式进行解析之前相关的字符串替换即可。
对于指数函数,由于其指数因子的多样性,增加了本次作业的难度。
架构分析
在本次作业中,我沿用了第一次作业的架构。
对于本次作业添加的部分,自定义函数我写在了HanleIn里面,在解析之前完成了相关字符串的替换。
对于指数函数,我单独创建了一个类,其中含有Factor属性,同时,对指数函数的解析添加了以下部分:parseExp(),当在因子类解析的时候判断是指数函数时就调用该方法。
本次作业主要麻烦在对于指数函数的相关计算和化简。
这里我采取的措施是将每个项中的指数函数因子的字符串拼接(如果有多个),然后在项结束的时候重新对这一新拼接的表达式因子进行解析。返回一个表达式因子类型。
对于含括号的项,如果相乘时是两个指数因子相乘,我把这两个指数函数的因子作为字符串进行了拼接,同样是用HashMap进行相应的计算。在计算完之后再次调用语法分析器对相乘完的项进行化简,返回表达式。
在这里我依然沿用着基本项的思想,即a*x^b*exp(),在解析完每一项之后,形式也相应地变成这种。
问题分析
在我按照上述方法写的时候,我感觉我的代码十分的复杂,不仅new了很多的对象,而且解析表达式中间穿插了一些表达式的化简,同时我沿用第一次代码的结构使得一些方法中的代码量巨大,使得我的代码臃肿,晦涩难懂,很难去发现bug,在写完之后,我看着我的代码,也非常不想再去阅读,以至于我萌生出想重构的想法,但是时间一ing不允许我去重构了,所以我硬着头皮交上了这次代码,而且没有进行充分的测试。结果就是爆炸。
输出化简
我的化简主要集中在每一项解析完之后,输出的时候主要是对表达式相同幂函数和指数函数的像进行了系数合并。同时,由于我在每一项结束的时候对指数函数的因子再一次进行了解析,同时将其视为一个表达式因子,因此无论因子是什么类型,我的exp()里面一定套有一层括号,因此,对于比如exp里面是带符号的整数或者幂函数或者是指数函数的因子的类型进行了去括号的操作,还有就是有符号整数与幂函数和指数函数的结合,我的处理方法是将整数取绝对值提取出来,如果有符号剩余的式子仍是表达式因子,括号不能去掉,否则,括号可以去掉。
我的化简其实含有一个问题,就是当exp里面是表达式因子并且每项前面的系数含有很大的公因数时,我没有考虑这种情况,我细细想来这种情况要去实现也是比较麻烦,非常容易出现bug,因此宁可舍弃这部分性能分也要保证输出结果的正确性。
复杂度分析
本次作业中,由于我沿用上次作业的架构,并且基本没有对方法进行拆分和化简,使得我的很多方法的复杂度飙升,这些复杂度高的方法主要是对于自定义函数的处理,然后就是表达式的计算过程中的转化成HashMap储存的基本项,并且在计算完成之后输出字符串的过程。
HW3
UML类图
作业分析
本次作业主要是添加了求导因子和自定义函数定义时可以调用前面已经定义的函数,这样看来这次作业难度并不算大,因此在看到这次作业的要求之后,我果断放弃了我重构第二次作业的想法。因为这次作业按我的想法来看是在解析表达式之前做的工作,与我在表达式解析上代码冗余没啥关系。
架构分析
这次作业我只是在第二次作业的基础之上在HandleIn里面多次调用了 tmpReplace 函数使得如果存在定义式中调用前面已定义的函数的情况,可以完全替换掉函数表达式中的自定义函数表达式。
对于求导因子,我单独写了一个类:Defivation ,该类实现了表达式之中的求导因子的计算,其实现方式是如果读取到dx,将会进入求导的方法,该方法首先会判断在该dx之中是否还含有dx如果不含有的话,就会进行相应的求导计算,在进行求导计算时,规则就是相应的求导法则;加入该dx表达式中还含有dx就会递归调用该方法,最后递归的尽头就是所求导的表达式中不含有dx。
再求导的过程中最难处理的就是指数函数因子的求导方法,我所使用的策略是将指数函数的因子提取出来,再次调用求导的方法,递归求得指数函数的导数。
在使用求导的方法之前,我先调用了一下表达式的解析与化简的方法,使得所求导的式子一定是基本项的形式,即a*x^b*exp(c)的形式。这样对每一项求导便可变得简单,即用链式求导法则即可,重要的是将链式求导的最后两部分的式子相加的时候不要忘记合并,在这里我使用的方法是HashMap存储的方式,然后调用merge方法。
输出化简
本次作业只是涉及在表达式解析之前的替换与计算,并不涉及新添加的因子,因此输出化简和第二次作业一样。
复杂复分析
除去上次作业的复杂度十分高的方法之外,本次作业的新增的求导因子的处理方法的复杂度也同样很高,主要原因是要判断所求导的因子是带符号的整数还是幂函数亦或是指数函数。同时在相应的求导结果的存储上也需要进行判断其是否存在。
bug分析
在第一次作业中,我忘记了指导书中提到的0^0的情况
在第二次作业中,我由于没有充分的测试自己的代码的正确性导致出现了很多,其中主要有以下几个bug:幂函数的指数我用Integer存储了;自定义函数的空格和制表符没处理;对某个行数过多的方法拆分时出现了问题。
在第三次作业中,由于新增内容比较简单,我没有出现bug。
互测
由于第二次作业炸的太多,我没有进入互测,因此我主要谈谈自己在第三次互测中的体会,第三次互测我主要依靠别人的互测机来对房间中其他人的代码进行测试,我主要找到了三个人的bug,其中一个人被我hack了两次,同时我考虑到评测机生成的数据的不全面性,我也自己手搓了一些边缘数据对房间内其他人进行测试。
我主要的测试数据如下:
1 f(x)=-x^2*exp(-1) f((x+x*exp(1)))
1 f(x)=-x f((2+1)^2)
2 f(x)=exp(x) g(x)=f(f(x^2)) g(x)
0 exp(1)+exp(exp(3)^ 2)
我的测试的思想主要是找到其他人的bug为主,并没考虑太多,但是当互测数据出来的时候,我发现原来还可以这样hack,我的设有被以下的数据hack了,而且是cpu超时,可能解决办法只有重构吧。
数据如下:3 g(z)=exp(exp(exp(exp(z)))) f(y)=exp(exp(exp(exp(g(y))))) h(x)=exp(exp(exp(exp(f(x))))) h(f(g(exp(exp(exp(exp(x^8)))))))
心得体会
虽然我在上学期上过oopre课程,但是oopre课程的难度和oo正课的难度可谓是一个天上一个地下,因此,刚开始上oo正课的时候,我还认为会比较轻松,谁知在第一次作业就给我了当头一棒,打得我有点不知所措(主要是开始的有点晚了),这种感受一直延续到第二次作业,使得我前两周一直处于十分焦虑的状态之下,直到第三次作业才有点缓和。
虽然说oo第一单元我完成得并不怎么顺利,但是也是让我认识到了自己的不足与需要提升之处,以及在第二单元开始之前所要做出的调整与准备。
我相信在第一单元的洗礼之下,我对面向对象的思想认识得更加清晰,对于其特点了解得更加透彻,同时也对于程序的bug测试与数据构建与生成有了更深一步的认识。我相信带着这些认识与感悟,当我走进第二单元的时候,我会比第一单元更加游刃有余,得心应手。
未来方向
首先,我认为应该开启一个假期预习任务,在假期了解一下递归下降以及AST语法树,以免第一次作业直接去写的话会使得自己有点不知所措、有点懵的状态。
其次是,我感觉每次作业的难度最好要平均一下第二次作业相对于第一次作业难度提升有点大,而第三次作业的难度又降下来了,同样是一周的时间,完成起来可能就比较匆忙,甚至于说完成的效果不是那么的好。
最后,我认为每单元的博客任务不应当在三次作业之后才发布,而应该是每单元开始就发布,让同学们每次作业完成后都写一下相应的部分,这样不仅印象深刻,而且刚写完作业,对代码相应的细节也更加清楚明白,思路也更清晰明了。如果拖到最后一周再写的话,当时许多的思考和细节忘记了,博客总结写起来总觉得少点细节。