301
社区成员
发帖
与我相关
我的任务
分享因为没有上过OOpre,没有java基础的我在开始面向对象课程的初期无疑是十分痛苦的。我熬了不少夜,看了许多博客,磕磕绊绊地完成了第一单元的任务。回过头来看,不禁感觉到满满的成就感,我写下这篇博客,不仅是为了任务,也抱着一点点希望,希望它能够帮助到像我一样曾感到困惑的学弟学妹们。
第一单元最终总体架构如下:

可以看出,我的第一单元代码主要分为2个部分:表达式的词法解析,语法树的构建,其中计算部分被我分散在类的多个方法中,这就导致表达式,项和因子之间的相互依赖性较高。
根据面向对象的思想,我们要思考建立哪些类,这就要求我们切分表达式的结构。我将表达式的结构划分为三个层次,分别是表达式Expr、项Term和因子Factor。表达式是项的和,项是因子的积,而因子又可分为常数因子ConFactor,幂函数因子PowFactor,指数因子ExpFactor,表达式因子Expr等。
根据此次作业的强测和互测情况来看,架构的总体情况还是令人满意的,三次作业的迭代过程中没有进行过大规模的重构,且时间性能上较为优异,这得益于对未来需求的大致预估与规划,同时也十分感谢博客和讨论区中他人分享的思路,这为我建立一个好的架构思路提供了不小的助力。


从表格可见,方法Expr.equal的复杂度较高。在实现这个方法时需要遍历表达式的每一项进行比较,所以复杂度相对较高,但感觉没有很好的优化方法。

可以看到Expr与Parser类的复杂度较高,这可能是由于这两个类中包含了大量递归下降的结构,导致解析时的反复调用。

