------------恢复内容开始------------
第一单元作业总结
基于度量来分析程序结构
第一次作业
-
本次作业,需要完成的任务为简单多项式导函数的求解,无WF判定。
-
本次作业中的类以及解释:
- Term(幂函数)
- Polynomial(加减法连接的多项式)
- Main
-
UML图
-
任务分析:
-
这次作业中不同的求导规则共有三个:
- Term类中对基本幂函数形式的x的求导
- Polynomial类中对以加减法连接的多项式的求导
-
需要性能优化的部分:
- 在Polynomial类中将Term类进行加法合并,合并之后指数不变,系数相加。
- 在输出部分中系数为1或-1导致的省略
- 在输出部分中存在0的省略
-
-
实际架构:
-
构造了Term类,其具有以下属性与方法:
private BigInteger coefficient; private BigInteger exponent; private BigInteger deCoefficient; private BigInteger deExponent; void addCoefficient(BigInteger coefficient) void derivative() String getDerivativedTerm()
基本思路是通过coefficient与exponent计算得出deCoefficient与deExponent,getDerivativedTerm()方法通过deCoefficient与deExponent返回求导得到的字符串。
-
构造了Polynomial类,其具有以下属性与方法:
private HashMap
termMap; private List sortedList; void updateTerm(BigInteger coefficient, BigInteger exponent) void turnSortedList() void derivativeAll() { String getPolynomial() 首先是在termMap中以指数为引索,创建一个Term类的HahMap,updateTerm()方法中传入系数与指数,判断指数对应引索在termMap中是否为空,若已经存在则进行加法合并,若不存在则创建一个新的Term类对象并加入termMap。getPolynomial()通过调用Term类的getDerivativedTerm()方法返回求导之后的字符串。
-
-
复杂度分析:由于第一次作业的架构比较简单,不予复杂度分析
-
Bug与反思:
- 对象之间没有联系,面向对象的思想不够明确。
- 在大体的结构上,由于求导是采用对现有表达式树内的属性进行赋值而并没有创建新的对象,导致在返回求导字符串的时候比较混乱(不知道是调用求导前还是求导后的参数)。
- Input的处理堆积在Main函数中,导致Main函数臃肿。
- 对求导的思路有一些偏差,直接返回String的思路十分错误。
- 在性能优化时,对-1的优化有误,导致互测被爆出Bug。
第二次作业
-
本次作业,需要完成的任务为包含简单幂函数和简单正余弦函数的导函数的求解,有WF判定,但没有因为空白字符导致的WF判定。
-
本次作业中的类以及解释:
- Everything(多项式顶层类)
- Polynomial(加减法连接的多项式)
- Monomial(乘法连接的单项式)
- Single(没有加减法以及乘除法的独立项顶层类)
- Term(幂函数)
- Trigonometry(三角函数顶层类)
- Cos(Cos类)
- Sin(Sin类)
- SinglesTree(记录复合关系的类,在本次作业中暂未用到)
- ElementX(基元X,为复合函数做准备,在本次作业中暂未用到)
- Main
-
UML图
-
任务分析:
-
这次作业中不同的求导规则共有五个:
- Term类中对基本幂函数形式的x的求导
- Cos类中对余弦函数的求导(新增)
-
Sin类中对正弦函数的求导(新增)
- Monomial类中对以乘法连接的单项式的求导(新增)
- Polynomial类中对以加减法连接的多项式的求导
-
新增了WF判定
-
需要性能优化的部分:
-
需要完全具备作业一中对Term类的优化
-
在Monomial类中将Term类进行乘法合并,合并之后指数相加,系数相乘。
-
在Polynomial类中合并同类项
-
三角函数的优化
-
-
-
实际架构:
-
抽象出了Spring接口
public EveryThing getDerivativedObject() throws CloneNotSupportedException; public boolean isZero(); public boolean equals(EveryThing everyThing); public String getString();
主要规定了项需要具有求导方法,返回字符串方法,比较是否相等(进行同类项合并)。
-
Term类在作业一的基础上进行了重构,主要是将获得字符串方法与求导方法分开。
public EveryThing getDerivativedObject() { return new Term(this.deCoefficient,this.deExponent); }
现在求导返回的是一个(Term)对象,而getString()方法返回的是未求导时的字符串
-
构造了Cos与Sin类,求导方法与Term类似。Cos对象求导返回一个新的Sin对象,Sin对象求导返回一个新的Cos对象。
-
构造了Monomial类,其具有以下属性:
private ArrayList
monomial; private BigInteger coefficient; 通过数学知识,Monomial的求导方法如下:
public Polynomial getDerivativedObject() throws CloneNotSupportedException { Polynomial polynomial = new Polynomial(); int length = this.monomial.size(); for (int i = 0;i < length;i++) { Monomial monomial = this.clone(); monomial.remove(i); Monomial deMonomial = this.monomial.get(i).getDerivativedObject(); if (deMonomial != null) { monomial.addAll(deMonomial); polynomial.add(monomial); } } return polynomial; }
即依次对Monomial对象列表中包含的所有的类进行求导,假设列表中有n个对象(即该单项式可以表示为n个项相乘),则求导后返回一个具有n个Monomial对象的Polynomial对象。第i个Monomial对象中包含原Monomial对象列表中第i个元素的求导返回对象以及其余n-1个元素的clone()
![Monomial求导图示表](F:\zhang_hk\Desktop\批注 2020-03-21 174219.jpg)
-
Polynomial类与Term类做了类似改动
-
对Polynomial与Monomial类给予了Simplify()方法,可以进行主动简化。
-
-
复杂度分析:(本次仅给出关键数据,完整数据见第三次作业)
-
Method Metrics
ev(G) iv(G) v(G) Total 134.0 164.0 196.0 Average 1.47 1.80 2.15 其中比较复杂度较高的方法如下:
method ev(G) iv(G) v(G) Main.inputPrepare(String) 1.0 11.0 11.0 Monomial.annexable(Monomial) 4.0 2.0 4.0 Monomial.getString() 4.0 11.0 11.0 SinglesTree.annexable(SinglesTree) 7.0 3.0 7.0 SinglesTree.singleCompare(EveryThing,EveryThing) 13.0 1.0 13.0 以SinglesTree.singleCompare(EveryThing,EveryThing)这个方法为例,其代码如下:
public int singleCompare(EveryThing everyThing1,EveryThing everyThing2) { if (everyThing1 instanceof Term) { if (everyThing2 instanceof Term) { return 0; } else { return -1; } } else if (everyThing1 instanceof Sin) { if (everyThing2 instanceof Term) { return 1; } else if (everyThing2 instanceof Sin) { return 0; } else { return -1; } } else if (everyThing1 instanceof Cos) { if (everyThing2 instanceof Term | everyThing2 instanceof Sin) { return 1; } else if (everyThing2 instanceof Cos) { return 0; } else { return -1; } } else if (everyThing1 instanceof Monomial) { if (everyThing2 instanceof Term | everyThing2 instanceof Sin | everyThing2 instanceof Cos) { return 1; } else if (everyThing2 instanceof Monomial) { return 0; } else { return -1; } } else { if (everyThing2 instanceof Polynomial) { return 0; } else { return 1; } } }
可以发现其非结构化问题严重,圈内复杂度相当高,不便于阅读与调试。
-
Class Metrics
Class CBO OCavg WMC Term 4 1.9 19 Sin 5 1.14 8 Cos 5 1.14 8 ElementX 2 1 3 Single 5 1 2 Monomial 4 2.44 39 Polynomial 4 1.62 21 EveryThing 9 1 5 SinglesTree 9 2.9 58 Main 6 4.12 33 可以看到各个类的耦合度比较低,主要是类内(以Monomial和SinglesTree为首的)方法圈内复杂度较高。
-
-
Bug与反思:
- 对象之间有了基本的联系,并且为第三次作业的函数嵌套做了准备
- 完全重构了作业一的思想,求导之后生成了一个全新的表达式树
- Input的处理依然堆积在Main函数中,导致Main函数臃肿
- 合并同类项的思路出现问题,当时没有准确的分清加法合并和乘法合并,导致合并关系紊乱
- 由于singleCompare()的方法出现Bug,优化并未起效,导致性能分较低。
- 对指数超过10000的WF的判定采用了错误的正则表达式,导致互测时超出5位的前导0会错误的给出WF。
- 出现了clone()浅拷贝的Bug,对对象与对象的引用有了更深的了解。
第三次作业
-
本次作业,需要完成的任务为包含简单幂函数和简单正余弦函数的导函数及其组合的求解,有全部WF判定。
-
本次作业中的类以及解释:
- Term(替代作业二中的Everything类,幂函数由单词PowerTerm替代,且变为顶层抽象类)
- PolynomialTerm(加减法连接的多项式)
- MonomialTerm(乘法连接的单项式)
- SingleTerm(没有加减法以及乘除法的独立项顶层抽象类)
- Trigonometry(三角函数顶层抽象类)
- Cos(Cos类)
- Sin(Sin类)
- Element(基元X)
- Input(接受输入与输入初始化的,部分由于格式问题引起的WF判定)
- ExpressionFactory(工厂类)
- Main
-
UML图
任务分析:
-
这次作业中不同的求导规则共有六个:
- Term类中对基本幂函数形式的x的求导
- Cos类中对余弦函数的求导
- Sin类中对正弦函数的求导
- Monomial类中对以乘法连接的单项式的求导
- Polynomial类中对以加减法连接的多项式的求导
- 在以上基础上进行嵌套的求导(新增)
-
更改了WF判定
-
需要性能优化的部分:
- 与作业二的优化基本相同。
-
实际架构:
-
抽象出了Annexable(可合并的)接口
public boolean additionalAnnexable(Term term); //判断是否是加法可合并的 public void additionalCombine(Term term); //进行加法合并 public boolean multiplicativeAnnexable(Term term); //判断是否是乘法可合并的 public void multiplicativeCombine(Term term); //进行乘法合并
-
抽象出了Derivable(可求导的)接口
public Term derivative(); public boolean isZero(); public boolean equals(Term term); public String getString();
-
增加了工厂类
public class ExpressionFactory { public static void wrong() { System.out.println("WRONG FORMAT!"); System.exit(0); } public static void factorCheck(String string) public static PolynomialTerm createPolynomialTerm(String string) public static MonomialTerm createMonomialTerm(String string,boolean positive) public static SingleTerm createSingleTerm(String string) public static PowerTerm createPowerTerm(String string) public static PowerTerm createTrigonometry(String string)
-
对合并操作进行了优化,清楚的分出加法合并和乘法合并。
-
将Element类投入实用,其是一个不变类,任何方法不会进行任何改动直接返回
-
在SingleTerm的子类(即PowerTerm,Sin,Cos,Element)中增加了一个属性
private Term innerReference;//Sin,Cos,PowerTerm中初始值为Element类的对象。
而对他们的求导方式根据复合函数的求导法则做出了相应的改变,以PowerTerm类为例:
public Term derivative() { PowerTerm powerTerm = new PowerTerm(this.deCoefficient,this.deExponent); powerTerm.setInnerReference(this.innerReference); if (this.innerReference instanceof Element) { return powerTerm; } else { MonomialTerm monomialTerm = new MonomialTerm(); monomialTerm.add(powerTerm); monomialTerm.add(this.innerReference.derivative()); return monomialTerm; } }
可以看见采用的是递归的方式进行求导,而Element类的元素的主要作用是中断递归
同时对他们的getString()方法做出了相应的改变,以Cos类为例
@Override public String getString() { String ret = ""; if (this.isZero()) { return ""; } else if (this.coefficient.compareTo(BigInteger.valueOf(-1)) == 0) { ret = "-"; } else if (this.coefficient.compareTo(BigInteger.ONE) != 0) { ret += String.valueOf(this.coefficient); ret += "*"; } ret += "cos(x)"; ret = ret.replace("x",this.innerReference.getString()); return ret; }
主要核心思想是先给出该类的基本表达形式,再将其中的"x"替换为其innerReference的getString()返回值,与正常的数学计算逻辑相同。
-
在优化上,取消了作业二中的simplify()函数,将其分散到PolynomialTerm类与MonomialTerm类的add()方法中,即将全部添加完毕之后集体合并的方式改为添加一个便进行合并。
-
将作业二中的SinglesTree.singleCompare(EveryThing,EveryThing)方法拆散插入每个实体类中的equals()方法中,极大的降低了圈复杂度
-
全部重新手写了所有类的clone()方法,确认进行深拷贝,避免因为浅拷贝问题产生Bug
-
将作业二中的对输入部分的处理提取出形成Input类,最后Main()函数只有不到十行。
-
-
复杂度分析:(完整版)
-
Method Metrics
Method ev(G) iv(G) v(G) Cos.Cos(BigInteger) 1 1 1 Cos.additionalAnnexable(Term) 2 2 2 Cos.additionalCombine(Term) 1 1 1 Cos.clone() 1 1 1 Cos.derivative() 2 2 2 Cos.equals(Term) 2 2 2 Cos.getCoefficient() 1 1 1 Cos.getInnerReference() 1 1 1 Cos.getString() 2 4 4 Cos.isZero() 1 1 1 Cos.multiplicativeAnnexable(Term) 1 1 1 Cos.multiplicativeCombine(Term) 1 1 1 Cos.setCoefficient(BigInteger) 1 1 1 Cos.setInnerReference(Term) 1 1 1 Element.additionalAnnexable(Term) 1 1 1 Element.additionalCombine(Term) 1 1 1 Element.clone() 1 1 1 Element.derivative() 1 1 1 Element.equals(Term) 1 1 1 Element.getCoefficient() 1 1 1 Element.getString() 1 1 1 Element.isZero() 1 1 1 Element.multiplicativeAnnexable(Term) 1 1 1 Element.multiplicativeCombine(Term) 1 1 1 Element.setCoefficient(BigInteger) 1 1 1 Element.setInnerReference(Term) 1 1 1 ExpressionFactory.createMonomialTerm(String,boolean) 3 7 11 ExpressionFactory.createPolynomialTerm(String) 3 5 10 ExpressionFactory.createPowerTerm(String) 1 5 5 ExpressionFactory.createSingleTerm(String) 2 2 2 ExpressionFactory.createTrigonometry(String) 1 7 8 ExpressionFactory.factorCheck(String) 2 3 4 ExpressionFactory.wrong() 1 1 1 Input.Input() 1 1 1 Input.addCharacter(char,StringBuilder,boolean) 2 4 6 Input.blankCheck() 1 8 8 Input.bracketsFill(String) 6 7 13 Input.bracketsSimplify(String,boolean) 2 7 9 Input.exponentCheck() 1 2 2 Input.factorCheck(String) 2 5 8 Input.getString() 1 1 1 Input.signCheck() 1 7 7 Input.totalCheck() 1 1 1 Input.wrong() 1 1 1 Main.main(String[]) 1 1 1 MonomialTerm.MonomialTerm() 1 1 1 MonomialTerm.add(Term) 3 3 3 MonomialTerm.addAll(ArrayList ) 1 2 2 MonomialTerm.additionalAnnexable(Term) 5 3 5 MonomialTerm.additionalCombine(Term) 1 1 1 MonomialTerm.clone() 1 1 1 MonomialTerm.derivative() 1 3 3 MonomialTerm.equals(Term) 6 3 6 MonomialTerm.get(int) 1 1 1 MonomialTerm.getCoefficient() 1 1 1 MonomialTerm.equals(Term) 5 8 8 MonomialTerm.getTermList() 1 1 1 MonomialTerm.isZero() 1 1 1 MonomialTerm.multiplicativeAnnexable(Term) 1 1 1 MonomialTerm.multiplicativeCombine(Term) 1 1 1 MonomialTerm.remove(int) 1 1 1 MonomialTerm.setCoefficient(BigInteger) 1 1 1 MonomialTerm.size() 1 1 1 PolynomialTerm.PolynomialTerm() 1 1 1 PolynomialTerm.add(Term) 3 3 3 PolynomialTerm.addAll(ArrayList ) 1 2 2 PolynomialTerm.additionalAnnexable(Term) 1 1 1 PolynomialTerm.additionalCombine(Term) 1 1 1 PolynomialTerm.clone() 1 1 1 PolynomialTerm.derivative() 1 2 2 PolynomialTerm.equals(Term) 6 3 6 PolynomialTerm.get(int) 1 1 1 PolynomialTerm.getCoefficient() 1 1 1 PolynomialTerm.getString() 5 5 8 PolynomialTerm.getTermList() 1 1 1 PolynomialTerm.isZero() 1 1 1 PolynomialTerm.multiplicativeAnnexable(Term) 1 1 1 PolynomialTerm.multiplicativeCombine(Term) 1 1 1 PolynomialTerm.remove(int) 1 1 1 PolynomialTerm.setCoefficient(BigInteger) 1 1 1 PolynomialTerm.size() 1 1 1 PowerTerm.PowerTerm(BigInteger,BigInteger) 1 3 3 PowerTerm.additionalAnnexable(Term) 2 2 2 PowerTerm.additionalCombine(Term) 1 1 1 PowerTerm.clone() 1 1 1 PowerTerm.derivative() 2 2 2 PowerTerm.equals(Term) 2 2 2 PowerTerm.getCoefficient() 1 1 1 PowerTerm.getExponent() 1 1 1 PowerTerm.getInnerReference() 1 1 1 PowerTerm.getString() 2 5 7 PowerTerm.isZero() 1 1 1 PowerTerm.multiplicativeAnnexable(Term) 2 2 2 PowerTerm.multiplicativeCombine(Term) 1 1 1 PowerTerm.setCoefficient(BigInteger) 1 1 1 PowerTerm.setExponent(BigInteger) 1 1 1 PowerTerm.setInnerReference(Term) 1 1 1 Sin.Sin(BigInteger) 1 1 1 Sin.additionalAnnexable(Term) 2 2 2 Sin.additionalCombine(Term) 1 1 1 Sin.clone() 1 1 1 Sin.derivative() 2 2 2 Sin.equals(Term) 2 2 2 Sin.getCoefficient() 1 1 1 Sin.getInnerReference() 1 1 1 Sin.getString() 2 4 4 Sin.isZero() 1 1 1 Sin.multiplicativeAnnexable(Term) 1 1 1 Sin.multiplicativeCombine(Term) 1 1 1 Sin.setCoefficient(BigInteger) 1 1 1 Sin.setInnerReference(Term) 1 1 1 Total 164.0 215.0 252.0 Average 1.48 1.94 2.27 可以发现复杂度比较高的方法主要是equals以及其他的以String为参数的或者返回值为String的方法。
- equals()方法由于其参数为最顶层的Term类,即任意两个实体类均可比,所以这个方法外部调用起来比较简单,但内部实现就会变得复杂,属于牺牲自己的办法。
- 对于参数为字符串的方法,同上。比如工厂类中的的各种create方法,为了减轻Input类的的压力所以将一些字符串处理的方法放在了工厂类中。
- 对于返回值为String类的方法,为了优化考虑,不得不增加一些语句尽可能的节约输出的字符
-
Class Metrics
Class CBO OCavg WMC Element 5 1 12 Sin 6 1.43 20 Cos 6 1.43 20 PowerTerm 5 1.75 28 MonomialTerm 6 2.44 44 PolynomialTerm 4 2.11 38 ExpressionFactory 8 5.86 41 Input 1 5.18 57 Main 3 1 1 Average 2.27 21.0 5.17 可以看到大部分的复杂性都集中在Input类与ExpressionFactory类中,即从String类的输入转化为表达式树的部分复杂度很高。
-
-
Bug与反思:
- 在表达式树的逻辑关系上经过深思熟虑达到了一个比较清楚的水平,在求导上没有大问题。
- 优化方面也做得比较好,在强测中通过的点性能分均满分。
- 主要问题出现在了输入处理的部分
- 首先是对括号的处理:没有考虑到括号不匹配的情况;思考不全面,优化多余括号导致了一些Bug;在判断三角函数中嵌套的是否为因子的部分出现了很大的问题,将判断是否为因子的部分交给了工厂类处理。而工厂类接受的字符串已经是经过了Input类预处理了的,所以缺失了一些情况下的WF判定,这个问题导致强测一个点失分。
- 在输入处理的部分,对Input类与工厂类的思考不够深入,导致这两个类之间关系混乱。我浅显的将Input类定义成了”一次性使用“的,所以我是将字符串全部进行了预处理之后才交给工厂类进行表达式树的建立。而由于嵌套的存在,在工厂类中我又不得不重新对字符串进行处理判断是否为嵌套函数,对字符串的处理没有集中的在一个类中,导致调试存在一些困难,并且不够“面向对象”。工厂类“以为”Input类已经做好了所有字符串的预处理,而Input类"以为"工厂类还会对字符串做一些判断,导致了许多Bug.
- 在指数超出范围的判断上出现了一些小bug,我是将判断指数超出范围的部分卸载了PowerTerm的初始化函数中,这导致了只要有类试图创建一个指数大于50的PowerTerm类,就会WF报错。而事实上,由于优化的存在,指数可以相加,而最后clone()的时候完全可以创建出大于50的PowerTerm类。其实指数超出范围的判断应该为只要工厂试图创建一个指数大于50的PowerTerm类,就会WF报错。所以将判断转移到了工厂内,解决了这个Bug。
分析自己发现别人bug所采用的策略
- 用python写了一个简单的对拍机,可以一次输入获得八个输入,简便手动对拍的操作。
- 前两次简单的数据采用了自己写的数据生成器,但效果其实一般,不如手动极限数据来的快,而且浪费硬件资源,一般来说过了强测的同学不会有常规数据的Bug。(其实还是没有一个比较好的生成数据的策略的锅,毕竟”枚举“是无穷无尽的)
- 第三次采用了纯手动的测试方法(其实是写不出来由递归的数据生成器),好处在于发现Bug的速度比较快,经过几个不同方向的极端数据的测试后找到了互测屋十多个Bug,缺点在于手动测试耗费心力,上限比较低。
对比和心得体会
- 前两次作业中的命名水平低下,导致自己引用的时候都不知道这个方法是干什么的,增加写代码负担。但在第三次作业中已经有了比较规范的命名方式,有了一些进步,还需继续努力。
- 类的继承逻辑比较清楚,但是还不会合理应用抽象类,以及许多类似的类中写了基本相同的方法,其实没有必要。今后会加强对父类子类继承的学习,减少代码量。优秀代码中代码结构都很清晰,值得学习。
- 在处理输入部分的“面向对象”思路上仍有许多问题,对于输入部分的处理还欠妥,今后会注意这些问题。