BUAA_OO第一单元总结性博客作业——表达式求导问题
一、程序设计思路与结构分析
在这三次作业中我主要是把求导的类型分成几个类,分别进行求导,体现了面向对象的思维方法。但实现的具体方法不尽相同。
(一)、第一次作业
在第一次作业中,主要分为两个部分,一、判断合法性,二、求导。
1、首先面对的便是表达式合法性的判断,我直接利用了正则表达式对整个表达式进行了合法性判断。首先,我将表达式首先分为第一项与非第一项的区别,易知,第一项前正负号可有可无,而其他项正负号必须存在,除此之外,二者并无其他区别,于是只用考虑清楚一项便可以实现判断;其次,在一项中,有两种可能性,常数or一般幂函数;然后,在常数内部又分为带符号常数与无符号常数,一般幂函数又分为含有“^……”和不含的形式,带系数与不带系数的形式……通过分析即可得到正则表达式如下图
2、进行求导,经分析可知求导分为三个层次,一是切分出单项式,二是对单项式的求导,三是对单项式求导的加减合并。切分则以+-进行,切分出来存入ArrayList,利用求导公式对其系数与指数进行相应操作即可,最后进行合并同类项即可。
(二)、第二次作业
在学习了两周的OO课程,学习了大佬们一些tricks,老师对结构的再三强调之下,我对我的方法进行了重构(因为第一次作业相当于一main到底的结构,丑到人神共愤!!!被强烈吐槽),程序主要分为了下图几个部分。
其结构如下图
第二次作业将程序分为8个类:
Derivation:为主类
Inputhandler:解决输入问题
Check:判断字符串的合法性检测
potrixDe:解决求导问题,求导问题分为三类,常数求导,单项式求导和三角函数求导,利用ArrayList进行存储,第1项代表其系数,第2项代表其x的次数。第3项代表sin(x)的次数,第4项代表cos(x)的次数;
Outputhandler:进行输出操作。
在check类内,我对第一次作业的方法进行改进,先排除到\t与空格符不满足的条件,在这里我出现过多次错误,主要是每一项的第一个因子的问题,其’[+-]’最多3个,最终经过我分析发现简化方法,分为两种情况:
①出现连续少于三个’[+-]’不论\t, 空格在其中哪均正确
②连续三个’[+-]’,则在其末尾不能有\t, 空格。如此便能轻松解决问题。
(三)、第三次作业
程序结构与第二次类似
大致关系如图:
增添的PoTrDe主要进行了切分项,切分因子,递归求导,对幂函数,三角函数求导的功能。
在这次作业中,判断字符串的合法性和求导难度都上了一个台阶,由于出现了嵌套的问题,嵌套次数不确定,故找不到很好的容器来存储系数与次数的问题,于是我转换了方向,进行直接求导。
1、合法性的判断:这里有小tricks。初始时我的check函数有很多漏洞,是分嵌套和非嵌套进行的,非常容易出错。后来我突然想到其实表达式因子实际上相当于一个x,如果我把所有的表达式因子替换成x,一直替换至没有表达式因子,最后判断替换后的表达式的形式是否满足第二次测试的expression,岂不是美滋滋。
但其中有很多需要注意的地方:
当匹配到(expressiong)需要判断begin前3个字符是不是sin,cos,此处又分为2种情况,
A) 是sin,cos:由于三角函数只有一层括号时只能是简单的因子,故判断expression是否是简单的常数因子,单项式因子,三角函数因子,如果是,将整个三角项替换为x(此处一定要小心,只替换expression则会进入死循环,一直都能寻找到(……)),之后继续判断,否则不满足,return false,输出wrongformat。
B) 不是则继续替换。
代码如图:
这一个方法十分简单且效果很好,有效地解决了基本的check format问题。
2、求导:利用递归求导
递归求导函数特殊情况就是传入的为一个因子或者满足第二次作业的形式:
(1)如果满足第二次作业的形式,则直接调用第二次的函数
(2)如果是一个因子,分为三种情况
①(exp)类型,去括号,继续递归求导
②指数函数类型,则执行相应操作
③三角函数类型,执行相应操作
(3)上述情况均不满足则进行下述操作
①切分项:利用判断装有括号的栈的size==0,字符为[+-],且此时下标为0(千万别忽视了这种情况!!!否则会很容易stackoverflow,笔者在强测凉凉就是由于这个原因)或者前一个字符不是^,*的情况即可判定前面的字符已经成为了一项,添加入项的数组
②继续切分为因子,同样利用新的括号的stack.size=0,字符为*则说明前面的字符串已经是一项了。最后利用求导公式进行求导,进入递归求导函数。
二、程序结构分析
利用度量工具实现了算法复杂度的判断。方法的复杂度分析主要基于循环复杂度的计算。循环复杂度是一种表示程序复杂度的软件度量,由程序流程图中的“基础路径”数量得来。
(1)ev(G):Essentail Complexity,用来表示一个方法的结构化程度,范围在$[1,v(G)]$之间,值越大则程序的结构越“病态”,其计算过程和图的“缩点”有关。
(2)iv(G):Design Complexity,用来表示一个方法与所调用的其他方法的紧密程度,范围也在$[1,v(G)]$之间,值越大联系越紧密。
(3)v(G):循环复杂度,可以理解为穷尽程序流程每一条路径所需要的试验次数。
第一次作业类图及度量:
第二次作业类图及度量:
第三次作业类图及度量:
优点:设计比较独立。
缺点:部分方法时间复杂度过高。
三、bug分析
1、自己的bug:
(1)第一次实验的bug:没有使用BigInteger类型,导致强测中有关的5个点全部凉凉。
(2)第二次实验中由于切分项时没有考虑到^+or-等形式,导致切分出错
(3)第三次实验中,
①由于想到第二次作业中需要考虑到*+or-,^+or-等形式,当存储的str长度不为0时,存入项中,但是仍旧忽略了下标为0时存入+,下一个字符仍为+ or -,则会将第一个+or-存入,并进行求导,导致一直递归而爆栈。
②符号被忽略,以及括号问题,笔误,为了简化,导致一直求导,从而栈溢出等问题。
总的来说,我的bug总要分为两类:考虑不全,笔误。
在今后的测试过程中一定要脑子清醒,仔细考虑各种情况,否则代码5小时,debug两天,最后还是凉凉的惨剧会一直发生。
2、发现别人的bug所用策略:
(1)自己的测试用例
(2)收集其他人的测试用例
(3)结合测试程序,利用上次的一些测试样例
主要还是比较具有代表性意义的数据,最后开6到7idea进行测试,利用python来判断他人的答案是否正确(很low……)
四、Applying Creational Pattern
在我的三次作业,对“面向对象”概念并没有深入的应用,没有很好地进行类的设计和划分,对继承,接口方法并没有什么涉及,初始写代码时,类分得还是比较清楚,但到了最后,求导的方法还是凑到了一个类内,这是十分不利的。应该按照老师所提示的将求导中的方法powerfunct,三角函数求导分离出来,不让代码看起来那么臃肿。
代码复用性在第三次作业中体现的还是可以,利用好了第二次作业的正则表达式和基本求导方法。