北航OO第一单元总结

北航OO第一单元总结

第一次作业

本次作业内容为简单多项式的导数求解,现在看来第一次作业其实总体来说难度不高,但是作为本学期第一次作业,也是我接触面向对象思想以来的第一次作业,对于当时的我来说还是颇具难度。

在设计上,由于这一次作业的表达式格式并不复杂,运算关系也只有简单的整数四则运算,我总共只建了3个类,分别是:
1) Mainclass类:用于处理输入和输出,仅包含scnner方法和表达式求导方法的调用。
2) Expression类:表达式类,存放了该表达式的每一个项,并提供构造方法,求导(derivate)和字符串输出(toString)方法。
3) Term类: 项类,有系数(coe)和指数(exp)两个属性,也提供构造,求导,toString三个方法。

类图与复杂度:
image
image
image

总体思路:

  1. 预处理
    首先对字符串进行预处理,由于本次作业无需格式检查,因此可以默认输入格式为正确,使用replaceAll方法除去字符串中的空格,将‘**‘替换为‘^’,将连续的加减号进行等价替换。这样就可以初步得到一个比较易于处理的字符串。
  2. 项和因子的提取
    因为项与项之间恒为加减法关系,因子与因子之间恒为乘法关系,因此我很自然的想到使用加减号和称号对字符串进行分割。我的方法是首先使用split方法将表达式分割为项,将每个项都存入TreeMap中,然后在每个项内再次使用split方法将项分为因子,第一次作业的形式非常简单,每一个项都可以表达成coe*x^exp的形式,因此我没有继续往下建立因子类(然后第二次作业铁重构了)。在项内没提取到一个因子,首先判断其类型,第一次作业有两种类型,常数项和指数项,分别对应项的coe属性和exp属性。
    3.求导
    第一次作业的求导法则可谓良心,只需对coe*x^exp形式求导即可,但是要注意对指数为1和指数为0的情况进行特殊判断,最后求导返回String类型,直接输出即可。

遇见的BUG:

  1. 指数为1或0的情形
    这可以说是一个很明显的注意点了,然而我第一次写还是忘记处理这种情况了,只需进行特判即可。
  2. 可能出现连续三个加减号的情况
    我是在讨论区中见到这样的说法,然后论证过后发现这的确是可以出现的,但由于我第一次作业未采用递归下降式架构,因此只能在预处理中暴力解决。(逃)
  3. replaceAll的指针移动导致的问题
    在replaceAll中若使用分组形式的正则表达式例如“()[+-]()”,在成功匹配一次过后,指针会移动到该表达式内容后,这样一来可能导致诸如1+2+3这样连续的加减号匹配不到的问题,思来想去我也没有什么好的解决办法,只好再遍历一遍。

第二次作业

第二次作业引入了三角函数求导和表达式因子的求导,可以说第二次作业难度骤增,不仅需要加入三角求导法则,还要引入乘法求导法则,对表达式整体求导,之前我的架构显然是完全不能满足这样的需求的,所以我重构了。

重构的思路主要是将之前省略的因子类加入了进来,建立了不同的因子类,然后又建立了工厂类用于创建因子类,以下:
1) Mainclass:输入与输出
2) Expr类: 表达式类,重构了构建方法,其余没变
3) Term类: 不再承担因子类的功能,功能与Expr类类似
4) Factor类: 抽象类,各个因子类的父类
5) 因子类: Const类, Var类, Triangle类,分别对应常数因子,指数因子,三角函数因子,此外Expr类也是因子类,都继承Factor类。

类图与复杂度:
image
image
image

可以看到,Expr的构造方法复杂度很高,因为这次重构是我第一次使用类似递归下降的思想处理字符串,但是真正的递归下降用不太懂,转而用for循环来达到类似的效果,在写的过程中增增改改,最终代码显得比较臃肿,且难以维护,时间复杂度高,这也直接导致了我强测很多数据超时。

