目录
1 第一次作业
1.1 架构设计
1.2 度量分析
2 第二次作业
2.1 架构设计
2.2 度量分析
3 第三次作业
3.1 架构设计
3.2 度量分析
4 测试与debug
5 心得体会
Homework1
1.1 架构设计
本次作业要求为解析表达式并化简,其中表达式由项通过加减法连接,项由因子(变量,常量,表达式)通过乘法连接。故本次作业分为两个模块,一是解析表达式,使用递归下降的办法构造表达式树;二是拆括号,合并同类项化简。
解析表达式
-
分析题意,可以很容易看出表达式-项-因子的层级关系,考虑到因子中表达式因子的设定,故采取递归下降的解析方案——设定读取与解析两个模块,并行操作。不断读入表达式,对于运算符/常数/变量判断后分别处理输入,输入模块将输入的字符串投入解析模块。解析模块自顶到下递归调用。
-
在解析过程中,对于表达式-项的存储,采用Arraylist;在项-因子的存储中,采用Hashmap<factor,index>,即为三种因子统一设置factor父类。
表达式化简
-
设置simplify接口,为表达式中的每个类重写expand函数。
-
为了方便合并同类项,统一化简成Hashmap<指数,系数>(特别注意指数才是唯一的,用作key)。具体实现则是,变量和常量是显然的;对于表达式因子和项,设置运算类,调用编写的加法与乘法函数,统一化简成Hashmap。利用hashmap的特性,将指数相同的进行合并。
1.2 度量分析
UML图
类复杂度
class | OCavg | OCmax | WMC |
---|---|---|---|
expr.Calculator | 4.0 | 4.0 | 8.0 |
expr.Constant | 1.0 | 1.0 | 4.0 |
expr.Expr | 2.0 | 3.0 | 8.0 |
expr.Factor | 1.0 | 1.0 | 2.0 |
expr.Term | 2.0 | 4.0 | 10.0 |
expr.Variable | 1.5 | 2.0 | 3.0 |
MainClass | 5.0 | 7.0 | 15.0 |
Parser | 2.62 | 5.0 | 21.0 |
TokenStream | 2.67 | 7.0 | 16.0 |
Total | 87.0 | ||
Average | 2.42 | 3.78 | 9.67 |
类平均复杂度总体不高,其中Calculator和MainClass方法复杂度较高。主要因为我将本属于Expr类的打印方法放在了主函数中,其中又包含了计算方法,导致耦合度较高。
方法复杂度
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
expr.Calculator.mul(HashMap<Integer, BigInteger>, HashMap<Integer, BigInteger>) | 7 | 1 | 4 | 4 |
expr.Calculator.plus(HashMap<Integer, BigInteger>, HashMap<Integer, BigInteger>) | 5 | 1 | 4 | 4 |
expr.Constant.Constant(boolean) | 0 | 1 | 1 | 1 |
expr.Constant.expand() | 0 | 1 | 1 | 1 |
expr.Constant.setNum(BigInteger) | 0 | 1 | 1 | 1 |
expr.Constant.toString() | 0 | 1 | 1 | 1 |
expr.Expr.addTerm(Term) | 0 | 1 | 1 | 1 |
expr.Expr.expand() | 2 | 1 | 3 | 3 |
expr.Expr.Expr() | 0 | 1 | 1 | 1 |
expr.Expr.toString() | 3 | 1 | 3 | 3 |
expr.Factor.getPow() | 0 | 1 | 1 | 1 |
expr.Factor.setPow(int) | 0 | 1 | 1 | 1 |
expr.Term.addFactor(Factor) | 0 | 1 | 1 | 1 |
expr.Term.expand() | 4 | 2 | 4 | 4 |
expr.Term.flip() | 0 | 1 | 1 | 1 |
expr.Term.Term() | 0 | 1 | 1 | 1 |
expr.Term.toString() | 3 | 1 | 3 | 3 |
expr.Variable.expand() | 0 | 1 | 1 | 1 |
expr.Variable.toString() | 2 | 2 | 2 | 2 |
MainClass.appendValue(int, StringBuilder, HashMap<Integer, BigInteger>) | 13 | 1 | 7 | 7 |
MainClass.main(String[]) | 0 | 1 | 1 | 1 |
MainClass.print(HashMap<Integer, BigInteger>) | 12 | 5 | 8 | 9 |
Parser.parseExpr() | 7 | 1 | 6 | 6 |
Parser.parseExprFactor() | 0 | 1 | 1 | 1 |
Parser.parseFactor() | 3 | 3 | 3 | 3 |
Parser.parseNum() | 3 | 1 | 3 | 3 |
Parser.parsePow(Factor) | 3 | 1 | 3 | 3 |
Parser.Parser(TokenStream) | 0 | 1 | 1 | 1 |
Parser.parseTerm() | 3 | 1 | 4 | 4 |
Parser.parseVariable() | 0 | 1 | 1 | 1 |
TokenStream.blankHidden() | 3 | 1 | 3 | 4 |
TokenStream.getNumber() | 2 | 1 | 3 | 3 |
TokenStream.next() | 8 | 3 | 4 | 7 |
TokenStream.powJudge() | 3 | 3 | 2 | 3 |
TokenStream.symbol() | 0 | 1 | 1 | 1 |
TokenStream.TokenStream(String) | 0 | 1 | 1 | 1 |
Average | 2.39 | 1.33 | 2.42 | 2.58 |
整体复杂度不高,主要还是集中在MainClass中的打印函数部分(涉及print和appendValue)。
Homework2
2.1 架构设计
概览
表达式->项:Hashmap<项,系数>
项->因子:Hashmap<因子,指数>
因子:常数;变量;三角函数因子;表达式因子(sum函数与自定义函数在解析阶段直接展开/替换成表达式因子)
sum函数的展开
在读入了循环的始末后,对sum函数内部的因子再次进行递归解析,没有针对于题目给定的几种因子的分类讨论,而是采取统一的递归下降解析因子的办法,返回一个解析后的因子类,这样的设计也满足了第三次作业中内部嵌套的需求。展开阶段,为了和自定义函数中的replace方法统一,这里将“i”作为形参,循环变量作为实参传入,返回替换后的因子类。再包装成单因子项,加入表达式的hashmap。于是,在初步解析后,sum因子不复存在,直接返回表达式因子。
自定义函数的替换
在预处理阶段获得自定义函数范式的过程中,使用hashmap<函数名,函数因子>并传入解析类。首先,读取解析获取自变量函数名,实参列表。利用hashmap,由函数名获取自定义函数范式,得到表达式因子类。再者,利用表达式的replace方法,传入形参和实参表,进行替换。在我的设计中,replace方法,同样采用递归下降的处理方式。好处是这样的设计使得替换的因子不受限制。
单独为三角函数设置类,存储sin/cos函数名,内部因子和指数。解析中,对于内部因子再次采用递归下降处理因子的办法,即可解决内部因子嵌套的问题。在化简合并的过程中,我统一枚举hashmap<因子,指数>中的因子,利用重写的hashcode&equals方法,在抽象的因子层面比较异同,无需过多考虑sin/cos或者幂函数的形式差异。
2.2 度量分析
UML图
类复杂度
class | OCavg | OCmax | WMC |
---|---|---|---|
homework.Pair | 1 | 1 | 3 |
homework.factors.Factor | 1 | 1 | 5 |
homework.factors.Func | 1 | 1 | 4 |
homework.factors.Function | 1 | 1 | 5 |
homework.factors.Sum | 1 | 1 | 6 |
homework.MainClass | 2 | 2 | 2 |
homework.factors.Constant | 1.272727273 | 3 | 14 |
homework.factors.Variable | 2 | 4 | 16 |
homework.Calculator | 3.333333333 | 5 | 10 |
homework.Parser | 2.461538462 | 6 | 32 |
homework.factors.Expr | 2.642857143 | 7 | 37 |
homework.factors.Term | 2.1875 | 7 | 35 |
homework.TokenStream | 3 | 8 | 21 |
homework.factors.Triangle | 2.4 | 10 | 24 |
Total | 214 | ||
Average | 2.018867925 | 4.071428571 | 15.28571429 |
这次作业中,将上次放在MainClass中的打印方法放入每个类中,最后调用Expr的打印方法,故这次类的复杂度都比较低。
方法复杂度
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
homework.Pair.Pair(T, U) | 0 | 1 | 1 | 1 |
homework.factors.Triangle.Triangle(String, Factor) | 0 | 1 | 1 | 1 |
homework.factors.Triangle.expand() | 0 | 1 | 1 | 1 |
homework.factors.Triangle.hashCode() | 0 | 1 | 1 | 1 |
homework.factors.Triangle.replace(ArrayList, ArrayList) | 0 | 1 | 1 | 1 |
homework.factors.Triangle.setFactor(Factor) | 0 | 1 | 1 | 1 |
homework.factors.Variable.Variable(String) | 0 | 1 | 1 | 1 |
homework.factors.Variable.expand() | 0 | 1 | 1 | 1 |
homework.factors.Variable.hashCode() | 0 | 1 | 1 | 1 |
homework.factors.Variable.optimize() | 0 | 1 | 1 | 1 |
homework.Calculator.mulConst(Expr, BigInteger) | 1 | 1 | 2 | 2 |
homework.MainClass.main(String[]) | 1 | 1 | 2 | 2 |
homework.Parser.parseFunc(HashMap<String, Function>) | 1 | 1 | 2 | 2 |
homework.Parser.parseSum() | 1 | 1 | 2 | 2 |
homework.Parser.preFunc() | 1 | 1 | 2 | 2 |
homework.factors.Constant.print(int) | 1 | 2 | 1 | 2 |
homework.factors.Expr.expand() | 1 | 1 | 2 | 2 |
homework.factors.Expr.optimize() | 1 | 1 | 2 | 2 |
homework.factors.Expr.replace(ArrayList, ArrayList) | 1 | 1 | 2 | 2 |
homework.factors.Expr.squeezeConstant() | 1 | 1 | 2 | 2 |
homework.factors.Expr.toString() | 1 | 1 | 2 | 2 |
homework.factors.Factor.deepClone() | 1 | 1 | 2 | 2 |
homework.factors.Term.Term(HashMap<Factor, Integer>) | 1 | 1 | 2 | 2 |
homework.factors.Term.optimize() | 1 | 1 | 2 | 2 |
homework.factors.Term.replace(ArrayList, ArrayList) | 1 | 1 | 2 | 2 |
homework.factors.Triangle.toString() | 1 | 1 | 2 | 2 |
homework.factors.Variable.toString() | 1 | 1 | 2 | 2 |
homework.Calculator.plus(Expr, Expr) | 2 | 1 | 3 | 3 |
homework.TokenStream.getNumber() | 2 | 1 | 3 | 3 |
homework.factors.Expr.addTerm(Term) | 2 | 1 | 2 | 2 |
homework.factors.Expr.addTerm(Term, BigInteger) | 2 | 1 | 2 | 2 |
homework.factors.Term.addFactor(Factor, int) | 2 | 1 | 2 | 2 |
homework.factors.Term.equals(Object) | 2 | 3 | 1 | 3 |
homework.Parser.parseNum() | 3 | 1 | 3 | 3 |
homework.Parser.parsePow(Factor) | 3 | 1 | 3 | 3 |
homework.Parser.parseTerm() | 3 | 1 | 4 | 4 |
homework.TokenStream.blankHidden() | 3 | 1 | 3 | 4 |
homework.TokenStream.getTriOrSum() | 3 | 4 | 3 | 4 |
homework.TokenStream.isPow() | 3 | 3 | 2 | 3 |
homework.factors.Constant.equals(Object) | 3 | 3 | 2 | 4 |
homework.factors.Expr.equals(Object) | 3 | 3 | 2 | 4 |
homework.factors.Expr.squeezeZero() | 3 | 1 | 3 | 3 |
homework.factors.Triangle.equals(Object) | 3 | 3 | 3 | 5 |
homework.factors.Triangle.print(int) | 3 | 2 | 3 | 3 |
homework.factors.Variable.print(int) | 3 | 2 | 3 | 3 |
homework.factors.Term.toString() | 4 | 2 | 3 | 4 |
homework.factors.Variable.equals(Object) | 4 | 3 | 3 | 5 |
homework.factors.Variable.replace(ArrayList, ArrayList) | 5 | 4 | 3 | 4 |
homework.Parser.parseExpr() | 7 | 1 | 6 | 6 |
homework.factors.Term.squeezeConstant() | 7 | 1 | 5 | 5 |
homework.Calculator.mul(Expr, Expr) | 9 | 1 | 5 | 5 |
homework.Parser.parseFactor() | 9 | 6 | 12 | 12 |
homework.factors.Expr.appendValue(Term, StringBuilder, HashMap<Term, BigInteger>) | 9 | 5 | 6 | 7 |
homework.TokenStream.next() | 10 | 3 | 5 | 9 |
homework.factors.Term.expand() | 11 | 1 | 7 | 7 |
homework.factors.Expr.print() | 12 | 5 | 8 | 9 |
homework.factors.Triangle.optimize() | 20 | 9 | 6 | 10 |
... | ... | ... | ... | ... |
Average | 1.622641509 | 1.424528302 | 1.962264151 | 2.20754717 |
方法复杂度总体不算高,但是三角优化部分和复杂度比较高,仅考虑了性能和正确性。parseFactor方法实际上复杂度并不高,只是由于有很多if条件语句,故值也较高。
Homework3
3.1 架构设计
由于第二次作业一定程度上的超前设计,整体架构基本没有改动。第二次作业中,因为没有进行三角优化,性能分比较拉垮,故在第三次作业中进行了sin(x)^2+cos(x)^2=1以及sin(-x)=-sin(x)的优化,具体实现如下:
第二个优化可以在去除常量优化中同时进行,当三角函数内的表达式只有一项,且该项的符号是负号时,就会根据诱导公式提取出来结合指数乘入表达式的系数。
而第一个优化相对比较复杂,而进行的也不彻底。首先遍历表达式中的所有项,判断其是否有某一指数大于等于2的三角函数因子,如果存在则构造它的对偶形式,如f(x)sin(g(x))^2->f(x)cos(g(x))^2。如果其对偶形式也存在的话,用f(x)*(1-cos(g(x))^2)代替原来的f(x)sin(g(x))^2,其他项保留不变,如此反复迭代直到表达式不再变化即为最终化简结果。
3.2 度量分析
UML图
类复杂度
class | OCavg | OCmax | WMC |
---|---|---|---|
homework.Pair | 1 | 1 | 3 |
homework.factors.Factor | 1 | 1 | 2 |
homework.factors.Function | 1 | 1 | 5 |
homework.MainClass | 2 | 2 | 2 |
homework.factors.Constant | 1.2 | 3 | 12 |
homework.factors.Variable | 1.714285714 | 4 | 12 |
homework.Calculator | 3.333333333 | 5 | 10 |
homework.factors.Triangle | 1.833333333 | 5 | 22 |
homework.Parser | 2.538461538 | 6 | 33 |
homework.TokenStream | 3 | 8 | 21 |
homework.factors.Term | 2.916666667 | 10 | 35 |
homework.factors.Expr | 3.2 | 11 | 48 |
Total | 205 | ||
Average | 2.277777778 | 4.75 | 17.08333333 |
整体架构还算清晰层次化,耦合现象不明显,类平均复杂度不算高。
方法复杂度
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
homework.Pair.Pair(T, U) | 0 | 1 | 1 | 1 |
homework.Pair.getFirst() | 0 | 1 | 1 | 1 |
homework.Pair.getSecond() | 0 | 1 | 1 | 1 |
homework.Parser.Parser(TokenStream) | 0 | 1 | 1 | 1 |
homework.Parser.Parser(TokenStream, HashMap<String, Function>) | 0 | 1 | 1 | 1 |
homework.Parser.parseExprFactor() | 0 | 1 | 1 | 1 |
homework.Parser.parseTri() | 0 | 1 | 1 | 1 |
homework.Parser.parseVariable() | 0 | 1 | 1 | 1 |
homework.factors.Function.getVars() | 0 | 1 | 1 | 1 |
homework.factors.Triangle.setFactor(Expr) | 0 | 1 | 1 | 1 |
homework.factors.Variable.Variable(String) | 0 | 1 | 1 | 1 |
homework.factors.Variable.expand() | 0 | 1 | 1 | 1 |
homework.factors.Variable.hashCode() | 0 | 1 | 1 | 1 |
homework.factors.Variable.optimize() | 0 | 1 | 1 | 1 |
homework.factors.Variable.toString() | 0 | 1 | 1 | 1 |
homework.Calculator.mulConst(Expr, BigInteger) | 1 | 1 | 2 | 2 |
homework.MainClass.main(String[]) | 1 | 1 | 2 | 2 |
homework.Parser.parseSum() | 1 | 1 | 2 | 2 |
homework.Parser.preFunc() | 1 | 1 | 2 | 2 |
homework.factors.Expr.expand() | 1 | 1 | 2 | 2 |
homework.factors.Expr.optimize() | 1 | 1 | 2 | 2 |
homework.factors.Expr.replace(ArrayList, ArrayList) | 1 | 1 | 2 | 2 |
homework.factors.Expr.squeezeConstant() | 1 | 1 | 2 | 2 |
homework.factors.Factor.deepClone() | 1 | 1 | 2 | 2 |
homework.factors.Term.optimize() | 1 | 1 | 2 | 2 |
homework.factors.Term.replace(ArrayList, ArrayList) | 1 | 1 | 2 | 2 |
homework.factors.Triangle.getNegType() | 1 | 1 | 1 | 2 |
homework.Calculator.plus(Expr, Expr) | 2 | 1 | 3 | 3 |
homework.TokenStream.getNumber() | 2 | 1 | 3 | 3 |
homework.factors.Constant.equals(Object) | 2 | 3 | 1 | 3 |
homework.factors.Expr.addTerm(Term) | 2 | 1 | 2 | 2 |
homework.factors.Expr.addTerm(Term, BigInteger) | 2 | 1 | 2 | 2 |
homework.factors.Expr.equals(Object) | 2 | 3 | 1 | 3 |
homework.factors.Term.addFactor(Factor, int) | 2 | 1 | 2 | 2 |
homework.factors.Term.equals(Object) | 2 | 3 | 1 | 3 |
homework.Parser.parseNum() | 3 | 1 | 3 | 3 |
homework.Parser.parsePow() | 3 | 2 | 3 | 3 |
homework.Parser.parseTerm() | 3 | 1 | 4 | 4 |
homework.TokenStream.blankHidden() | 3 | 1 | 3 | 4 |
homework.TokenStream.getTriOrSum() | 3 | 4 | 3 | 4 |
homework.TokenStream.isPow() | 3 | 3 | 2 | 3 |
homework.factors.Expr.squeezeZero() | 3 | 1 | 3 | 3 |
homework.factors.Triangle.equals(Object) | 3 | 3 | 2 | 4 |
homework.factors.Variable.equals(Object) | 3 | 3 | 2 | 4 |
homework.Parser.parseFunc(HashMap<String, Function>) | 4 | 1 | 4 | 4 |
homework.factors.Term.toString() | 4 | 2 | 3 | 4 |
homework.factors.Triangle.optimize() | 5 | 4 | 2 | 4 |
homework.factors.Variable.replace(ArrayList, ArrayList) | 5 | 4 | 3 | 4 |
homework.Parser.parseExpr() | 7 | 1 | 6 | 6 |
homework.Calculator.mul(Expr, Expr) | 9 | 1 | 5 | 5 |
homework.Parser.parseFactor() | 9 | 6 | 12 | 12 |
homework.TokenStream.next() | 10 | 3 | 5 | 9 |
homework.factors.Triangle.toString() | 10 | 5 | 5 | 6 |
homework.factors.Term.expand() | 11 | 1 | 7 | 7 |
homework.factors.Expr.appendValue(Term, StringBuilder, HashMap<Term, BigInteger>) | 12 | 6 | 8 | 9 |
homework.factors.Expr.print() | 13 | 1 | 7 | 7 |
homework.factors.Term.squeezeConstant() | 27 | 7 | 9 | 10 |
homework.factors.Expr.triOptimize() | 36 | 7 | 11 | 12 |
... | ... | ... | ... | ... |
Average | 2.411111111 | 1.566666667 | 2.188888889 | 2.477777778 |
可以明显看出,由于仅考虑了性能和正确性,三角优化和常量优化的复杂度比较高。parseFactor方法实际上复杂度并不高,只是由于有很多if条件语句,故值也较高。
测试与debug
中测前debug
笔者在中测debug过程中,三次均使用自动化评测的方式。第一次的数据生成器主要参考了王小鸽同学的帖子,对拍器部分使用sympy的expand等方法基于python完成。第二次由于引入了三角函数,求和函数与自定义函数,在构造sympy的输入表达式的时候,同时进行了自定义函数与求和函数的展开,之后便可以利用第一次的思路进行比对。第三次由于递归调用的存在,第二次的方法不太适用,直接采取了与伙伴对拍的方法,略微修改了自动评测机,便可以再次利用sympy比对两人的输出。三次测试准备还算比较充分,三次作业在强测和互测中,均未发现bug。
互测
互测阶段,笔者采用自动化评测+边界/特殊样例结合的方式。若在自动化评测中发现房友可能存在bug,则简化测试样例后,使用错误样例对房友代码debug。发现错因后,构造简单样例提交数据。第一次互测中,发起成功hack2次;第二次互测中,发起成功hack1次;第三次互测中,发起成功hack5次。
遇到的bugs
在互测阶段发现的伙伴的bug包含:优化过程中直接将“1*”替换成“ ”而忽略具体语意;嵌套括号后不符合语法规范问题;sum函数循环因子未使用biginteger而爆int出现exception.etc
我在第三次作业的debug过程中,发现由于在输出函数中toString()方法的错误调用,无意间破坏了对象的不可变性,导致了一个很离奇的bug。为了提高代码的稳健性,修改了指数的setPow()和getPow()函数,使得一经初始化的指数不再可更改。除此之外,我在debug过程中,还遇到了如在遍历hashmap时同时进行赋值,直接用=号对对象进行浅拷贝等bug。
心得体会