OO第一单元作业总结
第一次作业
基于度量分析代码结构
-
基本算法
第一次作业是简单多项式导函数求解,不需要对输入数据的合法性进行判定,
基本思想是用 (coeff, expo)
表示二元组 coeff*x**expo
,而多项式中的每一项都可以以二元组的形式存储,这样做的好处在于多项式的每一项存储形式规范单一,求导规则随之变得很简单,再加上由于不需要考虑输入数据的合法性与否、化简相对简单,使得实现很快。
当然缺点也很明显,可拓展性很差,只能支持简单的多项式,下一次迭代必须得重构了。
UML类图:
耦合度分析:
结合代码具体分析,本次作业写了四个类,主类;Polycomputer,用于解析字符串生成表达式Poly对象;Poly类,采用ArrayList存储表达式,并且把对Poly的各种数据管理都放在这个类里了,如添加新的项、求导、化简等等,Term类,表示项。
耦合度分析显示,整个程序的基本复杂度最小,说明程序的结构化程度整体较好;
而方法smpAndPrint()和 PersePoly()干了较多的工作,导致圈复杂度(v(G))和模块设计复杂度(iv(G))都比较大。这说明smpAndPrint()模块设计的不够好,导致模块间频繁调度,该模块也承载了较多的功能,控制语句较多。
仔细想来这两个方法的实现确实不够好,由于Poly类用List存储表达式,导致了化简的时候不得不创建一个新的 HashMap,将化简后的表达式用HashMap表示,这样放不到任何一个已有的类中,只能够顺带打印输出,这样做不仅消耗了内存资源,还极大地限制了可拓展性,例如我们就不能对单独的项进行操作,也不能单纯地进行化简处理。若Poly类中采用HashMap的存储方式,就可以很好地解耦。
公测
强测测出一个bug,当表达式化简后有系数为0的项时输出可能为“0”而不加任何连接符,得分也只有91分了,于是深刻认识到强测的重要性。
问题所在类和方法为Poly类的smpAndPrint(),由于该方法设计得不好,导致打印的时候没有特判0,出错了。
互测
我方被hack了6次,都在攻击这个点呜呜呜;
互测策略
先是采用了手动构造数据进行测试,黑盒测试,没有自己观察被测程序的代码设计结构,但是我认为这次作业比较简单,大家写的大同小异,于是在边界条件、组合模式、化简上着重测试,例如系数或指数为0的情况,hack成功5次,最后对对方代码进行简单地查看,发现非同质bug应该是3个;
之后,由讨论区的帖子的启发构造出了简陋的评测机,反而一个bug也没测出来。
第二次作业
基于度量分析代码结构
基本算法
第二次作业增加了简单的正余弦函数,和一些组合规则,并且需要对数据的合法性进行判断;
是重构的一周~本次作业和上一次作业需要很大的改动,二元组是不行了,数据的输入也得进行判断;但是我发现每一项经过化简都可以表示成a*x**b*sin(x)**c*cos(x)**d
的形式,于是采用了四元组(a,b,c,d)
, 并且吸取了上一次的教训,表达式类Expression
采用HashMap<(b,c,d),a>
的容器存储表达式,这样有利于合并同类项,也有利于进一步的化简。对于输入数据合法性的处理,则是通过大正则判断,但是解析字符串提取项的时候则是更有针对性的小正则。
本次作业动笔的时候还没有想好应该怎么化简,完成基本功能后,出于对实现难度和正确性与性能之间的权衡考量我决定只依据sin(x)**2+cos(x)**2=1
进行化简。
思路如下:实现在Expression类中实现simplify方法重新创建一个hashmap<(a,b,c+d),List<c>>
,其中(a,b,c+d)
为新加入的矢量类SmpVector,(a,b,c+d)
完全相等的项,意味着有希望进行合并化简,于是将有希望的项放在一个容器里,分别处理每个List,使得c1和c2相差2的可以合并,这样就能够实现化简啦。
UML类图:
耦合度分析:
从度量分析来看,Homework2虽然实现了很多类,但是设计结构还是比较清晰的,总的来说各个类还算是各司其职,唯一不足的是Expression的重写的toString方法和DealString的StringParse方法,可能写的太复杂了?应该每一项实现toString然后在Expression中调用的。DealString太大了。
公测
有了上一次强测错一个强测分降到90、ROOM降到B屋的惨痛教训,在中测提交前我就好好测了测,对Python语法不够熟练,不太会用Exger和Sympy库,这次没来得及造评测机。
全程手测,自己构造测试数据,构造策略就是先是构造基本数据,确保基本功能正常实现;之后考虑边界条件、数据合法性、尽可能多的组合规则等等构造一些复杂的数据,尤其是针对数据的合法性以及边界条件,几乎每一次的构造数据都会涉及边界情况,如系数指数计算化简之后为0,合并之后相消等等。这样构造测出了不少bug,专门花了一个晚上,所幸,中测和强测就全AC了,但还是被同学hack了。由于只做了比较基础的优化,性能分获得了14.00021/20.
我觉得对拍还是很重要的,可惜我的python全忘了。
互测
被HACK:这次ROOM互测大家都很礼貌,见好就收,不故意提交同质数据。还是被hack了一个bug,是在toString打印的时候,特判符号应该在每一项都特判,然而我只有表达式的第一项才特判。bug确实出在了圈复杂度和模块设计复杂度高的地方ORZ。
HACK:
手动评测,感觉这屋都是大佬,代码风格都不错,手动测试了一下午一无所获,悻悻离开。
周日晚上再测,发现Saber我给漏掉了,手动构造奇异的数据测了两小时竟然找出了5个bug(后来发现至少三个非同质)。突然感觉手动构造其实优势也很明显,如果是自动评测可能不一定测的出来,毕竟有的数据我也是根据他的反馈结果一步步构造出来的,还挺刁钻。
第三次作业
基于度量分析代码结构
这次作业增加了表达式因子
的数据,组合方式多种多样,支持嵌套,求导规则变多,对数据合法性的检查要求也高了,感觉难度大了很多,我差点以为要无效作业了。
又是重构的一周~上次决定用四元组的时候就意识到了第三次作业必然又要重构了,虽然试图对架构进行了更高的抽象,但不知道怎么实现,于是无奈只能用四元组。恰好这次课讲了继承和接口,突然意识到其实我之前思路实现的障碍完全可以用接口来解决。
本次作业的思路如下:采纳了指导书的建议,将 sin(x)
cos(x)
幂函数因子
常数因子
这些基础因子和加法``乘法 嵌套
这些基础运算视为不同的类,然后就可以实现表达式树的构建。他们都实现了共同的接口Function
,支持求导takeDerivative
和 toString
。在这里我采用的是二叉树实现,也就是基础运算都只支持两项运算。
这样总的思路就比较清晰了,构建二叉树的时候解析字符串从下至上开始构建;输出表达式树只需要调用表达式的toString
方法即可递归实现。
我认为这里处理字符串的时候需要小心,由于有表达式因子
我们无法继续使用大正则判断提取字符串了。这里我将最外层的括号换成[]
,[]
里面的内容全部换成E
,这样就可以使用正则表达式提取字串,也能初步判断表达式的合法性;为了进一步解析子表达式可以将之前的内容替换回来,递归判断即可。所以二叉树的构建和输入数据合法性的检验都是在递归中一步步进行的。
UML类图:
耦合度分析:
这次作业主要是求稳,但求作业有效、强测能够全部AC。由于还有时间于是还是忍不住进行了简单地优化。化简主要是针对运算符.例如对于*,若两个操作数是常数a,b ,则返回常数a*b ;另外还有一些关于0 或+1或-1 的特判......等等这样实现简单易行,并且由于是递归,其实化简也会递归地进行下去,不仅能够减轻递归的负担,防止TLE,而且化简的效果也很好.
当然,后来稍微想了想,如果表达式树是多叉树,即加法和乘法可以有多个操作数,采用上述思路化简的效果可能会更棒.可惜当时已经写好了基本架构,难以大改,只能作罢.
公测
在debug的时候全程手测,由于时间来不及再加上对python语法不熟悉,没有评测机,手测是无奈之举.对复杂的程序对拍真的是一个很好的选择,由于括号太多稍微复杂一点的输入就会得到括号很多的输出,难以肉眼判断正确与否,手动构造数据真的风险很大.所幸的是可能这次算法比较简单,很多都交给递归自动完成,公测没有出现bug,性能得分17.0578/20.
互测
被HACK: 本次作业没有被有效HACK;
HACK: 由于没有评测机,只能够手测,构造一些简单的数据. 为此,我着重从合法性的层面进行测试,即构造一些数据,使得输出应该是合法的表达式,但是他却输出WF或者不合法的表达式. 通过这种方式hack成功了3次.
对比和心得体会
观摩了四位大佬的优秀代码,我发现林宇菁同学实现了我的改良方案,即多叉树。最让我印象深刻的是他的代码结构组织得非常清晰,每个方法不超过五十行,递归在同一个类的不同方法中调用进行;此外他的化简方法也让我印象深刻,他是先构建后化简,我是边构建边化简,不能说孰优孰劣吧,可能我的方法在资源消耗上占优,他的方法在代码组织上占优,解耦得很好。总之,相比之下,我在组织方面、架构设计方面还有很多可以学习。
本单元的学习让我对面向对象思想有了更深的理解和体会,总的来说能看出来我的三次作业都是在不断尝试使用面向对象的思想的,强测的平均分也在稳步提升. 这一单元学到了很多有用的知识, 但仅凭课上的学习是不够的,还有很多设计思想需要学习消化. 另外,这次作业也有一些遗憾,比如虽然在第三次作业我尝试使用工厂模式,但好像没有起到应有的效果;手测和对拍都很重要,我应该好好掌握如何实现对拍. . . . . .
但总的来说是不错的, 加油 ~