北航OO第一单元作业总结(1.1~1.3)

经过了三次作业之后,OO第一单元告一段落,作为一个蒟蒻,我初步了解了面向对象的编程思想,并将所学内容用于实践。


一、第一次作业

1.架构分析

  本次作业需要完成的任务为简单多项式导函数的求解。表达式仅支持常数项以及幂函数项的简单加减运算,并且输入保证是符合格式规范。整体要求上较为简单,但由于我对一些基础知识掌握不透彻,并且对面向对象编程的思路还理解得不够,所以整体上本次作业基本上还是面向过程,可移植性以及可扩展性很差。

  大致思路:首先将表达式化简,暴力去掉空白项,并且将连续的+、-号化简,使每项统一规范,便于正则表达式的统一。由于我没有过多注意帮助文档,我的正则表达式写得十分随意,并且采用的是对性能影响非常大的大正则。但好在本次作业整体较为简单,因此大正则没有过多影响性能。

  设计类时,我仅仅设计了一个Poly类,用于存储每一项,并且将每一项表示成cxn的形式,便于合并同类项。抽象类PolyGetter为工具类,用于获取每一项。

2.性能分析

类图如下图所示:

北航OO第一单元作业总结(1.1~1.3)

可以看出,这几个类之间并没有什么联系,仅仅各自完成自己的功能。

复杂度分析:

北航OO第一单元作业总结(1.1~1.3)

输出多项式的Poly.print()方法圈复杂度较高,v(G)达到了10,主要是本次作业我参考了上机实验的输出,因此花了较多的篇幅用于格式化输出,提高性能分(性能分和输出字符串的长度有关,复杂度高也不影响分数)。

3.评测分析

强测阶段:

  由于本次作业大家基本都进行了合并同类项,因此强测时拿到满分还是比较困难的,而我在输出的细节方面处理得不够到位(比如简单地将每项用+连接),因此扣了一些性能分,但好在没有测出bug。

互测阶段:

  我在格式化输出的时候犯了一个极其可笑的错误——当系数为-1的时候,幂的符号会输出成“^”。这一bug直接被人hack了9次,甚至一度让人以为我是抄的去年作业。好在被hack的点一次合并修复就解决了,只扣了1分,并且我算是运气比较好,hack到了同屋子内的4个bug,最后在互测时反而获得了加分。

4.反思与总结

  本次作业设计得最丑陋的一点就是将整个表达式的正则用一个字符串表示,没有进行任何拆分,导致debug时遇到困难。另外,我对程序的检查仔细程度也不够,导致出现了"^"这样的可笑失误。

  下次作业应该注重的是正则表达式字符串的拆分,并且应该增加程序的内聚性,尽量减少对其他类的调用。


二、第二次作业

1.架构分析

  本次作业在第一次作业的基础上,增加了三角函数因子(sin(x)cos(x))以及因子的乘法运算。显然本次作业的正则表达式复杂性要远高于上一次,因此使用大正则已经不可取了。同时,由于“+”、“-”号判断更复杂,且增加了判断WRONG FORMAT的要求,采用上一次的简化符号已经不可取。在这种情况下,我对所有的类都进行了重构。当然,本次作业总体上还是有点良心的,因为因子、项、表达式仍然能用固定的正则表达式表示,判断格式仍然比较方便,且保证空白字符的出现不影响格式的正确性,仍然可以暴力去空格。此外,每一项可以表示为axn1sinn2(x)cosn3(x)的形式,所以在求导、合并同类项时仍然可以归一化判断。

  在解析表达式的过程中,将表达式分解为项,再分解为因子,比上一次多了一层判断,但复杂度增加的不多。

2.性能分析

类图如下:

  北航OO第一单元作业总结(1.1~1.3)

复杂度分析:

  北航OO第一单元作业总结(1.1~1.3)

每个方法的复杂度比上一次作业没有明显增加,但是仍然有部分方法的复杂度较高,特别是Poly类的耦合性仍然较高,是需要改进的地方。

3.评测分析

强测阶段:

  本次作业强测是炸得最惨的一次,错了四个点,其他的点性能分也十分拉胯,主要是化简做得非常烂,仅仅只合并了三个指数都相同的情况,对实际性能分提升很小。

互测阶段:

  由于本次作业是确实写出了比较严重的bug(在循环的时候标记忘记置位),当项数比较多且符号变化时就会出错。因此被hack了多次。不过幸运的是强测数据并没有很强,错的点不是很多(我认为我的bug不配过这么多点),而且被hack的点属于同质bug,一次合并修复就OK了。也许是因为进了B屋或者C屋的原因,这次同屋的人bug都比较离谱,为了挽回一些分数,我拿他们开了不少刀,最终也是赚了一笔,当然最终的分数仍然不太好看。

4.反思与总结

  这次作业最大的教训就是没有本地充分测试,比较严重并且易于发现的bug都没有找到。我过于依赖中测的评测结果,后来发现中测等于不测。因此以后一定要在本地做充分的测试,自己编造大量的测试数据,或者尝试使用评测机生成。如果不会写评测机一定要抱紧大佬大腿,白嫖就完事了。另外,我的优化工作也做得很差,对于基本的sin2(x)+cos2(x)=1的简化都没有实现,因此性能分也比较低。这可能是跟我在做完作业后就想摸鱼有关。因此以后在做完基本的功能后,一定要想办法提高自己的性能分。


