BUAA_OO_第一单元
本单元任务分为三个阶段,依次是简单多项式导函数的求解、包含简单幂函数和简单正余弦函数的导函数的求解以及包含简单幂函数和简单正余弦函数并存在内嵌多项式的导函数的求解。
一、程序分析
(一)第一次作业
1. 分析思路
第一次作业给出格式为用+-号连接的项,每一项为带有系数的幂函数。最该多项式求导后,为完成优化目标应输出合并同类项之后的结果。
因此可以项为单位,处理每一项的内容,判断其是否满足格式要求。对于每一项,可用{系数,指数}表示。
2. 具体实现
(1) 输入处理
首先判断空串与非法字符,之后便以+-号为分隔,分割出一个一个项进行内容匹配,若第一项不含+-则在前面补一个“+”,匹配的正则表达式为:
([ \t]*[+-][ \t]*[+-]?\\d+([ \t]*\\*[ \t]*x[ \t]*([ \t]*\\^[ \t]*[+-]?\\d+[ \t]*)?)?)|
([ \t]*[+-][ \t]*[+-]?[ \t]*x[ \t]*([ \t]*\\^[ \t]*[+-]?\\d+[ \t]*)?)
之后分别获取ceo和pow,按照求导公式,在类的List中存入求导后的系数和指数。
(2) 优化处理
本次作业优化概括为:合并同类项、正项提前、输出最简形式。
输入处理完成之后,类中所存放的两个List列表便是求导之后的结果,List中的每一项一一对应。之后把指数相同的项的系数相加,两项变为一项。然后判断所有项中有没有系数为正数的项,将其提前到第一位。
3. 结构分析
-
LOC: Line of Code
-
NCLOC:Non-Commented Line Of Code
file type | LOC | NCLOC |
---|---|---|
Java | 254.0 | 248.0 |
-
ev(G) 基本复杂度(Essential Complexity):衡量程序非结构化程度。
-
iv(G) 模块设计复杂度(Module Design Complexity):衡量模块判定结构,即模块和其他模块的调用关系。
-
v(G) 圈复杂度(Cyclomatic Complexity):衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
PolyDer.printAns(Vector,Vector) | 6.0 | 13.0 | 14.0 |
PolyDer.Merger() | 3.0 | 5.0 | 6.0 |
PolyDer.main(String[]) | 1.0 | 3.0 | 3.0 |
PolyDer.hasWrongChar(String) | 1.0 | 1.0 | 1.0 |
PolyDer.findSub(String,int) | 8.0 | 5.0 | 14.0 |
PolyDer.computeTerm(String) | 1.0 | 15.0 | 16.0 |
PolyDer.checkContent(String) | 6.0 | 6.0 | 7.0 |
Total | 26.0 | 48.0 | 61.0 |
Average | 3.7142857142857144 | 6.857142857142857 | 8.714285714285714 |
从以上的数据中可以看出,第一次作业其实是按照面向过程的程序思路完成的,并且存在着冗余的现象,多次遍历,复杂度较高。
本次作业的优点在于实现了正确性并具有较好的优化性能,完成得比较快;缺点在于完全不具有可扩展性。
(二)第二次作业
1. 分析思路
第二次作业比起第一次作业增加了正余弦函数的求导,增加了因子种类,使每一项的内容更加丰富。但是经过分析,每一项仍具有固定的最简式,即为 。
2. 具体实现
本次作业仍以加减号为隔断依据,每判断到加号或减号时,新建Factor类处理这个项。类中有四个域coe,xpow,spow和cpow,分别对应项的系数,幂因子的指数,正弦函数的指数和余弦函数的指数,用以保存最简式的变量。在Factor处理的过程中,以乘号为隔断单位,判断常数因子等具体因子的格式问题。
同时单独建立专门的抽象类,用于实现具体操作。
(1) 输入处理
首先判断空串与非法字符,之后便以+-号为分隔,分割出一个一个项进行内容匹配,在每一项中以*为分隔,分割成一个一个因子具体匹配:
-
常数因子:“\s[-+]?\d+\s”
-
幂因子:“\sx\s(\^\s*[+-]?\d+)?”
-
正弦函数因子:“\ssin\s\(\sx\s\)\s(\^\s[+-]?\d+)?”
-
余弦函数因子:“\scos\s\(\sx\s\)\s(\^\s[+-]?\d+)?”
对于不同的因子改变不同的Factor域的值,直到遇到下一个不符合格式的字符为止。这样在结束时能够使该项成为最简形式。
(2) 求导处理
在Operation抽象类中,定义derX,derSin和derCos三种求导操作,可以对一个项的一个因子进行求导。同时对于每个项,若四个域中对应的值不为0,那么就调用相应的求导函数,得到一个新的Factor,将其加入求导后的新多项式中。这里便利用了面向对象的思想,只对这一个对象进行操作,如果是面向过程的,这一部分将很复杂。
(3) 优化处理
由输入和求导步骤,新的求导后的多项式为:每一项为最简式但是可能存在仅有系数不同的项。以xpow,spow和cpow三个域为键值,合并同类项。
之后的化简还需要考虑三角函数合并的问题,使用四个域的Factor类而不是HashMap类型的好处便体现出来了:可以在不同项间随意匹配四个域的值。
化简方法的返回值为一个Map,若存在化简的更新内容,那么返回{True,化简后的多项式}。根据True键值对应的值是否存在来循环多次化简。
3. 结构分析
类名 | LOC | NCLOC |
---|---|---|
Expr | 253 | 248 |
Factor | 156 | 149 |
Operation | 27 | 27 |
PolyDer | 26 | 24 |
TrigonTrans | 156 | 149 |
代码行数比起第一次作业多了一倍,但是分类更加明确。
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
TrigonTrans.twice() | 5.0 | 6.0 | 8.0 |
TrigonTrans.transTri(Vector) | 9.0 | 9.0 | 10.0 |
TrigonTrans.squareS() | 4.0 | 5.0 | 6.0 |
TrigonTrans.square() | 4.0 | 6.0 | 7.0 |
TrigonTrans.pd(Factor,Factor) | 6.0 | 1.0 | 6.0 |
TrigonTrans.fourth() | 5.0 | 6.0 | 8.0 |
PolyDer.main(String[]) | 1.0 | 3.0 | 3.0 |
Operation.derX() | 1.0 | 1.0 | 1.0 |
Operation.derSin() | 1.0 | 1.0 | 1.0 |
Operation.derCos() | 1.0 | 1.0 | 1.0 |
Factor.startWith(String) | 1.0 | 2.0 | 3.0 |
Factor.showFactor() | 1.0 | 1.0 | 1.0 |
Factor.setCeo(BigInteger) | 1.0 | 1.0 | 1.0 |
Factor.getXpow() | 1.0 | 1.0 | 1.0 |
Factor.getSpow() | 1.0 | 1.0 | 1.0 |
Factor.getCpow() | 1.0 | 1.0 | 1.0 |
Factor.getCoe() | 1.0 | 1.0 | 1.0 |
Factor.findNum(String) | 1.0 | 2.0 | 2.0 |
Factor.Factor() | 1.0 | 1.0 | 1.0 |
Factor.dealItem(String) | 1.0 | 8.0 | 9.0 |
Factor.afterFactor(String,int) | 1.0 | 5.0 | 5.0 |
Factor.addSpow(BigInteger) | 1.0 | 1.0 | 1.0 |
Factor.addCpow(BigInteger) | 1.0 | 1.0 | 1.0 |
Factor.addCeo(BigInteger) | 1.0 | 1.0 | 1.0 |
Expr.zeroNum() | 1.0 | 1.0 | 4.0 |
Expr.toString() | 1.0 | 8.0 | 12.0 |
Expr.showExpr() | 1.0 | 2.0 | 2.0 |
Expr.normalStr(String) | 1.0 | 7.0 | 9.0 |
Expr.moveZheng() | 3.0 | 3.0 | 4.0 |
Expr.mergeExpr() | 1.0 | 4.0 | 4.0 |
Expr.exprDer() | 3.0 | 6.0 | 7.0 |
Expr.Expr(String) | 1.0 | 5.0 | 7.0 |
Expr.Expr() | 1.0 | 1.0 | 1.0 |
Expr.canMerge(Factor,Vector) | 3.0 | 4.0 | 5.0 |
从表格中可以看出本次作业完全经过了重构,各个方法的复杂度均有所下降,较具有可扩展性,其实也算是为下一次嵌套的作业做准备。
本次作业的优点在于为下次任务搭好框架,具有较强的可扩展性;缺点在于代码冗余,重复造轮子还不知道应该怎么合并。而且我也不知道为什么我的项要取名为因子,感觉容易造成混乱。
4. bug们
本次作业中出现了因为马虎粗心导致的bug,原因是忽略了正则表达式的中括号中-号位于中间是意味着从前面那个字符到后面那个字符的意思,并且在调试中没有测出来。
在测试中侧重于对于WF的测试,而忽略了对正确格式的测试。这也是因为懒导致的顾此失彼,所以我们的强测也是侧重于保证正确而减少了对错误格式的测试点,这一点是比较合理的,死的明明白白。
同时因为重复造轮子,我的程序在修改bug的过程中也可能会出现漏改的情况,在第三次作业的测试里也加强了对于同类型错误不同位置的多次测试。
(三)第三次作业
1. 分析思路
第三次作业相对于第二次作业增加了嵌套表达式和嵌套因子,这样的嵌套让我想起来了编译原理,因此本次作业的思路就是递归下降子程序法 。在第二次作业的基础上,增加对因子的类型的细分,在每一个因子中应该增加对可嵌套因素的域。
2. 具体实现
本次作业前半部分几乎没有变化,仍然是利用+-号来进行分割,*号对每一项进行分割,但是不同点在于每一个因子的处理过程中需要递归嵌套,对内部的进行进一步的处理。
总的来说,
每一个表达式的结构依然为:
每一项的结构变为:
也就是保留了每一项的系数和幂函数的指数,对于三角函数和表达式全都以列表的形式存储。
三角函数因子的结构为:
每一个三角函数因子都具有pow和type域的值,对应于不同的type,分别填充不同的子嵌套部分。同时Sin函数和Cos函数因子只有求导和输入处理部分不同,故只写了Sin函数因子,而使用继承来完成了Cos函数因子。
多项式因子类继承多项式类,只在其基础上修改了toString函数。
类图为:
(1) 输入处理
输入处理与第二次作业类似,只是运用编译语法分析的思想,遇到处理嵌套的部分时声明一个新的对应子类,递归处理。
(2) 求导处理
经过输入的处理之后,得到的是合并了同类项因子的项,这使我们对同一个函数的求导不需要进行多次。第二次作业中是在Operation中实现对不同因子类型的求导操作,但是对于嵌套n层的情况不适用,于是在本次作业中,对每一个因子实现求导功能。最后按照嵌套求导公式,依次输出因子或者因子的导。
(3) 优化处理
虽然重写了equal函数和toString函数等,对于高层次的优化很方便,同时完成基础功能后还有大把的时间去优化,但是为了避免不必要的错误,只实现了合并同类项。
3. 结构分析
类名 | LOC | NCLOC |
---|---|---|
Expr | 166 | 166 |
Factor | 377 | 369 |
Operation | 108 | 108 |
PolyDer | 31 | 30 |
TrigonTrans | 10 | 10 |
SinFactor | 287 | 285 |
CosFactor | 135 | 134 |
ExprFactor | 70 | 70 |
比起第二次作业,功能多了很多,但是代码行数增加的不算多。
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
polyder.TrigonTrans.transTri(Vector) | 1.0 | 1.0 | 1.0 |
polyder.SinFactor.typeX(String) | 1.0 | 3.0 | 3.0 |
polyder.SinFactor.typeNum(String) | 1.0 | 2.0 | 2.0 |
polyder.SinFactor.toString() | 3.0 | 8.0 | 10.0 |
polyder.SinFactor.SinFactor() | 1.0 | 1.0 | 1.0 |
polyder.SinFactor.setXpow(BigInteger) | 1.0 | 1.0 | 1.0 |
polyder.SinFactor.setType(int) | 1.0 | 1.0 | 1.0 |
polyder.SinFactor.setSin(SinFactor) | 1.0 | 1.0 | 1.0 |
polyder.SinFactor.setPow(BigInteger) | 1.0 | 1.0 | 1.0 |
polyder.SinFactor.setNum(BigInteger) | 1.0 | 1.0 | 1.0 |
polyder.SinFactor.setExpr(ExprFactor) | 1.0 | 1.0 | 1.0 |
polyder.SinFactor.setCos(CosFactor) | 1.0 | 1.0 | 1.0 |
polyder.SinFactor.less() | 7.0 | 11.0 | 11.0 |
polyder.SinFactor.getXpow() | 1.0 | 1.0 | 1.0 |
polyder.SinFactor.getType() | 1.0 | 1.0 | 1.0 |
polyder.SinFactor.getSin() | 1.0 | 1.0 | 1.0 |
polyder.SinFactor.getPow() | 1.0 | 1.0 | 1.0 |
polyder.SinFactor.getNum() | 1.0 | 1.0 | 1.0 |
polyder.SinFactor.getExpr() | 1.0 | 1.0 | 1.0 |
polyder.SinFactor.getCos() | 1.0 | 1.0 | 1.0 |
polyder.SinFactor.findPower(Matcher,String) | 1.0 | 2.0 | 2.0 |
polyder.SinFactor.equals(SinFactor) | 7.0 | 6.0 | 7.0 |
polyder.SinFactor.derSinFactor() | 2.0 | 3.0 | 3.0 |
polyder.SinFactor.afterSin(String) | 1.0 | 9.0 | 9.0 |
polyder.SinFactor.addPow(BigInteger) | 1.0 | 1.0 | 1.0 |
polyder.Operation.findNum(String) | 1.0 | 2.0 | 2.0 |
polyder.Operation.derX(Factor) | 1.0 | 1.0 | 1.0 |
polyder.Operation.dersce(Factor,int,int) | 1.0 | 10.0 | 10.0 |
polyder.Operation.derFactor(Factor) | 2.0 | 7.0 | 8.0 |
polyder.Main.main(String[]) | 1.0 | 4.0 | 4.0 |
polyder.Factor.toString() | 2.0 | 5.0 | 6.0 |
polyder.Factor.stringX(int) | 1.0 | 3.0 | 4.0 |
polyder.Factor.startWith(String) | 1.0 | 2.0 | 3.0 |
polyder.Factor.sfsEq(Factor) | 3.0 | 4.0 | 6.0 |
polyder.Factor.setXpow(BigInteger) | 1.0 | 1.0 | 1.0 |
polyder.Factor.setSfactors(Vector) | 1.0 | 1.0 | 1.0 |
polyder.Factor.setEfactors(Vector) | 1.0 | 1.0 | 1.0 |
polyder.Factor.setCoe(BigInteger) | 1.0 | 1.0 | 1.0 |
polyder.Factor.setCfactors(Vector) | 1.0 | 1.0 | 1.0 |
polyder.Factor.notX() | 1.0 | 6.0 | 6.0 |
polyder.Factor.mergeFactor() | 7.0 | 7.0 | 9.0 |
polyder.Factor.getXpow() | 1.0 | 1.0 | 1.0 |
polyder.Factor.getSfactors() | 1.0 | 1.0 | 1.0 |
polyder.Factor.getEfactors() | 1.0 | 1.0 | 1.0 |
polyder.Factor.getCoe() | 1.0 | 1.0 | 1.0 |
polyder.Factor.getCfactors() | 1.0 | 1.0 | 1.0 |
polyder.Factor.Factor() | 1.0 | 1.0 | 1.0 |
polyder.Factor.everyItem(String) | 1.0 | 5.0 | 5.0 |
polyder.Factor.equals(Factor) | 5.0 | 3.0 | 7.0 |
polyder.Factor.efsEq(Factor) | 3.0 | 3.0 | 5.0 |
polyder.Factor.dealXItem(String) | 1.0 | 4.0 | 4.0 |
polyder.Factor.dealSinItem(String) | 3.0 | 4.0 | 4.0 |
polyder.Factor.dealNumItem(String) | 1.0 | 2.0 | 2.0 |
polyder.Factor.dealExprItem(String) | 1.0 | 2.0 | 2.0 |
polyder.Factor.dealCosItem(String) | 3.0 | 4.0 | 4.0 |
polyder.Factor.cfsEq(Factor) | 3.0 | 4.0 | 6.0 |
polyder.Factor.afterFactor(String,int) | 1.0 | 3.0 | 3.0 |
polyder.Factor.addCeo(BigInteger) | 1.0 | 1.0 | 1.0 |
polyder.ExprFactor.toString() | 1.0 | 1.0 | 1.0 |
polyder.ExprFactor.equals(Expr) | 1.0 | 1.0 | 1.0 |
polyder.ExprFactor.derExprFactor() | 3.0 | 3.0 | 3.0 |
polyder.ExprFactor.afterExpr(String) | 1.0 | 3.0 | 5.0 |
polyder.Expr.toString() | 1.0 | 4.0 | 6.0 |
polyder.Expr.setFactors(Vector) | 1.0 | 1.0 | 1.0 |
polyder.Expr.moveZheng() | 3.0 | 3.0 | 4.0 |
polyder.Expr.mergeExpr() | 1.0 | 4.0 | 4.0 |
polyder.Expr.getFactors() | 1.0 | 1.0 | 1.0 |
polyder.Expr.exprDer() | 1.0 | 3.0 | 3.0 |
polyder.Expr.Expr(String) | 1.0 | 5.0 | 7.0 |
polyder.Expr.Expr() | 1.0 | 1.0 | 1.0 |
polyder.Expr.equals(Expr) | 3.0 | 3.0 | 5.0 |
polyder.Expr.canMerge(Factor,Vector) | 3.0 | 2.0 | 3.0 |
polyder.CosFactor.toString() | 3.0 | 8.0 | 10.0 |
polyder.CosFactor.equals(SinFactor) | 7.0 | 6.0 | 7.0 |
polyder.CosFactor.derCosFactor() | 2.0 | 3.0 | 3.0 |
polyder.CosFactor.CosFactor() | 1.0 | 1.0 | 1.0 |
polyder.CosFactor.afterCos(String) | 1.0 | 9.0 | 9.0 |
Total | 131.0 | 220.0 | 252.0 |
Average | 1.7012987012987013 | 2.857142857142857 | 3.272727272727273 |
从表格中可以看出本次作业平均复杂度减少很多。
本次作业的优点在于重写了很多方法如equal和toString,新增的方法很多都是get和set方法,这些给程序扩展功能提供了很大的便利;代码冗余的缺点依然存在,同时一个类的功能应该只和这个类有关,一个方法应该只实现一个功能,这些做的不好,很多方法很长,实现了很多功能。其中最明显的是对类的划分有的地方太细,有的地方就没有划分,比如幂函数因子并没有单独成类,这导致了“按照每一小模块处理”的出发点被改变,让Factor类中出现了许多不必要的方法和重复代码。
4. 程序测试
经过了第二次作业测试过少的教训,本次作业增大了测试的强度,最后还利用脚本生成测试数据进行随机测试。
测试是随着程序的一步步功能进行的,比如:
-
先测试读取,能否检测出输入内容的格式是否正确;这一部分中主要就是对于非法字符、空格个数位置、+-号的个数位置进行判断;
-
进而测试读取后经过化简的式子是否与原式等价;
-
完成求导后,使用第一二次的测试数据进行测试,并与前两次作业程序的结果进行对比,检测是否对于简单多项式能够实现正确求导;
-
再对三角函数因子嵌套进行测试,测试其在不同位置时的求导结果;
-
对嵌套多项式因子进行测试
感觉一般出的bug都是不细心引起的。
二、Applying Creational Pattern
第一次作业其实是按面向过程的设计方法设计的,第二次作业中学到了面向对象的设计思路以及层次化思想,第三次作业中使用了继承。
若要重构,可以从每一个因子入手,让它们都为Factor(当然不是我那读作项写作Factor的Factor)接口,该接口中有合并、求导等方法,然后再在每一个具体因子中实现。