前言
在过去的一个月中,我们进行了面向对象第一单元的学习。第一单元主要是面向对象的一个基础阶段,主要是为了培养我们的对“对象”的认识,从以往的面向过程的思维中跳出来。第一单元的作业内容是多项式求导,包含了三次难度递增的编程任务。这三次任务对于少有接触面向对象的我来说,难度确实不小,但经历了这一个月的学习与训练,我的进步也十分明显。接下来我将从思路分析,代码解读,Bug and Hack以及作业点评四个方面来对这三次作业做一个总结。
第一次作业
1.1 思路分析:
第一次作业是简单多项式幂函数的求导,较为容易,不存在连乘和嵌套的情况,我认为唯一需要注意的点在于对符号的判断。项与项之间可以用符号链接,常数可以带符号,表达式前面也可以加符号,这就需要一个精确的判断。
在输入处理部分,由于没有错误处理,我是用正则表达式来直接切分输入,从项的层面,采用 [+-]? + <项> 的方式对项进行切分,也就是每一个项前面都会带一个符号。再从项中提取出幂函数的系数和指数,完成求导操作。
第一次作业的优化部分也相对简单,只需对幂函数进行合并同类项,采用HashMap存储 <指数, 项> 这一键值对。但是最好边求导边比较,这样只需遍历一次导函数集合,不要在求导完成后再对整个集合遍历,造成不必要的开销。
1.2 代码解读:
首先是类图:
可以看出第一次作业的代码并没有那么面向对象,只用了一个导数类来进行求导和合并同类项的操作,其他的比如输入识别,输出调整等等全部放在main函数里了。
复杂度分析:
可以看出将所有方法都放在两个类中使得复杂度和耦合度都很高,这并不是一个好的代码风格。
1.3 Bug and Hack:
本次作业我的强测和互测都顺利通过,没有发现bug。由于这道题本身较为简单,思路清晰,所以整个写代码过程也较为顺利没有遇到什么问题(然后后两次作业直接暴毙)。本来在互测过程中我想要搭建一个自动对拍评测机,然而bug频出导致我只能生成数据后手动测试。。使用python的xegex包生成数据,再加上我自己设计的一些临界条件使得测试数据具有一定的针对性。不过我提交了几个数据都没有刀到人。。但是阅读大佬们的代码让我也学到了很多东西,也算收获不小。
1.4 作业点评:
第一次作业是简单多项式的求导,只包含幂函数一种,没有更加复杂的括号与嵌套。这一次作业难度不大,主要是让我们熟悉面向对象的这种编程思想。但是由于我对面向对象的不熟悉,在写代码的时候思路完全转不过来,不知不觉就写成了纯粹调用函数的面向过程式编程,这也导致后来两次作业在重构上花了很多时间。
第二次作业
2.1 思路分析:
第二次作业在第一次的基础上增加了三角函数以及连乘。在输入上我还是延续第一次的思路,逐项读取。但由于这一次加入了错误处理,在输入时需要先进行判断。我的做法是使用一个长正则去匹配,同时为了防止栈溢出而使用了独占模式。
在识别输入的函数时,我使用了一个factor接口,被幂函数类,三角函数类和常函数类实现,使用工厂模式自动创建函数对象,然后链式法则求导即可。
在输出处理方面,可以发现所有的项都可以化为a*x**b*sin(x)**c**cos(x)**d
的形式,于是就以三个指数b, c, d 作为key将所有的项存入HashMap中,同第一次作业一样合并同类项。
2.2 代码解读:
类图:
本次作业我进行了大量重构,将程序分为:
-
表达式
-
项
-
因子
幂函数
三角函数
常函数
这样的结构,输入处理放在表达式中,使用工厂模式创建函数,求导时逐级向下调用,输出亦然。
复杂度分析:
可以看出我写代码的时候一方面使用了很多几乎无用的函数,另一方面又将大量的功能集中于某几个函数中,没有合理规划代码。这个之后要多加注意。
2.3 Bug and Hack
本次作业我强测和互测都顺利通过,没有发现bug。这一次我在构建测试数据的时候依然是使用自动生成数据,添加一些条件来构造复杂的,临界的数据。但是我依然没有hack到人。
虽然互测本身没有出问题,但对比了我自己和room中其他大佬的代码,我觉得我的结构还可以进一步改进。比如使用长正则匹配,就需要调整。
2.4 作业点评:
相对于第一次作业来说,本次作业不论是难度还是复杂度都大幅上升。由于我在一开始没有使用面向对象的方法,导致我这一次花了很长的时间来重构代码。这使得我这一周的时间较为紧张,代码没有更多的时间去优化。还有就是最开始的输入处理,我在明知道长正则不太妥当的情况下依然偷懒使用长正则,虽然这一次没有出问题,但为我第三次作业埋下了隐患。
第三次作业
3.1 思路分析:
第三次作业在原有基础上添加了嵌套结构,可以使用括号进行一层一层的嵌套。虽然我早已知道第三次作业的内容,但在实际写的时候还是感到很迷茫。
我大致的思路如下:首先输入先识别,这一次长正则不能直接使用,需要先对括号进行处理。我的做法是先将最外层的括号识别出来,然后将括号的内容整个替换为一个特殊字符“#”,然后对替换过的字符串进行正则匹配,这时只需要对正则表达式进行微调,便可沿用第二次作业的结构。
在识别函数的部分,基本思路与第二次一致,使用功能工厂模式自动创建函数。不同的是这一次的表达式,项和因子都是使用一个多叉树来存储(最开始是想使用二叉树来着,可惜失败了,于是就使用相对来说开销较大但是更为简单的多叉树)。一个表达式存储多个项,一个项存储多个因子,一个因子存储表达式或函数。
求导和输出部分沿用第二次作业的结构,一层一层往下调用,最后输出结果。
3.2 代码解读:
类图:
这一次代码与上一次在框架上几乎一致,我将输入处理分离出来放在Input
类中,然后将正则的规则也独立出来,使得代码更清晰。
复杂度分析:
和第二次一样的毛病,总是想要将大部分功能集中在一个函数(类)中,而不是使用多个函数相互调用。这就让我的程序的耦合度和时间复杂度提升。在之后的编程中要改正。
3.3 Bug and Hack:
这一次作业给了我一个惨痛的教训:由于时间没有把握好,加上前期没有仔细思考就贸然动手敲代码,导致我在输入处理那一部分卡了很久,写主要部分的时间被压缩,最后提交时只是匆匆测试了几个数据没有很详细的去debug,导致中测没有通过。后来我多花了一天时间才搞定所有内容,但是为时已晚。这给我提了个醒,以后OO作业发布要早点开始做,多动脑思考,在编程的时候才不会迷茫,速度也会大为提升。
说回bug本身,由于这一次我准备不够充分,整个程序的bug较多。主要集中在两部分:输入处理和求导。
在输入处理时,由于在前几次作业偷懒使用长正则,这一次在输入处理括号的时候出了bug,对于有多个平行关系(即非嵌套)的括号全部当做嵌套来识别。后来我使用一个ArrayList集合来存储平行括号的内容,将之解决。
在求导部分,由于多加了一个嵌套函数,我在求导时对原函数的处理有问题,链式法则求导,内部的嵌套函数要保留原样乘到外面,但是我在测试时经常会输出乘了导函数的结果。我的解决办法是将每一个函数类中的原函数和导函数都提取出来,分别存到两个ArrayList中,按照顺序有规律地交替调用。不过这几个修改都是在ddl之后的事了。
3.4 作业点评:
这一次的作业很失败,我对时间的把握十分模糊,且没有认真思考就开始动手敲,这其实是我的一个老毛病,也是编程的大忌。有些人写代码又快质量又高,基本都是因为他们早已对整个程序有了一个合理的规划。对于一个程序来说,思路和方法永远比代码本身重要,在动手前想清楚了,梳理好脉络,做好规划,这样在动笔写程序的时候就可以有条不紊,快速且高效的完成任务。
总结与反思:
这三次作业,虽然我逐渐尝试更加“面向对象”一些,但是我自己的代码仍然十分“面向过程”。对于接口,继承,只是初步的使用,没有太领悟到精髓。
代码风格较差,喜欢将很多功能集中放在一个函数中实现,同时又会去构造一些可有可无的函数来充数,这样代码的复杂度和耦合度都会大幅提升。这些养成的坏习惯必须要改正。
在开始编程之前一定要对程序做一个合理的规划,切忌直接上手莽,这样效率低下且质量不高。合理分配时间,之后不能再有交不上作业的情况。
希望经过一个学期的OO课程的洗礼之后,我的能力能够获得更大的提升。