三、第三次作业

1.架构分析

  本次作业在前两次的基础上,增加了三角函数因子内可嵌套的复合函数的设定。与前两次作业相比,本次作业复杂性大大增加,其原因就在于无法用通项的形式来表示每一项,同时也无法用统一的正则表达式来解析输入的字符串(Java不支持递归正则)。但我在思考过程中发现,虽然每一项没有统一的表示方法,但在形式上有相似之处。我在编写过程中,将每一项表示为sin(f1)**m1*sin(f2)**m2*...*cos(g1)**n1*cos(g2)**n2*...*pow,其中f1,f2,g1,g2均为内层表达式,pow为第一次作业中的幂函数表达式的形式,这样就大概确定了表达式类、项类、三角函数因子类的数据结构(互相嵌套),每一项的不同三角函数因子的乘积可表示为两个三角函数因子类型的链表,pow则可用HashMap来表示,这样就可以少设计很多类。

  具体设计时,主要数据结构的类包括Poly(表达式)、Term(项)、SinTerm(正弦函数因子)、CosTerm(余弦函数因子),其中Poly类包含一个相加项Term的列表,Term类已经解释过,SinTerm、CosTerm包含一个内层Poly、指数以及标记位(判断是否为原子表达式)。在求导时,先利用乘法法则对外层函数逐项求导,然后再乘以内层函数的导数,递归求解。

  本次作业中字符串判断是一大难点。首先空白字符不保证出现在合法位置,这使得暴力去空格变得不可取。其次,不能用统一的正则表达式来判断及解析,必须层层递归下降。我在一开始设计时就误入歧途——试图将三角函数因子表示为形如sin(.*)(\\*\\*[+-]?[0-9]+)?的形式(已省略空格,其余同理),然后通过捕获组来判断内部的表达式。这样做存在难以解决的问题——正则表达式只支持贪婪匹配和非贪婪匹配,当出现形如sin((2*x+1))的因子时,采用两种方式均不能正确获取内部表达式(括号匹配不正确),这也导致我花了几乎一天的时间重构,做了很多无用功。我在重构的过程中,增加了去除冗余括号(减少递归层数)以及利用栈的原理获取括号内的子表达式的方法。由于这两个方法基本是纯面向过程的,因此我整个字符串处理工作也偏向面向过程,最终仅仅个别地方用到了正则表达式。但这未免是坏事,因为正则表达式如果写得不好很容易导致性能急剧下降。而且面向过程的方法很多时候运行速度要高于面向对象的方法(优化了内部细节)。设计好整体架构之后,判断、解析起来也就不难了。

  在输出时,我做了一些简单的优化,比如去掉了多余的正负号。但是,由于数据结构设计的问题,合并同类项变得十分困难,而且就算实现合并,对性能提升的程度仍然十分有限,因此我放弃了合并同类项。

2.性能分析

类图如下图所示:

北航OO第一单元作业总结(1.1~1.3)

除了主类之外共有4个结构类、两个工具类,其中生成表达式的类采用工厂模式。

由于方法过多,无法全部贴出,这里只贴出部分复杂度较高的方法:

北航OO第一单元作业总结(1.1~1.3)

可以看到很多方法的圈复杂度都十分难看,基本上是因为我实现这些方法是面向过程的,当然,面向对象的方法表面上降低了复杂度,但实际运行时复杂度并不低。

3.评测分析

强测阶段:

  可能是因为本次作业本身较为复杂,中测的数据很弱,强测数据也并不强,不过我还是因为一个比较明显的bug导致强测炸了一个点。

互测阶段:  

  如上文所说,我存在的bug是输出过程的漏洞,由于存在可能导致数组越界的操作而使程序RE,具体的,当输出结果为0时会抛出异常,这一点被人砍了不少刀。这一点也是因为我本地评测不到位。另外,由于程序本身算法的缺陷,在递归层数很多时程序会TLE,屋子里的一些狼人也利用这一点hack了我几次。

4.反思与总结

  本次作业最大的问题就是架构设计的问题。由于我采用惯性思维,尝试使用通项的形式表示每一项,而导致数据结构设计得过于复杂。回想起来,其实没必要将通项表示为那种形式,如果当时采用多态的方法,将幂函数、两种三角函数同时设计为因子类的子类,数据结构会简单很多,运算过程也会变简单很多,也许就不会出现TLE的情况。我对面向对象程序的结构仍然不是十分熟悉,有时不能将所学的知识充分利用。因此,在以后写程序时应该多加构思,良好的构思会减少很多工作量。


四、总结

  第一单元算是面向过程与面向对象编程的衔接过程,三次作业带来的最主要的就是思想的转变,从面向过程到面向对象,这个转变很艰难,到现在也只是摸到点头绪,必须承认我仍然是个蒟蒻。面向对象是一种思维方法。类、方法的构建与层次关系都是要不断思考不断改进的。这个单元也让我学会了很多(特别是各种API的使用),我在写代码时也发现了自己的不少问题,仍然需要不断努力,解决问题。

上一篇:OO第一单元作业总结


下一篇:MySQL主从复制高级进阶