核心思想其实与第一次差不多,都是分级求导,上层求导调用下层求导,整体思路虽然不复杂,但是这一次作业的任务量自我感觉是三次作业中最大的,乘法的求导法则算法花了我大量时间,最终写出来的成品仍然感觉不够潇洒,比较臃肿。但话说回来,这次作业对我编程能力的提高也是实打实的。

遇见的BUG:

  1. 关于负号的处理
    一开始我的思路是每个类建立一个表征符号的属性,然而我的代码中有许多new一个新类的地方,这些地方都需要传递符号,结果就是导致很多符号错误,(也让我意识到了工厂类的重要性)。最终索性不进行符号处理,在构造方法中处理符号,解决了这个问题。
  2. 三角函数指数为1或0
    老毛病了,又一次犯错
  3. 输出格式有误
    符号处理引出的新bug,那就是会导致我的输出可能会有诸如-cos,-x这样的非法输出,最终考虑到复杂度和任务量的问题(主要是没时间了),在输出前使用了replaceAll进行再处理。

第三次作业

第三次作业需要支持三角函数的嵌套求导,同时需要进行格式检查。难度提升比较平滑,但是由于我第二次作业代码一直解决不了时间复杂度的问题,索性再一次重构了。

相比第二次作业只新增了一个CheckStyle类,用于格式检查。

类图与复杂度:
image
image
image
image

可以看到再复杂度上比第二次有所改进。

概述:
重构的思路主要是求导函数的返回类型和乘法求导法则的优化,在之前的作业中我使用ArrayList来存储项和因子,考虑到以后可能需要优化因此求导方法的返回类型我选择的也是ArrayList,但是在求导过程中产生了大量创建和遍历ArrayList的操作,造成时间复杂度较高。这次作业索性直接返回String类型,免去这些麻烦(翻译:不优化了)。然后将之前乘法求导法则算法的递归去掉了,在一个for循环内解决,优化些许时间复杂度。
然后在格式检查方面,由于我的算法不是真正的递归下降法,不是很好在递归下降中进行格式检查,思来想去还是使用了正则表达式进行格式检查,最终可以看到我的inputCheck方法四个复杂度都冒红,意味着复杂度还是太高。

遇到的bug:
其实没有遇到什么大bug,因为第三次作业也没有引入新的求导规则,我只是在最终输出结果的时候三角函数内的输出格式不合法,修复后便通过了中测。

互测策略

我的水平不足以让我搭建自动测评机,互测时还是以手动构造数据为主,主要测了一下我自己踩过的坑,也hack到过几次,但互测效率整体还是不高。

重构经历总结

三次作业历经三次重构,可谓是遍体鳞伤,重构的原因一是我在写代码时没有考虑到后续的需求(例如第二次作业),二就是自己写的代码虽然通过了中测,但在强测中显现出了架构上的缺陷,为了写一个更好的代码选择了重构(例如第三次作业)。重构的过程中,逐渐引入了继承关系,工厂模式等面向对象思想的代码,也算是交了学费吧。

心得体会

三次作业下来其实最大的感受其实是太累了,三次作业难度都很高,尤其是第二次,整整三个周末都泡在宿舍里一直写代码,没有老师教导怎样的思路是最好的,自己思考的思路又往往会出现这样那样的缺陷,本人的交际圈也有限,与几位同学交流以及研讨课过后总算是渐渐明确了思路,最终基本上每次都掐着ddl通过了中测,极限通关。但是累归累,收获也还是挺多的,首先当然是深刻体会到了面向对象编程的基本思想与优势,在三次作业的折磨中逐渐明确了面向对象的思想,其次也深刻意识到了代码风格的重要性,难以想象如果没有代码风格要求我在互测时会看到怎样的魔鬼代码,最后也是最重要的,提高了我的编程能力,学到了很多新知识。

总而言之,痛苦伴随着收获,希望下次作业我能写出优美又潇洒的代码。
2021.3.27

posted @ 2021-03-27 19:27  kiasama  阅读(93)  评论(1编辑  收藏  举报