第一单元的三次做业都围绕着表达式求导,经过迭代开发的方式完成了支持幂函数和三角函数(仅包括sin和cos)及其嵌套的表达式求导程序。下面是我对这一单元学习的一个总结。javascript
本次做业难度较低,只要求了简单幂函数的求导,我使用了四个类来完成,分别为主类、解析器类、项类和多项式类。java
数据存储
分别经过项类和多项式类完成。每一个项对象存储一个幂函数项,包括其指数和系数;多项式类中用一个TreeSet对象存储若干个项,构成多项式。正则表达式
输入
主类中获取输入,交给单独的解析器类进行解析;分别经过正则表达式获取系数、指数和常数,将其组成项和多项式。算法
求导
在主类中调用多项式类的求导方法,分别对TreeSet中的每一项调用各自的求导方法,获取求导结果后建立新的TreeSet,并更新类内的域。markdown
输出
在主类中调用多项式类的输出方法,分别得到TreeSet中每一项的输出字符串,将其链接并输出。数据结构
第一次做业UML图以下:
架构
复杂度分析结果以下:
框架
因为本次做业要求较为简单,只须要对幂函数的求导,本次代码复杂度尚可,各个方法的复杂度都在可接受范围以内,四个类的内聚度也较高。async
本次做业在公测和互测中均未发现bug,化简也很到位,在强测中得到了满分。ide
在本次做业的互测中,我尝试手动构造了一些样例,但都没有hack到。最后整个room中都没有bug发现,应该是确实没有什么bug。
本次做业中新增了因子之间的乘法运算和sin、cos两个三角函数。能够注意到本次做业有很明显的表达式-项-因子三层结构,所以我使用了五个类,采用了先分割输入再依次解析的方法,却没有考虑到可扩展性,直接致使了第三次做业的重构。
数据存储
最基本的单元为因子,存储其系数(仅限于常数项)、指数;因为没有采用继承和多态,在因子对象中还须要一个类型域,以标识是幂函数,常数仍是三角函数。
项类中存储一个因子的TreeSet,表达式中存储一个项的HashSet,构成三级结构。
**
替换,将连续的正负号处理为单独的正负号+-
将表达式分割为项;根据*
将项分割为因子求导和输出
和第一次做业同样,经过主类调用多项式对象的相应方法,多项式对象再依次调用项对象的相应方法,每一个项再依次调用因子的相应方法。
化简
本次做业除了第一次做业中的合并同类项和正项提早输出以外,新增了由\(sin^2x+cos^2x=1\)带来的化简可能。我采用的方法是经过屡次遍历各项,找出可能的化简状况同时化简。若是在完整的一次遍历以后都没有找到可化简的地方,就结束化简过程。
第二次做业UML图以下:
Parser.getFactor(String)
和
Term.toString()
两个方法的模块设计复杂度超标,代表其耦合度太高,这是由于这两个方法中存在大量特判状况,大部分是为了化简输出和读取省略系数或指数的因子。还有几个方法的基本复杂度超标,也有上述大量特判的缘由,另外对于
Poly.merge
方法,还存在多层循环的控制问题。这也致使了三个类的平均圈复杂度或总圈复杂度超标。
本次做业在公测中未发现bug,在互测中发现了一个bug,是因为对-+-
的正负号化简错误。本次做业化简较为成功,在强测中得到了94分,一些地方如\(1-cos^2x\)没有考虑到化简。
本次互测中只查出一个bug,是因为其在对输入幂函数指数10000的限制,误将整个过程当中的指数均限制在10000如下。这也是我在开始犯得一个错误,归根结底是没有仔细阅读指导书。根据本身在完成做业时的错误去寻找他人的错误也是一个不错的方法。
第三次做业难度大大提高,增长了表达式的嵌套,这也使得我以前的架构基本失效,只能进行重构。本次做业我使用了12个类。
数据存储
本次做业中有10个类是数据类,分别为项抽象类,幂函数、常数、sin函数、cos函数基本类,混合项抽象类,二项加法、乘法类(用于处理输入),多项加法、乘法类(用于求导、化简和输出)。
输入
本次做业我采起了上学期数据结构课程中学到的栈解析中缀表达式的算法,首先将幂函数、常数、sin函数、cos函数找到并存在队列中,将原表达式按照类型换为单个字母(如2*x+sin((x**2+x))化为n*p+s),再将字母看成单个操做数进行解析,最终获得表达式树。
二元项化多元项
经过递归的方式,将二元的加法、乘法项化为多元的相应项。
求导
我在本次做业中曾尝试在将二元项化为多元项以前进行求导,但测试发现其时间需求太高,因而采用了先化为多元项再求导的方法,时间复杂度获得显著降低。求导方法按照多态的思想,根据不一样的类型而不一样。
化简
本次做业可化简的部分实在太多,综合考虑我只进行了合并同类项和提公因式两方面,三角函数的相关化简就放弃了。在个人架构下,可合并的部分是加法项和乘法项。其中乘法项只需考虑合并同类(由于不容许表达式带指数),加法项须要考虑合并同类项和提公因式,其实在原理上是一致的。
化简的思路仍是经过若干次遍历,找到可合并的两项,若是在一次完整的遍历中没有合并点,就结束化简。对于乘法项,找到合并点后能够结束遍历直接化简,而加法项则须要找到化简程度最大的两项进行化简。
输出
各方面考虑下,我为每一个类设计了一个获取系数的方法和一个获取除系数外部分字符串的方法,在项抽象类中编写toString()
方法将两者合并为输出。
第三次做业UML图以下:
本次做业在公测中出现了两个问题,得分70分,在互测中也出现了其中的一个问题。一个很是低级的错误是我因为思惟惯性,在对输出进行化简时还依照前两次做业,将x**2
化简为x*x
,直接致使x*x
单独出如今三角函数中时的格式错误。公测一共WA了6个点,有5个都是由于这个格式错误。另外一个Bug是对于栈解析表达式算法的错误,在回忆时出现了误差。性能方面,个人化简仍是很成功的,在强测经过的点中性能分均为满分。
本次互测中共hack到11个点,主要仍是依照本身在完成做业过程当中发生的问题进行构造样例;此外,还构造了一些大量嵌套的样例,然而并无hack到TLE,反倒hack到了一些格式错误。
其实第三次做业很适合使用工厂模式,只是当时对工厂模式优越性的理解还不够,就没有采用,而是将从输入的字符串到表达式树的过程所有放在一个解析器类中,形成了极大的复杂度。能够将解析器类进行拆分,令其只进行找出每个最小单元的任务,剩下的将字符串转为项的工做能够交给工厂完成。这样应该能够大大下降耦合程度。
感谢寒假pre做业
寒假的两弹pre做业做为热身,效果仍是很不错的。第一是令我开始使用面向对象的思惟方式,第二是能熟悉Java语言的基本使用。
先想清楚再写代码
在第二次做业中,我看完指导书就打开了第一次做业的代码开始改,结果很显而易见,遇到了大量的bug,添加了无数个补丁才把这个程序稳住。第三次做业若是像这样去作那必然是原地爆炸,因此我仍是选择了先想清楚,要使用什么结构,怎样解析输入,使用什么化简算法,等这些大框架定下来以后,写起代码来也会流畅不少,只须要额外处理一些细节问题。只是这两次做业在分数上彷佛并不支持这个结论,我选择把它归为偶然。
考虑后续任务
也就是保持可扩展性。OO每一个单元的做业都是以迭代的方式进行的,若是不在以前的做业中考虑可扩展性,那每次做业都面临着重构,工做量反而会大大提高。
第一单元的学习就这样过去了,不得不说压力仍是比较大的,但收获也是不少。但愿在接下来的做业中能取得更好的结果。