可以看到,Expr与Parser类代码规模最大,这是因为Expr承担着多项式和因子的双重功能,Parser枚举出现的字母进行处理,由于情况较多导致码量较高。虽然看起来代码量很大,但因为是在三周的迭代过程中完成的,所以任务量并不很高,相比之下,我个人感觉debug才是最消耗时间的部分。
第一次是从零到一的过程,也是建立总体框架,极大程度上决定后续要不要重构的过程。第一次作业我们面对的问题主要是如何将字符串解析为一个表达式,以及得到表达式后该如何化简。
递归下降:这是OOlens公众号与众多博客推荐使用的算法,它抓住了表达式可一直拆分至因子的特点,将某一部分划分成更小的层次,该层次又能被划分,如此循环,因此只需要注意到因子Factor中可能包含表达式因子Expr这一特点,进行针对性的容器构建。同时我的Parser类里也沿用了此类思想,在解析表达式时调用解析项的方法,解析项时调用解析因子的方法,若因子含有表达式,则又调用解析表达式的方法。
表达式存储:第一次作业中,可知表达式的一般形式为: $$Expr = \sum_{}^{}{a}_{i}{x}^{i}$$ 可以发现,单变量的表达式仅存在指数和系数两个重要的部分,我们可以考虑使用HashMap来存储表达式,同时也自然而然实现了合并。但我考虑到未来可能会增添的其他函数因子与多变量解析,觉得这种实现方式狮子啊不易于扩展,于是建立了PowFactor这个类,利用Base存储基数,Exp存储指数部分。
表达式计算:根据题目要求,我们的目的是在Expr类里实现mul和addTerm方法,这就要求Term能够实现addFactor和mul方法,addFactor可以借助Term内含的HashMap<String, Factor>自然实现,而Term的mul就是将因子相加,这是项的性质决定的。
性能优化:本次作业的性能优化最为容易,只需要在做好同类项合并的基础上把正项提前即可。
本次作业我实现了Main PreProcess PowFactor Expr Term Factor ConFactor Lexer Parser类
第一次作业的强测与互测没有被测出bug,但其实有一个bug在第二次强测中被测试了出来,是Term传参时没有clone()的问题,导致系数出现意料之外的修改,debug用了超过6小时,以后编写代码时尤其要注意这一点。
在hack别人数据这一方面,由于是第一次作业,且中测数据极弱,发现了一些他人极为奇特的bug,比如只输出常数就没有输出,符号解析错误,无法正确读入指数等。
第二次作业的主要难点在于自定义函数和指数函数的部分,括号层数的部分由于采用了递归下降,不需要进行任何的代码修改。一开始我认为第二次作业的迭代开发较为简单,但实际上手才发现需要考虑的东西并不少。
自定义函数:关于自定义函数我建立了FuncFactor和FuncFactors类。最初的想法是将把自定义函数部分用字符串替换的方式解决,具体做法是先解析自定义函数的表达式,转化为Expr,同时记录实参的映射关系以供调用时替换字符串,但我发现我绕了一个弯,我将Expr转成String,之后又要ParseExpr,所以我去除了转换成字符串这一步骤,直接返回Expr即可。
指数函数:建立了Factor下的子类ExpFactor,其存储着Expr型的指数函数内部表达式inner,由于第一次没有采用HashMap,这次仅仅需要在Term下把指数函数因子的部分考虑进去,为其实现单独的加法、乘法与合并规则就完成了。
性能优化:从此次作业开始,优化的上限极高,主要是指数函数提取公因数以及分组的神奇化简,然而我由于此次作业进度不佳,化简存在一些bug,为了正确性考虑,故将化简的部分全部注释了事,没想到却因为项数太多导致CPU_TIME_LIMITED,不仅没拿到多少性能分,正确性的分数也丢失了不少。
本次作业我新实现了ExpFactor FuncFactor FuncFactors类。
第二次作业的强测与互测中被测出了不少bug,其类型如前文所述,分别是传参时忘记clone()和取消了化简,花了半天将它们全部定位并改正。
互测时发现仍有一些同学无法处理0-0型的内容,同时指数函数的嵌套处理也产生了问题。
第三次作业的难度在三次作业中最为简单,依照实验代码中的思路,仅仅花了一个上午就搞定了求导部分。允许自定义函数调用已定义的其他自定义函数,感觉我的写法十分凑巧,只需添加一行代码便可完成新增的需求。
求导:对常数因子,幂函数因子,指数函数因子都按照求导规则实现了其求导方法,对Term和Expr的求导需要注意链式规则和乘法法则,不具备很多思维上的难度。
自定义函数: 我的自定义函数FuncFactor会载入到FuncFactors中,解析表达式时会传入FuncFactors以实现调用,只需在解析自定义函数时传入当前的FuncFactors即可完成需求。
性能优化:此次作业在同类项合并的基础上,我实现了指数函数提取gcd系数,同时与原长进行比较,择优输出。至于其余的化简方法则由于精力所限未能付诸实施。
本次作业我未新增任何类,只在每个类中新实现了derive方法,并在Parser中加入了dx相关的处理。我深深感受到一个扩展性良好的架构的便利之处。
第三次作业在强测和互测中仍没有发现任何bug,性能分也比较高,总体来说我对我的第一单元完成成果还是较为满意的。
互测时整个房间没有发现任何bug(笑),证明本次任务新增的需求并不难,大家在经历了前两周的测试后,写出的程序也都较为完备。
Hack是一个找出代码bug的过程,一个善于Hack的人写出的代码常常不易被Hack,互测过程中我还是找到了别人的不少bug的,下面就分享以一下我的测试和Hack策略。
阅读他人代码,找到逻辑上的错误或疏忽之处。这个方法较为耗时,且效果并不理想,故我个人不是很喜欢这种方式。
针对指导书上的新增需求和限制构造极端样例,这个方法让我成功刀到不少人。
用评测机按照形式化规则生成大量随机样例以提高攻击范围。
三个星期以来,我从一个Java小白到成功入门Java,对面向对象课程有了自己的理解与体会。在debug的过程中发现自己对继承,深浅克隆等java语法内容不够了解,会去主动学习,不断增进代码知识。最后,我很感谢自己三周以来的努力,让我在最后这一刻感受到了无比的成就感。
希望OOpre能在大二上学期成为置课,或者在OO课程前提供Pre或相关资料以供没有java基础的同学学习。
希望互测提交数据点时能够显示cost是否超标。