一、前三次作业内容分析
前言
第一单元的作业以表达式求导为主题,分三次要求逐步增加,难度逐步提高。这三次作业下来,本人既有收获,也有遗憾,因此通过接下来的内容对我这三次作业进行分析和总结,希望能能为我以后的编程学习提供借鉴和经验。
第一次作业
作业要求简述:多项式求导,多项式由多个项之间用加减运算符连接而成,项由有符号整数和幂函数的乘积构成,支持一些符合数学规则的省略形式,不需要读表达式的格式正确判断。
简要思路:第一次作业要求相对简单,且表达式的项具有统一的表述形式a*x**b,只需建立一个Item类和相应的集合类即可。
类图
由于本人在MainClass中放入了一些对表达式进行预处理的函数,且用了正则表达式循环查找匹配并替换,部分方法的复杂度较高,同时为了减少单个方法的长度,本人将一些小的模块都拆分出来作为新的方法,所以方法数目较多,但大多方法都室友十几行甚至几行。
性能分析:这次的性能分相对好拿,只要将输出逻辑极致简化,并将正项排在第一个输出即可保证最短输出长度。
Bug分析:虽然第一次作业十分简单,但本人在输出逻辑犯了一个十分低级的错误,在互测环节被测出,对此应当加以警戒。
第二次作业
作业要求简述:多项式求导,增加简单三角函数和简单的格式判断,并且相乘的因子是不限个数的。
简要思路:第二次作业增加了两个难点,无限因子相乘(三角函数的增加不算难点),格式正误的判断。前者在正则表达式匹配的基础上增加对字符串的一些处理逻辑(将乘号相连的因子合并为一项),后者通过正则表达式表述出表达式的统一格式即可。这次的表达式的项同样具有统一的表述形式a*x**b*sin(x)**c*cos(x)**d,也只需建立一个Item类和相应的集合类即可。
类图:
显然本人分了三个类:Item(项),Itemgroup(项集合),MainClass(执行入口),没有继承关系。
复杂度分析:
同样,为了保证方法长度符合checkstyle,本人拆分了许多小方法,由于在格式检查中使用了结构较为复杂的正则表达式,一些方法的复杂度有所增加。同时,由于格式的自由度增加,一些复杂的字符串处理函数也使得总复杂度增加。
性能分析:这次的性能分也比较好拿,但由于在第一次的基础上增加了三角函数,这次的表达式化简具有了一定的竞争性,需要用到恒等变换将一些可以组合的项合并,并且要考虑项与项之间的最佳组合。其中一个思路是利用深度优先遍历找到最优解,但由于担心会复杂度过高刀子TLE以及本人算法能力较菜(主要原因)本人放弃了这个做法,只在第一次的基础上采用简单的循环匹配组合将可能组合的项合并,并未考虑最优解,只追求了次优解。
Bug分析:虽然第一次作业比较简单,本人没有犯错误(或者没有被找到错误)。
第三次作业
作业要求简述:多项式求导,增加三角函数的内部因子嵌套和括号因子嵌套,并且增加了格式判断的要求(空白字符位置的判断)。
简要思路:第三次作业由于括号因子的嵌套(三角函数内部的括号也算),难度相对于第二次呈几何倍数增长,项也因此不具有统一格式,不能用纯粹的正则表达式,而需要增加较复杂的字符串处理逻辑。由于本人较菜,没有像一些大佬一样用表达式树存储,而是采用了许多不同的Class,嵌套存储。为了解决嵌套的问题,本人构造了一套简单体系:表达式=项+-项+-项......,项=因子*因子*因子......,由于因子的定义较为严格,因此具有统一的格式可以此作为突破口和起始点。
类图:
因子具有五个基本类型:常数(Constant),幂函数(Power),正弦函数(Sin),余弦函数(Cos),括号嵌套的表达式(Expression),都继承自抽象类Factor。Factorgroup是因子集合,表示多个因子相乘;Item是项,拥有一个Factorgroup;Itemgroup是项集合,表示多项相加(减)。注意除了宏观的Itemgroup外每个Expression也具有一个Itemgroup。
复杂度分析:
在格式检查,表达式处理及存储,表达式求导,表达式输出中,本人都用到了递归调用的方法,再加之多个字符串处理函数使得方法复杂度陡然增加,但由于本人较菜,且这次作业难度相对较高,本人并未想出合理的解决方法,只能硬着头皮构造代码。通过学习room里其他人的代码,我发现采用表达式树的存储结构能有效降低各部分的复杂度,类的数量也大大减少。也有的人不采用结构进行存储,而是判断完类型之后交由不同的类直接对字符串变形处理并输出,这样一步到位的方法虽然操作难度高,犯错风险大,却省去了表达式存储的步骤,将表达式求导和输出合二为一代码复杂度和简洁性大大提升。这些都值得我去学习。
性能分析:由于难度门槛的提高,这次的性能分成为了奢侈的存在,也成了大佬相互竞标的项目。由于本人水平较低,只对一些基本的点进行处理如嵌套括号消除,相乘因子合并,输出格式简化等,对于那些复杂的化简如提出公因子,因式分解,和差化积本人均加以放弃,但奇迹的是本人在这次性能竞争中得分不错,强测中通过的点均以满分或接近满分(99.9)通过,经分析,那些需要数学公式的情况均是稀有情况随机haul构造的数据出现几率极低。
Bug分析:这次的作业本人十分不满意,因为bug数目高达3个,直接在强测中错了4个点,经过对样例的分析,bug集中在正则表达式的构造上,且都是情况考虑不周全导致的错误。我应当在构造完代码后进行形式化验证,而不是直接进行长度优化,通过中测以后的查错工作也十分不到位,以后的作业中还需要加强。
二、反思与总结
1.分析自己和他人程序采取的策略
在自测和荷测方面,犹犹豫豫本人在python脚本能力的欠缺,并没有像一些大佬一样搭建测评机和自动数据构造机,因此采用的是人工构造数据的方式,因此需要具有一定的策略。本人认为查出bug的数据应该做到最简,通过不断删去多余部分以逼近错误的根源。当然有些错误是一些部分组合起来才会出现,这一点也要注意。这不仅是在发现使自己代码出错的数据时的处理方式,因为要查到出错根源,更是在互测时提交样例的策略,这是出于人道主义的关怀,当互测结束后,同学拿到出错数据时也能处理得更方便。
2.应用对象创建模式
由于本人的考虑不周和懒散的特性,前两次作业因为实现难度较低且具有统一的形式表述(或者经过一定处理后具有统一的形式表述),本人并没有采用合理的类与继承关系来构造一个统一的架构来解决问题,自然而然地使得第三次作业不得不进行完全的重构。而许多同学现学现用,将简单工厂模式运用到了作业当中,从项与因子这些类的体系构建,到因子与表达式的生成独立封装成类,这些方式使得代码可扩展性,复用性和可读性都大大提高。
3.对比与体会
虽然第三次作业的大失误给本人很大的打击,但是本人对于作业最不满意的地方在于代码过于冗长,为了满足checkstyle,本人将很多内容都堆积到了Mainclass的其他方法,而并没有抽象封装到其他的类,许多发放具有重复的功能却没有合并在一起门,而是分散在个各类中成为单独的方法,同时也增加了代码的总复杂度(很多都是重复造轮子)。好在课程具有互测环节,在查找他人bug的同时也可以进行学习,课程组也给出了模范代码供我们学习(膜拜)。经过三次互测环节,本人发现我与其他人代码的差距,本人的类都是实体类,比如项,表达式等等,构造方式十分朴素;其他许多人在此基础上构造了一些作用类,它们不是实体,定义为类的原因是为了实现一些功能,比如字符串处理类,格式判断类,表达式化简类等等,这些类的核心在于其具有的方法,这些方法在main类,项类,因子类中频繁被调用,因此被抽象封装出来作为项目中的一套工具。这些都是课上介绍的工厂模式在作业中的运用,而本人则十分莽撞,为了图方便,一个类需要调用什么方法就在这个类上面加上,这是十分不好的习惯。
3.善用讨论区
每次作业,课程组都发布了讨论区,同学们可以在上面提问,为别人答疑以及提供自己的思路方法。虽然本人么有直接参与讨论区的讨论,但讨论区对于本人的作用是不可否认的。尤其是对于表达式形式的理解方面,讨论区大佬的发言给了本人很大的方面,否则仅仅通过阅读指导书,本人可能会忽略很多情况导致更多的bug。