OO第一单元总结(表达式求导)
前言
OO第一单元一共有三次代码设计作业,主题是表达式求导,从最初的简单多项式求导,到之后的三角函数求导,再到嵌套因子的求导,可谓是逐层递进,引人入胜。其中也给我留下了许多值得珍惜的回忆,在此,我将用博客的形式将其总结一下,来为第一单元画上一个完美的句号。
第一次作业
作业内容
第一次作业需要完成的任务为简单多项式导函数的求解,由于求导模式较为简单,并且保证了输入表达式的合法性,因此总体难度一般。
思路分析
通过阅读作业内容,我们可以知道,输入字符串为表达式的形式,而表达式又由项组成,因此我们自然而然的想到了将表达式拆分成很多个项,然后对每个项进行求导,最后再连接起来输出。考虑到本次作业的项仅仅是ax^b的形式,因此我没有将项进一步细分为因子然后再求导,而是利用求导公式直接对项进行处理,因此缺乏一定的可扩展性,不过解决这个问题已经是绰绰有余了。最后再将相同指数的项进行合并,优化输出效果。
基于度量的程序结构分析
方法度量
本设计使用的方法数不算太多,有很大一部分是Term类中的get和set方法,而main中主要承担了字符串的处理以及输出等工作。
类度量
这次作业仅仅使用了两个类,分别是主类和项类,因此还有很大一部分的面向过程的影子,并且主体集中在主类中。给我的感受是处理起来还比较方便,但是如果要在此基础上添加新的功能,可能就不再适用了,因此我在之后的作业中开始考虑起了面向对象的思想,也成功的实现了多种形式求导的功能。
类图分析
此类图阐明了两个类的相互关系,即主类中create了Term类。
Bug分析
自身bug
可能是由于功能较为简单的原因,第一次作业在强测和互测中均未发现bug。
他人bug
第一次作业没有搭建评测机,而是直接手动设计了一些测试数据,再加上作业功能要求单一,不容易出现错误,因此没能捕获到他人的bug。
优缺点分析
优点
这次作业设计的亮点在于对字符串的处理以及合并同类项。在处理字符串时,我采用了整体设计,即将整个字符串一次性读入,然后再进行拆分处理,而不是一个一个字符的读入,这样做的好处在于简单并且浅显易懂,大致设计如下:
另外在处理项合并时,可以通过使用ArrayList,来调用每个项的指数、系数,然后进行判断与合并,这其中我用到了coeff和index及其各自的get、set方法。
缺点
划分还不够细致,最终只停留在处理项上,而没有进一步细分为因子之间的运算,需要进一步改进或者重构。
第二次作业
作业内容
本次作业,需要完成的任务为包含简单幂函数和简单正余弦函数的导函数的求解。备注:包含嵌套括号!!!难度瞬间提起来了。
思路分析
花了long time看完题目之后,我受到了降维打击。苦苦思考了几天,最初打算在第一次作业基础上进行改进,后来发现无论如何也解决不了括号嵌套的问题,因此不得不放弃这个思路。然后我想到,要不先尝试把括号全部展开,让每一项都变成ax^bsin(x)^mcos^n的形式,然后再对每一项进行求导,最后合并输出。事实上,理想是美好的,并且后面的步骤也并不算难,而真正的难点就是前面的括号展开功能,这个功能我始终实现不了,无奈之下也不得不放弃。最后我选择了重构,这来自于表达式树给我的启发,因为表达式树的叶子节点刚好可以存放因子,而根节点可以存放符号,因此到时候只需要一次遍历,就能够访问整个表达式。关于求导,在遍历过程中,会对每一个节点的内容进行判断,如果是符号,则按照符号规则计算其导数。当然,其中会使用到孩子节点的导数和子树的内容,构成一种递归的模型。对于表达式树的建立,我将它的过程分为了两步,首先将输入的表达式由中缀转换成后缀表达式,然后再利用后缀表达式建立二叉树。总体而言,第二次作业要用到很多数据结构的知识,包括容器、栈、二叉树等,但是理解起来并不难,只要合理使用、互相配合即可。
基于度量的程序结构分析
方法度量
本次作业的方法数相对较多,毕竟要实现较为复杂的功能,由于是重构过的设计,因此与第一次作业的方法已经大体不一样了,但是也还是能够看到第一次作业的影子,比如对字符串整体的处理,让它变化成我们想要的样子。
类度量
本次设计包含四个类,主体是主类和树类,已初步具有面向对象的思想,但仍需要进一步细化。
类图分析
此类图包含了四个类的相互关系,主类仅与Tree类相联系,而Tree类中使用到了Node类和Str类,他们之间存在着交互。
Bug分析
自身bug
本次作业在互测和强测中都出现了错误,导致扣分较多,一方面的原因是提交之前没有进行充分的测试,使得一些隐藏的错误没有被发现出来,另一方面是本次作业进行了重构,较为复杂,因此产生了许多新类型的错误。经过对测试数据的观察与反思,我发现问题主要出在一个不起眼的地方,就是在处理“-(”的时候,我的架构缺少了这一情况,但是如果将“-(”换成“-1*(”就可以了,所以相当于最终就只有一个bug,啦!
他人bug
本次互测我采用了评测机,成功的hack到了三个人,其中有一个是在处理sin(x)^3*cos(x)^5的时候出现了问题,不知道为什么求导的结果只有一项了,正常应当是两项,这让我印象比较深刻。当然了,不同的人所产生的bug也不尽相同,主要还是要综合考虑清楚所有的情况,不能因为偷懒而大意疏忽。
优缺点分析
优点
本次作业我采用了表达式树的方法,成功的解决了字符串求导问题,并且具有较好的可扩展性,这一点在第三次作业中可以体现出来。
缺点
由于进行了重构,导致时间紧张,来不及处理优化问题,因此性能分不高。
第三次作业
作业内容
本次作业,需要完成的任务为包含简单幂函数和简单正余弦函数及其嵌套组合函数的导函数的求解,此外,还要判断输入表达式的合法性。
思路分析
本次作业看似很复杂,但事实上,在第二次作业的基础上,本次作业需要修改的地方并不是很多。关于求导,本次作业的要求仅仅是多了一个sin和cos中包含表达式因子的情况,这个用我搭建的表达式树可以轻松解决,方法是将sin和cos看成是一个分支结点,然后其内部表达式因子作为其左孩子结点,其右孩子结点为空,在求导时仅调用左孩子结点内容即可。然后对于Wrong Format的判断,我采用的是列举所有可能出现的错误情况,对症下药,依次进行判断和解决,这个方法比较麻烦,但是思考起来并不复杂。
基于度量的程序结构分析
方法度量
类度量
由于在第二次作业的基础上,本次作业无需改变太多,因此依然只使用了四个类,大部分修改也仅仅是在MainClass和Tree类中,较为方便。
类图分析
此类图与第二次作业中的类图大体一致,仅仅是有些类中的方法发生了一定的变化,主要体现在MainClass中。
Bug分析
自身bug
本次作业在互测中没有发现bug,可能是因为互测输入的数据都是合法的,然而在强测中我出现了一个问题,就是在包含sin(+ 1)的时候,由于带符号整数的符号与数字之间不能有空白字符,而此式我没能判断出来是Wrong Format,所以错了,这也是考虑不周的效果。
他人bug
我使用cos(0)**2*x一次性hack到了两个人,他们其中一个是在运行时会报错,另一个输出了类似于cos()的东西,也是很奇怪。总而言之,bug都是千奇百怪的,不同的人会犯不同的bug,及时发现并修改就好了。
优缺点分析
优点
在第二次作业重构之后,本次作业需要添加的东西不多,这也体现了面向对象的思想,提高了可扩展性,让我感到很欣慰。
缺点
对Wrong Format的判断还有待改进,对性能的优化也需要寻找新的方法,应当多多学习讨论区里的内容。
重构经历总结
随着题目难度的增加,题目要求的变化,我们之前的设计可能已经适应不了新的需求,因此往往需要重构。这次作业我包含两个设计,一共重构了一次,说实话,重构其实还是充满许多挑战的,因为要从之前的思维习惯跳转到另一个全新的设计中,所有还要考虑许多方面的内容。为了解决表达式树的问题,我还特意翻了翻大一下学期学的数据结构有关栈的知识,因为那个时候做过类似的题目,只是不包含求导而已。在我看来,重构是很有必要性的,我们在解决某个问题时,就应当去思考未来可能需要增加的需求,思考我们该如何添加我们可能需要添加的内容,这一方面可以减少重构的次数,另一方面也加强了我们对于这个的问题的思考深度。
心得体会
第一单元的作业让我感受到了面向对象技术的优越性,它在解决问题时具有良好的可扩展性,这在一些企业里面是十分重视的一点。表达式求导问题还可以包含许多其他的功能,比如对数函数求导、tan求导、偏导数、多次求导等,我们在课余时间也可以去多想想,提高自我的全局掌控能力,以适应将来的发展。这段时间的编程一方面提高了我的java水平,另一方面也改变了我的编程思想,开始有了变化,变得更加深刻了,而不是停留于浅显的层次。总而言之,完成第一单元后,我既充满喜悦,也充满对未来几单元的期待,希望能够顺利通过,加油吧!