主要目标:对表达式结构进行建模、计算和化简。
1. 第一次作业
主要目标:通过对表达式结构进行建模,完成单变量多项式的括号展开,初步体会层次化设计的思想。
1.1 基本思路
第一次作业原先是准备使用一般读入模式,但由于对Advance中parse类的改写失败和时间的不足,中途转战预解析模式。
整个程序主要分为两个大类,第一个是Main主类,主要实现表达式的读取和计算化简后的输出。第二部分为Operation类,主要实现表达式树的构建和表达式树的计算与基础化简。
1.2 具体实现
第一次作业的实现方法较为简单,主要点在于表达式树的构建和计算。在Operation类中使用了两个构造函数完成了表达式树的构建,这是为了叶节点没有更下层的左右节点而选择的方法。在该类中也使用了两个有差别的operate方法,分别对应两个操作数和一个操作数的情况,最终通过getData的递归调用就可以获得计算后的表达式结果。
1.3 UML图
1.4 度量分析
1.4.1 方法复杂度分析
CogC
:认知复杂度
ev(G)
:基本圈复杂度
iv(G)
:设计复杂度
v(G)
:圈复杂度
methord | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Operation.Operation(String) | 3.0 | 1.0 | 3.0 | 3.0 |
Operation.getData() | 4.0 | 3.0 | 4.0 | 4.0 |
Operation.operate(ArrayList, String) | 6.0 | 1.0 | 4.0 | 6.0 |
Operation.Operation(String, ArrayList) | 7.0 | 1.0 | 5.0 | 5.0 |
Main.main(String[]) | 24.0 | 1.0 | 12.0 | 12.0 |
Operation.operate(ArrayList, ArrayList, String) | 37.0 | 1.0 | 13.0 | 17.0 |
Total | 81.0 | 8.0 | 41.0 | 47.0 |
Average | 13.5 | 1.33 | 6.83 | 7.83 |
1.4.2 类复杂度分析
OCavg
: 平均操作复杂度
OCmax
: 最大操作复杂度
WMC
: 加权方法复杂度
class | OCavg | OCmax | WMC |
---|---|---|---|
Main | 11.0 | 11.0 | 11.0 |
Operation | 7.0 | 18.0 | 35.0 |
Total | 46.0 | ||
Average | 7.67 | 14.50 | 23.00 |
1.5 bug分析
本次作业代码存在一个bug,在数据处理时误将一个地方的数据转化为long型进行处理,导致数据溢出。
互测中发现两人bug,两人均是化简过程中输出错误。
2. 第二次作业
主要目标:通过对表达式结构进行建模,完成多项式的括号展开与函数调用、化简,进一步体会层次化设计的思想。
2.1 基本思路
第二次作业相较于第一次作业增加了三角函数,自定义函数和求和函数的处理。
整体思路分为两大部分:首先是对自定义函数和求和函数的处理,我的选择是在表达式解析前通过对字符串的替换来解决该问题;第二部分为替换后表达式的解析与计算,通过Lexer和Parse等类逐步读取表达式中的符号,并在解析过程中通过Poly类逐渐完成表达式的计算。
2.2 具体实现
2.2.1 自定义函数和求和函数的替换
自定义函数和求和函数的替换主要在Main类的operate函数中实现,通过循环字符串找到自定义函数和求和函数的位置,并通过字符串分割和字符串替换的功能将代入转换后的表达式替换到原表达式中,并返回到main主函数中。在此过程中需要注意求和函数替换i时避免sin中的错误替换。
2.2.2 解析、计算表达式
在这一部分,所有的表达式,项,因子均可定义到一个多项式类Poly类中,在Expr类和Term类中均有生成Poly类的构造函数,整个表达式的计算与化简也基本在Poly类中完成。在Poly类中具体定义了一个HashMap的属性,该HashMap的键为除系数外的所有因子,值为系数。在第二次作业中,与第一次作业不同的是除系数外不再仅仅只有x,而是增添了sin、cos的三角函数部分。对此,我的处理是新建了一个TriX类用来表示x和三角函数。
2.2.2.1 TriX类
在TriX类中,sin、cos也使用了HashMap用来存储,其键值分别为三角函数内的表达式Mono与其乘方,而x的部分则使用BingInterger类型的xindex存储其乘方数。除此之外,TriX类中的getCosString和getSinString方法用来返回化简后的cos和sin函数。
2.2.2.2 Poly类
对于整个项目而言,我认为Poly类的重要性绝对是前二的。在这个类中主要实现了多项式的创建,计算和化简。多项式的创建不必多言,而计算使用了多个方法共同完成。在乘法mul中,通过两次循环逐一相乘并相加实现乘法的运算,并通过对多项式key值的比较实现基础的同类项合并,而在乘方pow中,则是通过循环不断地调用mul方法实现乘方的计算。多项式的化简主要在toString方法中,这次实现的化简方法较为简单,只实现了x乘方数的特殊值化简,仍然有很大的改进空间。
2.3 UML图
2.4 度量分析
2.4.1 方法复杂度分析
methord | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
expr.Expr.addTerm(Term, String) | 7.0 | 1.0 | 5.0 | 5.0 |
expr.Expr.getPoly() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Mono.equals(Object) | 3.0 | 3.0 | 2.0 | 4.0 |
expr.Mono.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Mono.Mono(BigInteger, BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Mono.toString() | 4.0 | 1.0 | 3.0 | 3.0 |
expr.Poly.add(Poly) | 4.0 | 1.0 | 3.0 | 3.0 |
expr.Poly.getPoly() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Poly.getPolyNomial() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Poly.mul(Poly) | 39.0 | 1.0 | 12.0 | 12.0 |
expr.Poly.neg() | 1.0 | 1.0 | 2.0 | 2.0 |
expr.Poly.Poly() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Poly.Poly(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Poly.Poly(BigInteger, BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Poly.Poly(Factor, BigInteger, String) | 4.0 | 1.0 | 3.0 | 3.0 |
expr.Poly.pow(BigInteger) | 4.0 | 1.0 | 3.0 | 3.0 |
expr.Poly.sub(Poly) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Poly.toString() | 10.0 | 1.0 | 7.0 | 7.0 |
expr.Term.addFactor(Factor, String) | 10.0 | 5.0 | 6.0 | 6.0 |
expr.Term.getPoly() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.TriX.equals(Object) | 3.0 | 2.0 | 4.0 | 4.0 |
expr.TriX.getCos() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.TriX.getCosString(String) | 9.0 | 2.0 | 4.0 | 5.0 |
expr.TriX.getSin() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.TriX.getSinString(String) | 9.0 | 2.0 | 4.0 | 5.0 |
expr.TriX.getXindex() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.TriX.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.TriX.isEmpty() | 1.0 | 1.0 | 3.0 | 3.0 |
expr.TriX.TriX(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.TriX.TriX(BigInteger, BigInteger, String, Boolean) | 8.0 | 1.0 | 6.0 | 6.0 |
expr.TriX.TriX(Map, Map, BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
Lexer.getNumber() | 2.0 | 1.0 | 3.0 | 3.0 |
Lexer.getTri() | 0.0 | 1.0 | 1.0 | 1.0 |
Lexer.Lexer(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Lexer.next() | 4.0 | 2.0 | 4.0 | 5.0 |
Lexer.peek() | 0.0 | 1.0 | 1.0 | 1.0 |
Main.main(String[]) | 26.0 | 1.0 | 11.0 | 11.0 |
Main.operate(String, HashMap, ArrayList, ArrayList, ArrayList) | 99.0 | 1.0 | 32.0 | 36.0 |
Parser.parseExpr() | 4.0 | 1.0 | 5.0 | 5.0 |
Parser.parseFactor() | 25.0 | 5.0 | 14.0 | 14.0 |
Parser.Parser(Lexer) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.parseTerm() | 4.0 | 1.0 | 5.0 | 5.0 |
Total | 280.0 | 56.0 | 161.0 | 170.0 |
Average | 6.67 | 1.33 | 3.83 | 4.05 |
2.4.2 类复杂度分析
class | OCavg | OCmax | WMC |
---|---|---|---|
Parser | 4.75 | 12.0 | 19.0 |
Main | 23.5 | 36.0 | 47.0 |
Lexer | 2.0 | 5.0 | 10.0 |
expr.TriX | 2.27 | 6.0 | 25.0 |
expr.Term | 3.5 | 6.0 | 7.0 |
expr.Poly | 3.0 | 12.0 | 36.0 |
expr.Mono | 2.0 | 3.0 | 8.0 |
expr.Expr | 3.0 | 5.0 | 6.0 |
Total | 158.0 | ||
Average | 3.76 | 10.63 | 19.75 |
2.5 bug分析
本次作业未能进入互测房间,bug来源于对 "\t" 的替换并未实现,在中测2中挂掉一个点,强测中挂掉两个点,修复gaibug后强测通过。
3. 第三次作业
主要目标:通过对表达式结构进行建模,完成多层嵌套表达式和函数调用的括号展开与化简,进一步体会层次化设计的思想。
3.1 基本思路
第三次作业相较于第二次作业增加了多层嵌套,由于第二次作业已经基本满足第三次作业要求,故只有少量改动。
3.2 具体实现
第三次作业基本和第二次作业相同,只对连个小地方做了更改。
3.2.1 自定义函数和求和函数
由于本次作业支持函数的部分嵌套,因此在函数替换这一部分增加了while判断,直到表达式中不存在自定义函数和求和函数时才退出循环,并且将函数的替换拆分为了几个更小的方法,便于理解。
3.3.2 三角函数
本次作业的另一修改便是将第二次作业中三角函数部分的key值由Mono修改为String类型,在Parse解析时直接将化简后的表达式传入三角函数的内部。
3.3.3 表达式化简
第三次作业在表达式化简上更近了一步,对三角函数的特殊值,系数的特殊值,乘方的特殊值都进行了一定的处理替换,使得最后的表达式更加简洁,并且为了避免替换后仍然存在可化简部分,main主方法内部对整个程序又重新跑了一次。
3.3 UML图和度量分析
3.4 度量分析
3.4.1 方法复杂度分析
methord | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Parser.parseTerm() | 4.0 | 1.0 | 5.0 | 5.0 |
Parser.Parser(Lexer) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.parseFactor() | 33.0 | 6.0 | 19.0 | 19.0 |
Parser.parseExpr() | 4.0 | 1.0 | 5.0 | 5.0 |
Main.operateM(String) | 31.0 | 1.0 | 10.0 | 11.0 |
Main.operateH(String, HashMap, ArrayList) | 35.0 | 1.0 | 12.0 | 13.0 |
Main.operateG(String, HashMap, ArrayList) | 35.0 | 1.0 | 12.0 | 13.0 |
Main.operateF(String, HashMap, ArrayList) | 35.0 | 1.0 | 12.0 | 13.0 |
Main.operate(String, HashMap, ArrayList, ArrayList, ArrayList) | 10.0 | 1.0 | 9.0 | 9.0 |
Main.main(String[]) | 26.0 | 1.0 | 11.0 | 11.0 |
Lexer.peek() | 0.0 | 1.0 | 1.0 | 1.0 |
Lexer.next() | 4.0 | 2.0 | 4.0 | 5.0 |
Lexer.Lexer(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Lexer.getTri() | 0.0 | 1.0 | 1.0 | 1.0 |
Lexer.getNumber() | 2.0 | 1.0 | 3.0 | 3.0 |
expr.TriX.TriX(String, BigInteger, String) | 2.0 | 1.0 | 3.0 | 3.0 |
expr.TriX.TriX(Map, Map, BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.TriX.TriX(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.TriX.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.TriX.getXindex() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.TriX.getSinString(boolean) | 18.0 | 4.0 | 7.0 | 8.0 |
expr.TriX.getSin() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.TriX.getCosString(boolean) | 18.0 | 4.0 | 7.0 | 8.0 |
expr.TriX.getCos() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.TriX.equals(Object) | 3.0 | 2.0 | 4.0 | 4.0 |
expr.Term.getPoly() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.addFactor(Factor, String) | 10.0 | 5.0 | 6.0 | 6.0 |
expr.Poly.toString() | 41.0 | 1.0 | 13.0 | 16.0 |
expr.Poly.sub(Poly) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Poly.pow(BigInteger) | 4.0 | 1.0 | 3.0 | 3.0 |
expr.Poly.Poly(String, BigInteger, String) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Poly.Poly(BigInteger, BigInteger, String) | 4.0 | 1.0 | 3.0 | 3.0 |
expr.Poly.Poly(BigInteger, BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Poly.Poly(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Poly.Poly() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Poly.neg() | 1.0 | 1.0 | 2.0 | 2.0 |
expr.Poly.mul(Poly) | 39.0 | 1.0 | 12.0 | 12.0 |
expr.Poly.getPolyNomial() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Poly.getPoly() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Poly.add(Poly) | 4.0 | 1.0 | 3.0 | 3.0 |
expr.Expr.getPoly() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.addTerm(Term, String) | 7.0 | 1.0 | 5.0 | 5.0 |
Total | 370.0 | 59.0 | 189.0 | 199.0 |
Average | 8.81 | 1.40 | 4.50 | 4.74 |
3.4.2 类复杂度分析
class | OCavg | OCmax | WMC |
---|---|---|---|
Main | 10.5 | 12.0 | 63.0 |
expr.Poly | 3.54 | 16.0 | 46.0 |
expr.TriX | 2.7 | 8.0 | 27.0 |
Parser | 5.5 | 15.0 | 22.0 |
Lexer | 2.0 | 5.0 | 10.0 |
expr.Term | 3.5 | 6.0 | 7.0 |
expr.Expr | 3.0 | 5.0 | 6.0 |
Total | 181.0 | ||
Average | 4.31 | 9.57 | 25.86 |
3.5 bug分析
本次作业测出一个bug,三角函数的化简替换出现问题,程序运行第二遍时出现无法处理的6*1**3,最后答案会输出错误值216,修改方案只需在特殊值替换时外层套上一层括号。
互测中发现两个bug,一个是求和函数部分出现超大数据是会爆int,另外 一个是求和函数替换后出现的1**6无法解析处理。
4. 架构设计体验
4.1 优缺点
三次作业优缺点都很明显,部分类较为臃肿,可以更加细致地拆分为更小的类,部分地方的处理十分粗糙,还有许多的改进空间,例如化简部分,没有对三角函数进行更多的化简,丢失了许多的性能分。优点就是第二次作业的开发较为成功,在第三次作业的时候几乎没有太多的改动。
4.2 架构迭代
由于第一次作业选择了预解析模式,第二次作业写得十分痛苦,完全地把第一次作业推倒进行了重构,第三次作业在第二次的基础上进行了较小的迭代,更改了少量地方就完成了任务。
4.3 心得体会
- 项目的架构一定要便于拓展,防止后续迭代过程中多次重构浪费时间。
- 要学会构造实验数据,最好编写自动化测试的程序,确保测试数据完全覆盖。
4.4 总结
面向对象课程的第一次作业就已经给我带来了很大的压力,在没有太多基础的情况下第一次的一般读入开发以失败告终,最终在第二周经过系统性的重构才能够完成基础的任务。只能说这是一个很痛苦的过程,但也的确学到了很多东西,每一次的重新思考都会有一些收获,觉得这样弄可能会让代码更加的简洁明了,也会更适合后续的迭代开发。但我还是认为课程的难度分布不是十分地均匀,第一次作业从0开始的难度过大,而第三次作业迭代开发的难度相对有较小,希望能够稍微调整一下每次作业的难度分布。