面向对象设计与构造第一单元总结
作业分析
第一次作业
题目简述
实现只包含+,-,*,(),**表达式的化简,括号深度最大为一层。
思路简述
表达式解析部分参考了第一单元训练中的递归下降的结构,先将表达式拆成若干表达式、项和因子,然后根据从属关系合并得到答案。
对于负号,我把负号全部下传,标记到底层的因子上,这样只需要在因子记录正负号即可。
合并的时候使用Poly类来作为结果,Poly类是一个形如\(a_0\times x^{b_0}+a_1\times x^{b_1}+\dots\)的关于\(x\)的多项式,用HashMap存储指数对应的系数。
最后得到整个表达式的Poly作为答案,这样已经是合并同类项的形式。
做了几个优化:\(x**2 \rightarrow x*x\),把正系数放到第一个并且去掉"+"。
程序分析
代码规模分析
Source File | Total Lines | Source Code Lines |
---|---|---|
Expr.java | 28 | 24 |
Factor.java | 43 | 37 |
Lexer.java | 46 | 42 |
MainClass.java | 12 | 11 |
Number.java | 23 | 20 |
Parser.java | 62 | 55 |
Poly.java | 109 | 96 |
Term.java | 26 | 21 |
Total | 349 | 306 |
代码量并不太大,最大的代码量在Poly.java,也就是多项式的处理中,因为需要写加、乘、幂等多项式计算的方法。整体来说比较平均
方法复杂度分析
method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Expr.addTerm(Term, String) | 0 | 1 | 1 | 1 |
Expr.Expr() | 0 | 1 | 1 | 1 |
Expr.getPoly() | 2 | 1 | 3 | 3 |
Factor.Factor() | 0 | 1 | 1 | 1 |
Factor.getExp() | 0 | 1 | 1 | 1 |
Factor.getOp() | 0 | 1 | 1 | 1 |
Factor.getPoly() | 0 | 1 | 1 | 1 |
Factor.setExp(Lexer) | 5 | 2 | 4 | 4 |
Factor.setOp(String) | 1 | 1 | 1 | 2 |
Lexer.getNow() | 0 | 1 | 1 | 1 |
Lexer.getNumber() | 3 | 2 | 3 | 4 |
Lexer.Lexer(String) | 0 | 1 | 1 | 1 |
Lexer.next() | 8 | 2 | 6 | 7 |
MainClass.main(String[]) | 0 | 1 | 1 | 1 |
Number.getPoly() | 2 | 2 | 2 | 2 |
Number.Number(String) | 0 | 1 | 1 | 1 |
Parser.getExpr() | 4 | 1 | 5 | 5 |
Parser.getFactor() | 5 | 1 | 4 | 4 |
Parser.getTerm() | 3 | 1 | 4 | 4 |
Parser.Parser(Lexer) | 0 | 1 | 1 | 1 |
Poly.add(Poly) | 2 | 1 | 3 | 3 |
Poly.addTerm(Integer, BigInteger) | 2 | 1 | 2 | 2 |
Poly.coefToString(BigInteger, int, Integer) | 8 | 4 | 2 | 7 |
Poly.expToString(Integer) | 4 | 4 | 1 | 4 |
Poly.getCoef() | 0 | 1 | 1 | 1 |
Poly.multiply(Poly) | 3 | 1 | 3 | 3 |
Poly.neg() | 1 | 1 | 2 | 2 |
Poly.output() | 4 | 1 | 5 | 5 |
Poly.Poly() | 0 | 1 | 1 | 1 |
Poly.Poly(BigInteger) | 0 | 1 | 1 | 1 |
Poly.pow(int) | 1 | 1 | 2 | 2 |
Term.addFactor(Factor) | 0 | 1 | 1 | 1 |
Term.getPoly() | 1 | 1 | 2 | 2 |
Term.setOp(String) | 0 | 1 | 1 | 1 |
Term.Term() | 0 | 1 | 1 | 1 |
Total | 59 | 45 | 71 | 82 |
Average | 1.69 | 1.29 | 2.03 | 2.34 |
整体复杂度并不高,有几处ev(G)较高,是使用了多个if来判断输出内容进行简化的地方。
类复杂度分析
class | OCavg | OCmax | WMC |
---|---|---|---|
Expr | 1.67 | 3 | 5 |
Factor | 1.50 | 3 | 9 |
Lexer | 2.50 | 5 | 10 |
MainClass | 1.00 | 1 | 1 |
Number | 1.50 | 2 | 3 |
Parser | 2.50 | 3 | 10 |
Poly | 2.82 | 7 | 31 |
Term | 1.25 | 2 | 5 |
Total | 74 | ||
Average | 2.11 | 3.25 | 9.25 |
只有Poly类复杂度较高,原因和上面两个一样,需要进行一系列运算和输出。
类图
优点:
按表达式、项、因子等分类,结构比较清晰。采用递归下降方法解析,有良好的可扩展性。
缺点:
Number类中记录了String,包含了常数和\(x\),使用if判断。实际上应该将这两种再进行细分,或者是统一成系数和指数的形式。当时图方便就直接这样写了。
Poly类的各种运算是用方法实现的,这样不符合面向对象,而是面向过程了,应该对于每个运算新建类专门处理。
Bug分析
强测中获得了100分,互测中没有被Hack。
Hack了一位同学的1个bug,他在简化输出的时候遇到"-0"会只输出一个"-"。
第二次作业
题目简述
实现只包含+,-,*,(),**,\(sin()\),\(cos()\),\(sum()\)以及自定义函数的表达式的化简,多余括号深度最大为一层,\(sin()\)和\(cos()\)只会出现常数和\(x^k\),\(sum()\)和自定义函数不会嵌套。
思路简述
在第一次作业的基础上迭代开发。
合并的时候使用魔改后的Poly类来作为结果,Poly类形如\(a_0\times u_0+a_1\times u_1+\dots\)。其中\(u_i\)为Unit类。用HashMap<Unit,BigInteger>存储系数。
Unit类是由\(x,sin(x^k),cos(x^k)\)若干因子相乘所得项。用一个Integer存\(x\)的指数,两个HashMap<Number,Integer>分别存\(sin()\)和\(cos()\)的指数。
对于自定义函数,先把每个自定义函数解析成表达式。建立一个变量到因子的映射,化简最终表达式的时候,若碰到自定义函数,就修改映射,在自定义函数里化简。
例如,\(f(y,x)=y*2+x\),调用\(f(sin(x),x)\),此时映射为\(\{y \rightarrow sin(x),x \rightarrow x\}\)。
在自定义函数里碰到变量,就根据映射替换成对应的因子。
\(sum()\)函数也可以通过枚举\(i\),把\(i\)映射成因子(一个常数)来解决。
输出时做了\(sin(0) \rightarrow 0\),\(cos(0) \rightarrow 1\),\(sin(-k) \rightarrow -sin(k)\),\(cos(-k) \rightarrow cos(k)\)的简化。
程序分析
代码规模分析
Source File | Total Lines | Source Code Lines |
---|---|---|
CusFunc.java | 29 | 25 |
CustomFunc.java | 27 | 22 |
Expr.java | 32 | 27 |
Factor.java | 37 | 29 |
Lexer.java | 61 | 54 |
MainClass.java | 22 | 19 |
Number.java | 78 | 70 |
Parser.java | 138 | 120 |
Poly.java | 130 | 117 |
SumFunc.java | 49 | 41 |
Term.java | 27 | 22 |
TriFunc.java | 37 | 32 |
Unit.java | 163 | 143 |
Total | 830 | 721 |
第二次和第一次相比代码量有了明显提升,多了一倍多,新加的函数实现起来需要还是需要一定的代码量。
主要代码在于Parser.java,Poly.java和Unit.java,整体来说还是比较平均。
方法复杂度分析
method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
CusFunc.addFactor(Factor) | 0 | 1 | 1 | 1 |
CusFunc.CusFunc(String) | 0 | 1 | 1 | 1 |
CusFunc.getPoly(HashMap<String, CustomFunc>, HashMap<String, Factor>) | 2 | 1 | 3 | 3 |
CustomFunc.CustomFunc(String) | 0 | 1 | 1 | 1 |
CustomFunc.getPoly(HashMap<String, CustomFunc>, ArrayList |
1 | 1 | 2 | 2 |
Expr.addTerm(Term, String) | 0 | 1 | 1 | 1 |
Expr.Expr() | 0 | 1 | 1 | 1 |
Expr.getPoly(HashMap<String, CustomFunc>, HashMap<String, Factor>) | 3 | 1 | 4 | 4 |
Factor.Factor() | 0 | 1 | 1 | 1 |
Factor.getExp() | 0 | 1 | 1 | 1 |
Factor.getOp() | 0 | 1 | 1 | 1 |
Factor.getPoly(HashMap<String, CustomFunc>, HashMap<String, Factor>) | 0 | 1 | 1 | 1 |
Factor.setExp(int) | 0 | 1 | 1 | 1 |
Factor.setExp(String) | 0 | 1 | 1 | 1 |
Factor.setOp(String) | 1 | 1 | 1 | 2 |
Lexer.getNow() | 0 | 1 | 1 | 1 |
Lexer.getNumber() | 2 | 1 | 3 | 3 |
Lexer.getType() | 3 | 3 | 4 | 5 |
Lexer.Lexer(String) | 0 | 1 | 1 | 1 |
Lexer.next() | 8 | 2 | 5 | 6 |
MainClass.main(String[]) | 1 | 1 | 2 | 2 |
Number.equals(Object) | 4 | 3 | 4 | 6 |
Number.getNum() | 0 | 1 | 1 | 1 |
Number.getPoly(HashMap<String, CustomFunc>, HashMap<String, Factor>) | 5 | 1 | 5 | 5 |
Number.hashCode() | 0 | 1 | 1 | 1 |
Number.Number(String) | 3 | 1 | 3 | 3 |
Number.toString() | 7 | 4 | 4 | 5 |
Parser.getCusFunc() | 2 | 1 | 3 | 3 |
Parser.getExpr() | 4 | 1 | 5 | 5 |
Parser.getFactor() | 13.0 | 1 | 12 | 12 |
Parser.getSumFunc() | 8 | 1 | 5 | 5 |
Parser.getTerm() | 3 | 1 | 4 | 4 |
Parser.getTriFunc() | 0 | 1 | 1 | 1 |
Parser.Parser(Lexer) | 0 | 1 | 1 | 1 |
Poly.add(Poly) | 2 | 1 | 3 | 3 |
Poly.addUnit(Unit, BigInteger) | 2 | 1 | 2 | 2 |
Poly.adjust() | 4 | 1 | 3 | 3 |
Poly.coefToString(BigInteger, int, Unit) | 9 | 5 | 1 | 8 |
Poly.getCoef() | 0 | 1 | 1 | 1 |
Poly.getNumber() | 4 | 3 | 3 | 3 |
Poly.multiply(Poly) | 3 | 1 | 3 | 3 |
Poly.neg() | 1 | 1 | 2 | 2 |
Poly.output() | 4 | 1 | 5 | 5 |
Poly.Poly() | 0 | 1 | 1 | 1 |
Poly.Poly(BigInteger) | 0 | 1 | 1 | 1 |
Poly.pow(int) | 1 | 1 | 2 | 2 |
SumFunc.getPoly(HashMap<String, CustomFunc>, HashMap<String, Factor>) | 4 | 1 | 5 | 5 |
SumFunc.setEnd(String) | 0 | 1 | 1 | 1 |
SumFunc.setFactor(Expr) | 0 | 1 | 1 | 1 |
SumFunc.setStart(String) | 0 | 1 | 1 | 1 |
SumFunc.setVar(String) | 0 | 1 | 1 | 1 |
SumFunc.SumFunc() | 0 | 1 | 1 | 1 |
Term.addFactor(Factor) | 0 | 1 | 1 | 1 |
Term.getPoly(HashMap<String, CustomFunc>, HashMap<String, Factor>) | 1 | 1 | 2 | 2 |
Term.setOp(String) | 0 | 1 | 1 | 1 |
Term.Term() | 0 | 1 | 1 | 1 |
TriFunc.getPoly(HashMap<String, CustomFunc>, HashMap<String, Factor>) | 4 | 1 | 4 | 4 |
TriFunc.setFactor(Factor) | 0 | 1 | 1 | 1 |
TriFunc.TriFunc(String) | 0 | 1 | 1 | 1 |
Unit.addCos(Number, int) | 3 | 2 | 2 | 3 |
Unit.addSin(Number, int) | 2 | 1 | 2 | 2 |
Unit.adjust() | 15 | 1 | 7 | 8 |
Unit.checkZero() | 0 | 1 | 1 | 1 |
Unit.equals(Object) | 4 | 3 | 4 | 6 |
Unit.getCoss() | 0 | 1 | 1 | 1 |
Unit.getExp() | 0 | 1 | 1 | 1 |
Unit.getSins() | 0 | 1 | 1 | 1 |
Unit.hashCode() | 0 | 1 | 1 | 1 |
Unit.multiply(Unit) | 4 | 1 | 5 | 5 |
Unit.toString() | 10 | 2 | 8 | 9 |
Unit.Unit(int) | 0 | 1 | 1 | 1 |
Total | 147 | 89 | 168 | 186 |
Average | 2.07 | 1.25 | 2.36 | 2.61 |
整体复杂度不高。Parser.getFactor()的复杂度较高,是因为这里对Factor的种类进行了分类讨论。
类复杂度分析
class | OCavg | OCmax | WMC |
---|---|---|---|
CusFunc | 1.66 | 3 | 5 |
CustomFunc | 1.50 | 2 | 3 |
Expr | 2.00 | 4 | 6 |
Factor | 1.14 | 2 | 8 |
Lexer | 2.40 | 5 | 12 |
MainClass | 2.00 | 2 | 2 |
Number | 3.00 | 5 | 18 |
Parser | 3.28 | 8 | 23 |
Poly | 2.83 | 8 | 34 |
SumFunc | 1.66 | 5 | 10 |
Term | 1.25 | 2 | 5 |
TriFunc | 2.00 | 4 | 6 |
Unit | 3.00 | 9 | 36 |
Total | 168 | ||
Average | 2.36 | 4.53 | 12.92 |
Parser类的平均复杂度较高,因为有很多分类讨论。
Poly类和Unit类的总复杂度较高,因为方法比较多。
类图
为了简化,类图中所有getPoly(HashMap,HashMap)实际上两个HashMap分别为HashMap<String, CustomFunc>和HashMap<String, Factor>,分别存的是函数的映射和变量的映射。
优点:
使用HashMap映射的方式来代入变量和函数,比较方便、好写。
用adjust方法将Poly和Unit进行整理,合并同类项,输出更加简化。
缺点:
getPoly方法有些冗长,可以新建一个类专门存储两个映射关系,这样会美观很多。
Bug分析
强测中正确性通过了所有测试点,获得了95.3903分(没有处理\(sin^2+cos^2\)),互测中没有被Hack。
Hack了一位同学的1个bug,他把\(sin(0)\)处理成了\(1\)。
第三次作业
题目简述
实现只包含+,-,*,(),**,\(sin()\),\(cos()\),\(sum()\)以及自定义函数的表达式的化简,有括号嵌套,\(sin()\)和\(cos()\)可以是任意因子,\(sum()\)不会嵌套,自定义函数可能嵌套。
思路简述
在第二次作业的基础上迭代开发。
实际上第二次作业的架构已经支持括号嵌套和自定义函数嵌套。只需要增加三角函数的因子种类。
相当于在三角函数里放了Poly类。把Unit类的HashMap改成<Poly,Integer>即可。
化简仍然是之前的几类,但是相对复杂,需要注意深克隆HashMap。
Unit提取负号的时候,Poly里有可能全是正号,有可能全是负号,有可能两个都有。如果全是负号,那么提负号出来会减少长度。
但是这样也会影响外部的符号,负号可以是奇数个的时候可以把正负号都有的Poly也取反,让外部回正。
程序分析
代码规模分析
Source File | Total Lines | Source Code Lines |
---|---|---|
CusFunc.java | 29 | 25 |
CustomFunc.java | 27 | 22 |
Expr.java | 32 | 27 |
Factor.java | 37 | 29 |
Lexer.java | 61 | 54 |
MainClass.java | 30 | 26 |
Number.java | 39 | 35 |
Parser.java | 138 | 120 |
Poly.java | 166 | 151 |
SumFunc.java | 50 | 42 |
Term.java | 27 | 22 |
TriFunc.java | 37 | 32 |
Unit.java | 211 | 190 |
Total | 884 | 775 |
代码规模和第二次作业差不多,没有大的改动。
方法复杂度分析
method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
CusFunc.addFactor(Factor) | 0 | 1 | 1 | 1 |
CusFunc.CusFunc(String) | 0 | 1 | 1 | 1 |
CusFunc.getPoly(HashMap<String, CustomFunc>, HashMap<String, Factor>) | 2 | 1 | 3 | 3 |
CustomFunc.CustomFunc(String) | 0 | 1 | 1 | 1 |
CustomFunc.getPoly(HashMap<String, CustomFunc>, ArrayList |
1 | 1 | 2 | 2 |
Expr.addTerm(Term, String) | 0 | 1 | 1 | 1 |
Expr.Expr() | 0 | 1 | 1 | 1 |
Expr.getPoly(HashMap<String, CustomFunc>, HashMap<String, Factor>) | 3 | 1 | 4 | 4 |
Factor.Factor() | 0 | 1 | 1 | 1 |
Factor.getExp() | 0 | 1 | 1 | 1 |
Factor.getOp() | 0 | 1 | 1 | 1 |
Factor.getPoly(HashMap<String, CustomFunc>, HashMap<String, Factor>) | 0 | 1 | 1 | 1 |
Factor.setExp(int) | 0 | 1 | 1 | 1 |
Factor.setExp(String) | 0 | 1 | 1 | 1 |
Factor.setOp(String) | 1 | 1 | 1 | 2 |
Lexer.getNow() | 0 | 1 | 1 | 1 |
Lexer.getNumber() | 2 | 1 | 3 | 3 |
Lexer.getType() | 3 | 3 | 4 | 5 |
Lexer.Lexer(String) | 0 | 1 | 1 | 1 |
Lexer.next() | 8 | 2 | 5 | 6 |
MainClass.main(String[]) | 3 | 1 | 3 | 3 |
Number.getPoly(HashMap<String, CustomFunc>, HashMap<String, Factor>) | 5 | 1 | 5 | 5 |
Number.Number(String) | 3 | 1 | 3 | 3 |
Parser.getCusFunc() | 2 | 1 | 3 | 3 |
Parser.getExpr() | 4 | 1 | 5 | 5 |
Parser.getFactor() | 13 | 1 | 12 | 12 |
Parser.getSumFunc() | 8 | 1 | 5 | 5 |
Parser.getTerm() | 3 | 1 | 4 | 4 |
Parser.getTriFunc() | 0 | 1 | 1 | 1 |
Parser.Parser(Lexer) | 0 | 1 | 1 | 1 |
Poly.add(Poly) | 2 | 1 | 3 | 3 |
Poly.addUnit(Unit, BigInteger) | 2 | 1 | 2 | 2 |
Poly.adjust() | 4 | 1 | 3 | 3 |
Poly.coefToString(BigInteger, int, Unit, boolean) | 9 | 5 | 2 | 8 |
Poly.equals(Object) | 3 | 3 | 2 | 4 |
Poly.getCoef() | 0 | 1 | 1 | 1 |
Poly.getOp() | 6 | 3 | 3 | 6 |
Poly.hashCode() | 0 | 1 | 1 | 1 |
Poly.multiply(Poly) | 3 | 1 | 3 | 3 |
Poly.neg() | 1 | 1 | 2 | 2 |
Poly.Poly() | 0 | 1 | 1 | 1 |
Poly.Poly(BigInteger) | 0 | 1 | 1 | 1 |
Poly.pow(int) | 1 | 1 | 2 | 2 |
Poly.toString(boolean) | 9 | 3 | 7 | 9 |
SumFunc.getPoly(HashMap<String, CustomFunc>, HashMap<String, Factor>) | 4 | 1 | 5 | 5 |
SumFunc.setEnd(String) | 0 | 1 | 1 | 1 |
SumFunc.setFactor(Expr) | 0 | 1 | 1 | 1 |
SumFunc.setStart(String) | 0 | 1 | 1 | 1 |
SumFunc.setVar(String) | 0 | 1 | 1 | 1 |
SumFunc.SumFunc() | 0 | 1 | 1 | 1 |
Term.addFactor(Factor) | 0 | 1 | 1 | 1 |
Term.getPoly(HashMap<String, CustomFunc>, HashMap<String, Factor>) | 1 | 1 | 2 | 2 |
Term.setOp(String) | 0 | 1 | 1 | 1 |
Term.Term() | 0 | 1 | 1 | 1 |
TriFunc.getPoly(HashMap<String, CustomFunc>, HashMap<String, Factor>) | 4 | 1 | 4 | 4 |
TriFunc.setFactor(Factor) | 0 | 1 | 1 | 1 |
TriFunc.TriFunc(String) | 0 | 1 | 1 | 1 |
Unit.addCos(Poly, int) | 3 | 2 | 2 | 3 |
Unit.addSin(Poly, int) | 2 | 1 | 2 | 2 |
Unit.adjust() | 0 | 1 | 1 | 1 |
Unit.adjustCos() | 10 | 1 | 7 | 7 |
Unit.adjustSin() | 16 | 1 | 12 | 13 |
Unit.checkZero() | 0 | 1 | 1 | 1 |
Unit.equals(Object) | 4 | 3 | 4 | 6 |
Unit.getCoss() | 0 | 1 | 1 | 1 |
Unit.getExp() | 0 | 1 | 1 | 1 |
Unit.getFactorCnt(BigInteger) | 5 | 2 | 3 | 6 |
Unit.getSins() | 0 | 1 | 1 | 1 |
Unit.hashCode() | 0 | 1 | 1 | 1 |
Unit.multiply(Unit) | 4 | 1 | 5 | 5 |
Unit.toString(boolean) | 11 | 2 | 8 | 10 |
Unit.Unit(int) | 0 | 1 | 1 | 1 |
Total | 165 | 90 | 181 | 206 |
Average | 2.29 | 1.25 | 2.51 | 2.86 |
和第二次作业相比,主要多了一个Unit.adjustSin()这个复杂度高的方法,这里是对\(sin()\)里面的Poly进行调整,提取负号。这里涉及的判断比较多。
类复杂度分析
class | OCavg | OCmax | WMC |
---|---|---|---|
CusFunc | 1.66 | 3 | 5 |
CustomFunc | 1.50 | 2 | 3 |
Expr | 2.00 | 4 | 6 |
Factor | 1.14 | 2 | 8 |
Lexer | 2.40 | 5 | 12 |
MainClass | 3.00 | 3 | 3 |
Number | 4.00 | 5 | 8 |
Parser | 3.28 | 8 | 23 |
Poly | 3.14 | 8 | 44 |
SumFunc | 1.66 | 5 | 10 |
Term | 1.25 | 2 | 5 |
TriFunc | 2.00 | 4 | 6 |
Unit | 3.20 | 9 | 48 |
Total | 181 | ||
Average | 2.51 | 4.62 | 13.92 |
Number、Parser、Poly、Unit类的平均复杂度比较高,
类图
和第二次作业的类图基本完全一样,只有Poly和Unit进行了少量改动。
Bug分析
强测中正确性通过了所有测试点,获得了95.553分。
互测中被Hack了1个同质Bug,处理\(sum()\)的时候上下界范围溢出int。
Hack了三位同学的共8个bug(五个同质Bug):
1、和自己的Bug一样
2、\(sin((-x))\)没有加括号
3、括号嵌套时碰到指数解析错误
4、没有处理\(sum()\)中是\(i**k\)的情况
5、\(sum()\)中碰到\(i**k\)且\(i\)是负数,处理成\(-i**k\)
测试策略
自动化测试
根据形式化描述,很容易写出一个简陋的造数据的程序,使用一些参数对产生的数据进行控制。
可以使用Python的sympy库,看输出和输入表达式是否等价。
Hack策略
实际上每个人的代码量都不小,都认真读一遍是很难做到的。
在Hack的时候,我采用的是自动和手动结合的形式。
自动就是用自动测试程序把每个人程序都跑几千组数据。基本能查出那些比较明显的Bug。
手动是预先构造一些覆盖可能造成Bug的数据,或者根据题目的数据范围构造边界数据,针对这些数据测试。
然后随机挑选几份代码细看,主要查看输入处理和输出化简的部分(中间处理如果有Bug,很容易在自动测试发现),检查是否存在Bug。
这样的策略还是比较有效的,在三次互测中把能找到的Bug都找到了。(但自己的Bug没有及时找出来,是因为没有正确理解题目条件,在互测时才发现)
总结
架构分析
在第一次作业时,如果使用栈或者正则表达式,也可以很好地完成,但是扩展会比较麻烦。相比之下,第一次训练提供的递归下降的方法就有很强的扩展性。
使用多项式来存储结果也是比较明智的选择,避免了合并同类项的麻烦。
第二次作业,加入了三角函数、自定义函数和求和函数,三角函数意味着存储需要改进,而自定义函数和求和函数主要是要解决替换变量的问题。
一种暴力的方法是直接字符串替换,但是这样扩展性也很低,并且很不面向对象。所以我使用变量到因子的映射递归下去替换。
在这样的设计下,第三次作业实际上十分简单了,基础功能都已经实现了,只需要继续改进“多项式”的存储方法即可。
三次作业中我几乎没有重构代码,都是一步步迭代开发的,整体还算比较顺利。
心得体会
本次作业是我在面向对象程序设计方面的第一次尝试,尽管程序的很多地方看起来还是面向过程的,但我学到了很多面向对象的思想和Java语言的特性和技巧。
真正的面向对象,应该要对于每个运算符、每个表达式等都建立一个对象,通过对象的状态得到结果。在我的代码中还是有所欠缺。
要写出这样的大型代码,架构是非常重要的。在前两次作业中,我都花了1到2天进行构思,然后才开始写,实际写代码的时间不长。因此,我对自己的架构还是比较满意的,这也为我第三次作业的快速通关奠定了基础。
作业中题目描述也比较复杂,并且在迭代过程中数据要求有变化,需要先仔细阅读题目细节。我第三次作业中就没有仔细阅读,导致出现了一个小Bug。
在交流中我发现一些同学在化简方面下了很大的功夫,使用搜索等方法找到长度最短的结果,我十分佩服。不过也有同学弄巧成拙,在优化过后正确性出现了问题。
在总结的理论课上,老师提到要建立回归测试集,就是每改一次就要把之前测试过的数据都测一遍。我非常认同这样的做法,这样也可以最大程度避免优化后正确性出错。