OO第一单元博客
一、第一次作业
题目描述
本次作业需要完成的任务为:读入一个包含加、减、乘、乘方以及括号(其中括号的深度至多为 1 层)的多变量表达式,输出恒等变形展开所有括号后的表达式。
架构分析和实现
第一次作业要求比较简单,但由于缓考和各种其他事情的耽搁,以及对OO课程的难度认知不足,留给我写OO的时间十分有限,加之我对于java没有基础,于是便在极其有限的时间内通过对java的学习写出了一个线性算法,并未采用递归下降,惊险过关。
存储方法:HashMap<String, BigInteger>
String用于存储分别含有多少个xyz因子,BigInteger存储该项的系数。
我采用的方法为,建立三个map分别为Expr、Term、Factor,Term用于存储每一项的内容,并在该项结束之后把内容合并到Expr中,同时清空Term。Factor用于存储每一个因子的内容,并在该因子结束之后把内容合并到Term中,同时清空Factor。由于没有嵌套括号,如果Factor是表达式的话仍然采用暴力计算的方法,而不是递归调用Expr的内容。
bug分析
有限时间内debug过程较为痛苦,中测出现的一个bug原因为未使用 BigInteger,另一个bug原因是没有考虑指数前带有正号的情况。强测并未出现bug,互测中出现一个bug,原因为对于所有指数为0的因子,都暴力赋值为1,没有考虑之前因子出现过0的情况,如0*y**0我错误将其输出为1。
总结
当时的我对于对象的理解并不清晰,并未对三个map建立属于他们的类,也并未采用递归下降算法,实际还是面向过程编程,只求在有限时间内通过hw1,这导致我的代码连最基本的嵌套括号的功能都无法实现,为之后hw2的重构埋下伏笔。
分析评估
复杂度分析:
方法复杂度:
ev(G) :基本复杂度,是用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解。因此,基本复杂度高意味着非结构化程度高,难以模块化和维护。实际上,消除了一个错误有时会引起其他的错误。
iv(G) :模块设计复杂度,是用来衡量模块判定结构,即模块和其他模块的调用关系。软件模块设计复杂度高意味模块耦合度高,这将导致模块难于隔离、维护和复用。模块设计复杂度是从模块流程图中移去那些不包含调用子模块的判定和循环结构后得出的圈复杂度,因此模块设计复杂度不能大于圈复杂度,通常是远小于圈复杂度。
v(G): 是用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数,即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护,经验表明,程序的可能错误和高的圈复杂度有着很大关系。
类复杂度:
OCavg = Average operation complexity(平均操作复杂度)
OCmax = Maximum operation complexity(最大操作复杂度)
WMC = Weighted method complexity(加权方法复杂度)
由于我第一次作业架构并非采用递归下降,并不具备太多分析的价值和意义,因此着重分析后两次作业。
二、第二次作业
题目描述
本次作业中需要完成的任务为:读入一系列自定义函数的定义以及一个包含幂函数、三角函数、自定义函数调用的表达式,输出恒等变形展开所有括号后的表达式。
架构分析和实现
经过了对java的进一步学习和对面向对象编程的进一步理解,以及对递归下降算法的学习,我对第一次作业的代码进行了重构,并采用递归下降算法对表达式进行解析。
式 = 项 + 项 + 项 + ....
项 = 因子 * 因子 * 因子 *...
因子 =
{
sin(式)
cos(式)
f(式, 式, 式)
数字
x,y,z
式
}
存储方法
式 class Expr:Map<Term, BigInteger> //BigIntger为系数
项 class Term:
{
Map<Sin, Integer> TermSin //Integer为次数
Map<Cos, Integer> TermCos //Integer为次数
String TermStr
BigInteger TermNum //num为系数
}
因子 Factor:
{
FacSin = Expr
FacCos = Expr
FacNum
x,y,z
Expr
}
UML图
![](https://img-blog.csdnimg.cn/img_convert/d495576aaac8236c77926724e1b74899.png)
三角函数处理
新建两个类Sin和Cos,其内容代表sin和cos函数括号内的表达式,每当读到表达式中的sin或者cos,调用ParseExpr对括号内的表达式进行解析,并将解析结果返回至Sin或者Cos中。三角函数的化简形式比较多样,最显然的如sin(0)=0, sin^2+cos^2=1等等,但由于我debug时间过长,没有充足时间进行化简,于是全部采用暴力输出的形式。对于括号问题,我所有括号都暴力输出两个。
自定义函数处理
首先读入函数表达式时,将函数表达式里的x,y,z转化为$1,$2,$3,避免在替换过程中产生bug。在对表达式进行解析之前对表达式进行预处理,将其中的自定义函数全部转化为表达式,如f(Expr1, Expr2, Expr3),分别用Expr1, Expr2和Expr3替换函数表达式里的$1, $2, $3,并将替换后的字符串放到原来函数所在的位置。
合并方法
合并同类项的问题一开始我认为较为复杂,原因是两个sin()和cos()括号内部的式是否相同的判断,由于我采用的是树形结构,因此判断两个节点的所有分支是否相同是一件较为困难的事情。后经过一番研究发现通过重写equal和hashcode的方法,可以使我们定义的HashMap实现各种功能,判断两项是否相同可以直接采用map.containsKey()的方法来判断map1中是否含有map2的某个key,进而可以采用map1.put()的方法将该key合并到map1中。这样就顺利地解决了合并同类项的问题。
分析评估
规模分析
![](https://img-blog.csdnimg.cn/img_convert/e3a1bf4dc7ffbb72eee268ef9daa61f7.png)
就代码量而言可以看出Expr所占比重较多,这是由于我将很多merge操作和build操作都放在Expr中进行;其次是Print,由于我没有采用toString的方法转化为字符串输出,而是采用边递归边输出的方法,导致Print也占用了较多代码。
复杂度分析
![](https://img-blog.csdnimg.cn/img_convert/26ab62b4f12ec1e0bb4316a7597c6fd5.png)
其中可以看出Parse和Print的复杂度较高,因为二者更加偏向于面向过程编程,且递归调用过程的复杂度较高,因此二者产生了较高的复杂度。
method | CogC | ev(G) | iv(G) | v(G) |
Cos.Cos() | 0.0 | 1.0 | 1.0 | 1.0 |
Cos.equals(Object) | 4.0 | 4.0 | 1.0 | 4.0 |
Cos.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
Cos.MulCos(Map, Map) | 8.0 | 1.0 | 5.0 | 5.0 |
Expr.BuildChar(char) | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.BuildCos() | 2.0 | 1.0 | 1.0 | 3.0 |
Expr.BuildExpr(Term) | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.BuildNum(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.BuildSin() | 2.0 | 1.0 | 1.0 | 3.0 |
Expr.equals(Object) | 4.0 | 4.0 | 1.0 | 4.0 |
Expr.Expr() | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.InsertExpr(Expr, Expr) | 4.0 | 1.0 | 3.0 | 3.0 |
Expr.InsertTerm(Expr, Term) | 2.0 | 1.0 | 2.0 | 2.0 |
Expr.MulExpr(Expr, Expr) | 5.0 | 3.0 | 3.0 | 5.0 |
Main.iint() | 33.0 | 10.0 | 11.0 | 15.0 |
Main.main(String[]) | 0.0 | 1.0 | 1.0 | 1.0 |
Parse.GetNum() | 8.0 | 1.0 | 1.0 | 10.0 |
Parse.ParseExpr() | 4.0 | 3.0 | 3.0 | 3.0 |
Parse.ParseFac() | 9.0 | 5.0 | 5.0 | 11.0 |
Parse.ParsePower(Expr) | 4.0 | 3.0 | 2.0 | 7.0 |
Parse.ParseTerm() | 13.0 | 4.0 | 3.0 | 10.0 |
Parse.sort(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Print.PrintExpr(Expr) | 43.0 | 3.0 | 12.0 | 15.0 |
Print.PrintTerm(Term, boolean) | 60.0 | 1.0 | 28.0 | 28.0 |
Sin.equals(Object) | 4.0 | 4.0 | 1.0 | 4.0 |
Sin.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
Sin.MulSin(Map, Map) | 8.0 | 1.0 | 5.0 | 5.0 |
Sin.Sin() | 0.0 | 1.0 | 1.0 | 1.0 |
Term.equals(Object) | 5.0 | 4.0 | 3.0 | 6.0 |
Term.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
Term.IsEmpty(Term) | 3.0 | 4.0 | 1.0 | 4.0 |
Term.Term() | 0.0 | 1.0 | 1.0 | 1.0 |
Total | 225.0 | 72.0 | 105.0 | 160.0 |
Average | 6.818181818181818 | 2.1818181818181817 | 3.1818181818181817 | 4.848484848484849 |
从上表可以看出,方法复杂度主要集中在Main.iint()和Print.PrintExpr()和Print.PrintTerm()方法中;Main.iint()中我进行了对自定义函数的处理操作,将其中的自定义函数全部转化为表达式,如f(Expr1, Expr2, Expr3),分别用Expr1, Expr2和Expr3替换函数表达式里的$1, $2, $3,并将替换后的字符串放到原来函数所在的位置。而Print.PrintExpr()和Print.PrintTerm()方法中对输出内容的判断情况较多,因此复杂度较高。
Bug分析
有限时间内debug过程比较痛苦,其中中测出现的bug为,计算函数实参的时候,起始位置多加了一个1,比如f((1)),便会出现bug。强测和互测均未出现Bug。其中较容易出现bug的点可能为三角函数的括号问题,但由于我直接暴力输出两层括号,因此并未有bug。
总结
重构总结:hw2的重构是必然的,因此我在提交完hw1之后便开始思考重构策略,今早进行重构,以免雪球越滚越大,拖到hw3再进行重构已为时尚晚。同时重构并不代表完全重写,一部分基本内容还是可以是用之前的代码的。
本次作业我采用了递归下降算法进行重构,相较于编写代码的过程,我的思维过程时间更长,尤其是对于存储方式的思考和递归下降方法的理解。我对于对象这一概念的理解更加深入。同时重写equal和hashcode的方法来实现自定义map的各种功能让我体会到了java的便捷性。但由于本次作业进行了重构,思维时间过长导致debug时间不足,我并未对结果进行任何其他优化,只求通过正确性测试,这导致我的性能分不高。
三、第三次作业
题目描述
本次作业中需要完成的任务为:读入一系列自定义函数的定义以及一个包含幂函数、三角函数、自定义函数调用、求导算子的表达式,输出恒等变形展开所有括号后的表达式。
架构分析和实现
本次作业新增内容不多,同时有了第二次作业重构后的基础,我只进行了一小部分修改
UML类图
![](https://img-blog.csdnimg.cn/img_convert/e44ec42d40a01a7f4d9f42ad694ca853.png)
对求导的处理
首先解析要求导数的Expr,并对Expr中的每一项进行求导:
(1)、对幂函数的求导,先将该项的系数乘以幂函数的次数作为新的系数,同时将该幂函数的次数-1,其余因子的内容不变。
(2)、对三角函数的求导,首先将该三角函数当成幂函数进行求导,然后结果乘以该三角函数的导数(即sin变为cos,cos变为-sin,括号之中的内容不变),再乘以三角函数括号之中内容的导数,实现复合求导。
对自行义函数的处理
自定义函数中的导数的处理:
先对自定义函数表达式进行求导,并将原表达式的内容替换为求导后表达式的内容。
调用其他“已定义的”函数的处理:
用hw2中替换函数表达式的方法将自定义函数表达式中出现的其他自定义函数替换为表达式,得到一个不含其他已定义函数的自定义函数表达式。
分析评估
规模分析
![](https://img-blog.csdnimg.cn/img_convert/69089f4beeda5b666756012e9b85a3c1.png)
复杂度分析
![](https://img-blog.csdnimg.cn/img_convert/c28e6fc5dbc06b371f02e6aab5bb48f9.png)
hw3的类复杂度和hw2基本相同,主要复杂度还是集中在Main、Parse、和Print三个类中,具体原因和hw2基本相同。
method | CogC | ev(G) | iv(G) | v(G) |
Cos.BuildCos(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
Cos.Clone(Map) | 0.0 | 1.0 | 1.0 | 1.0 |
Cos.Cos() | 0.0 | 1.0 | 1.0 | 1.0 |
Cos.equals(Object) | 4.0 | 4.0 | 1.0 | 4.0 |
Cos.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
Cos.InsertCos(Term, Cos) | 2.0 | 1.0 | 2.0 | 2.0 |
Cos.MulCos(Map, Map) | 8.0 | 1.0 | 5.0 | 5.0 |
Der.DerChar(Term, BigInteger, char) | 4.0 | 2.0 | 2.0 | 5.0 |
Der.DerCos(Cos, char) | 0.0 | 1.0 | 1.0 | 1.0 |
Der.DerExpr(Expr, char) | 1.0 | 1.0 | 2.0 | 2.0 |
Der.DerSin(Sin, char) | 0.0 | 1.0 | 1.0 | 1.0 |
Der.DerTerm(Term, BigInteger, char) | 8.0 | 1.0 | 5.0 | 5.0 |
Expr.BuildChar(char) | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.BuildCos() | 2.0 | 1.0 | 1.0 | 3.0 |
Expr.BuildExpr(Term) | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.BuildNum(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.BuildSin() | 2.0 | 1.0 | 1.0 | 3.0 |
Expr.Clone(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.equals(Object) | 4.0 | 4.0 | 1.0 | 4.0 |
Expr.Expr() | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.InsertExpr(Expr, Expr) | 4.0 | 1.0 | 3.0 | 3.0 |
Expr.InsertTerm(Expr, Term) | 2.0 | 1.0 | 2.0 | 2.0 |
Expr.MulExpr(Expr, Expr) | 5.0 | 3.0 | 3.0 | 5.0 |
Main.BringFunc(String) | 26.0 | 10.0 | 7.0 | 11.0 |
Main.iint() | 7.0 | 1.0 | 5.0 | 5.0 |
Main.main(String[]) | 0.0 | 1.0 | 1.0 | 1.0 |
Parse.GetNum() | 8.0 | 1.0 | 1.0 | 10.0 |
Parse.ParseExpr() | 4.0 | 3.0 | 3.0 | 3.0 |
Parse.ParseFac() | 12.0 | 6.0 | 6.0 | 13.0 |
Parse.ParsePower(Expr) | 4.0 | 3.0 | 2.0 | 7.0 |
Parse.ParseTerm() | 13.0 | 4.0 | 3.0 | 10.0 |
Parse.sort(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Print.PrintExpr(Expr) | 43.0 | 3.0 | 6.0 | 15.0 |
Print.PrintTerm(Term, boolean) | 60.0 | 1.0 | 9.0 | 28.0 |
Sin.BuildSin(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
Sin.Clone(Map) | 0.0 | 1.0 | 1.0 | 1.0 |
Sin.equals(Object) | 4.0 | 4.0 | 1.0 | 4.0 |
Sin.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
Sin.InsertSin(Term, Sin) | 2.0 | 1.0 | 2.0 | 2.0 |
Sin.MulSin(Map, Map) | 8.0 | 1.0 | 5.0 | 5.0 |
Sin.Sin() | 0.0 | 1.0 | 1.0 | 1.0 |
Term.Clone(Term) | 0.0 | 1.0 | 1.0 | 1.0 |
Term.equals(Object) | 5.0 | 4.0 | 3.0 | 6.0 |
Term.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
Term.IsEmpty(Term) | 3.0 | 4.0 | 1.0 | 4.0 |
Term.Term() | 0.0 | 1.0 | 1.0 | 1.0 |
Total | 245.0 | 88.0 | 103.0 | 187.0 |
Average | 5.212765957446808 | 1.872340425531915 | 2.1914893617021276 | 3.978723404255319 |
从上表可以看出,方法复杂度主要集中在Print.PrintExpr()和Print.PrintTerm()方法中,与hw2基本持平;Main.BringFunc()的复杂度较高,其内容与hw2的Main.iint()基本相同。
Bug分析
由于hw3内容大部分继承了hw2的内容,新加的内容并不多,因此强测和互测中均未出现bug。
总结
本次作业耗时较短,思维量较少,且未出现bug。经历了第一次作业的临场发挥和第二次作业的含泪重构,我体会到了在一开始就构建出正确架构的重要性。应当采用普适的方法对问题进行求解而不是只求过关而埋下重构伏笔。由于我对java没有基础,因此这也是我不断学习java语言的过程,所以比较遗憾没有学习上学期的先修课,那样可以为我这学期的OO打下基础。因此接下来我应当投入更多时间到java的学习中,不断夯实基础。