1 程序分析
1.1 第一次作业
UML图:
行数:
复杂度:
简述:
本次作业难度较低,只有简单幂函数的求导,所以我直接按照输入、存储、求导、输出的思路进行编写。
由于不需要判断WF,在输入时直接利用正则表达式,写出一项的正则然后用find方法找到一项,传给解析函数解析存储。
对象方面,在类内部又写了个静态类Poly存储一项的系数和指数,并有求导方法。
对于化简,建立一个TreeMap,Key为指数,Value为系数,即可完成合并同类项。对于正项提前不再详述。
输出部分单独写了一个函数,里面包括了全部可省略情况的判断和处理,所以复杂度较高。
总而言之,第一次程序很面向过程,没有对后续迭代进行思考。
测试阶段:
自行测试阶段并未搭建自动评测机,而是构建了许多极端情况进行测试。
不出意料,强测获得满分。
互测未被hack成功。在提交测试数据时,我提交了系数或指数为0以及超长整数数据的测试点,均未hack成功。但互测结束后才发现有一位同学的数据存储类型竟使用了long,这显然是无法满足要求的,怀疑自己在超长整数测试点提交了一个63位的数据而并未检查,属实大意。
1.2 第二次作业
UML图:
行数:
复杂度:
简述:
第二次作业相较第一次作业区别在于增添了三角函数因子和WF的判断。
对于WF,我直接用了一个大正则,匹配失败即返回WF。另外使用find检查幂函数指数范围。(已经做好第三次重构的准备)
对于新增因子,将每一项都看成a*x^b*sin(x)^c*cos(x)^d的形式,Poly类增加三角函数幂字段,更改求导方法,将求导结果的三项存入ArrayList再合并。
增添Keys类作为Map的Key,实现合并同类项。
在化简方面,通过试探将所有sin(x)^2换成1-cos(x)^2、cos(x)^2换成1-sin(x)^2后比较三者长度,取最短情况。此外,对a*x^b*sin(x)^(c+2)*cos(x)^d和a*x^b*sin(x)^c*cos(x)^(d+2)两项也可以实现合并。可以发现这些化简普适性很弱,只能化简一些特殊情况。
Polyterm类复杂度较高同样是因为在其toString方法中判断可省略情况造成的。
测试阶段:
自行测试阶段延续了第一次作业的策略。
强测中,性能分不出所料表现不佳,还WA了一个WF的判断,这其实是第一次作业的遗漏,我把空格和tab在正则中写成了\s。最终分数勉强上90。
互测阶段没有被hack成功。考虑到这次限制更多,加大了边缘样例的测试力度,最终在幂函数指数判断和表达式化简方面成功hack5次。
1.3 第三次作业
UML图:
行数:
复杂度:
简述:
第三次作业较第二次难度有了飞升,在各个方面都不再能延续第二次作业中的处理。笔者已经料到这个情况,在第三次作业开始前就有了一定的思路,故重构较快。
在数据结构方面,采用二叉树,设置幂函数、Sin、Cos和常数四个叶子结点类,设置加、减、乘三个非叶子结点类。每类均实现Expression接口的求导和toString方法,按数学公式返回正确的导数,实现了递归求导和输出。
设置Factory工厂统一创建对象,利用PublicOperate抽象类简化工厂创建对象操作,所有结点类均继承PublicOperate。
在输入方面,对于合法表达式,为了处理括号造成的优先级改变,设置运算符栈和因子栈,实现表达式优先级从最高到最低实例化的顺序,最终返回一个表达式类对象。
对于输出,使用每一类的toString即可,需要注意高优先级实现低优先级的toString时要加括号。
对于化简,在工厂类中对于可化简情况进行判断,如相同项加减、幂函数或三角函数指数的乘积或指数的指数、常数之间的合并等。但由于采用二叉树而非多叉树,在相同层次之间的遍历比较困难,只有在可化简因子互为兄弟结点或根节点相邻时才能化简,所以局限性同样较大。
通过复杂度分析可以看出,在判断WF时由于设置了多个函数分别检测非法字符、括号、空格,Judgement类复杂度较高;在输入解析时用到与栈相关的复杂逻辑,Parser类复杂度较高;在工厂实例化过程中进行化简,Factory类复杂度较高。
测试阶段:
自行测试阶段采取与前两次相同的策略。
强测结果公布前,笔者发现了自己的两个bug,不过需要一定格式的输入才能触发。由于本次性能分算法改变,加上嵌套形式的化简更为困难,故在强测中获得接近满分的成绩,但程序仍然具有一定问题。
互测中被hack成功4次,原因均为上述的两个bug产生的样例。具体而言,一个是减法类第二项忘了判断加括号的情况,一个是两个表达式因子项即便相同也不能合并为指数形式。还好需要改动的地方并不多,笔者在一次合并修复中便将问题修复。在提交测试用例时,考虑到一定数量的同学使用函数递归解析括号,于是提交了有29层括号的样例,测出两个tle。此外又提交了一些复杂因子的嵌套组合。由于此次作业代码量大逻辑复杂,阅读代码所起到的帮助比较小,只能读一读在某些基本功能的实现上有无漏洞,给互测提升了难度。不过在并未使用黑盒测试的条件下,依然hack成功6次。
2 对象创建模式
第一次作业中,基本没有考虑对象如何创建。设计好Poly类之后直接就在解析输入函数中随读随建,主类耦合度较高。
第二次作业中,虽然分离了Keys类,但总体思路仍与第一次作业差别不大,实例化后存入ArrayList备用。
第三次作业中,划分了不同的因子类,设置了Factory类工厂模式,所有创建操作都在此类完成,并通过抽象类简化实例化过程,便于调用和后续化简的操作。
3 总结与反思
本单元是接触面向对象编程模式的第一单元,对于以前从未接触过这个概念的笔者而言很有挑战。笔者编写的代码从第一次的纯面向过程,逐渐到第三次中建立了合理的对象、绑定了相应的方法,可以说有一定的进步。
程序是对数据的处理,面向对象的思想让我们从思考如何把问题一步一步解决,到思考问题的数据有什么特点,从对象角度出发实现与其相关的功能,这种转变在复杂工程中会更加有用。笔者目前的代码水平距离真正的高内聚低耦合的面向对象程序相差还很远,动不动还能撸出复杂度爆表的类。并且随着不断地学习深入,笔者相信自身对于面向对象的理解肯定与现在这个时刻又大有不同。这也告诫笔者在遇到问题时勤于转换思考的角度,同时也要考虑解决方案的可迭代性,而非以完成任务的心态做作业。
感谢老师和课程组为我们设计了优质的测试体系,让我们都能在测试中学习进步,也感谢大佬们在讨论区的点拨,希望能一同成长。