#OO 第一单元总结
本文为OO第一单元总结,本单元主要任务是表达式的展开与化简。核心思想是递归下降法,主要分为预处理、解析、多项式生成、化简四个个核心步骤。三次作业增量迭代的要求如下
-
第一次作业
- 去括号
- 一层括号
- 多项式化简
- 去括号
-
第二次作业
- 括号嵌套
- 自定义函数
- 无递归定义,有递归调用
- 新增三角函数因子
-
第三次作业
-
自定义函数
- 递归声明与调用
-
新增求导因子
- 仅出现一次
-
一、程序结构
1.类图(第三次作业)
2.代码结构
2.1代码行数
Source File | Total Lines | Source Code Lines | Source Code Line[%] | Comment Lines | Comment Lines[%] | Blank Lines | Blank Lines[%] |
---|---|---|---|---|---|---|---|
Factor.java | 22 | 17 | 0.7727272727272727 | 0 | 0.0 | 5 | 0.22727272727272727 |
Deri.java | 24 | 19 | 0.7916666666666666 | 0 | 0.0 | 5 | 0.20833333333333334 |
Main.java | 28 | 27 | 0.9642857142857143 | 0 | 0.0 | 1 | 0.03571428571428571 |
Num.java | 32 | 27 | 0.84375 | 0 | 0.0 | 5 | 0.15625 |
Expr.java | 39 | 32 | 0.8205128205128205 | 0 | 0.0 | 7 | 0.1794871794871795 |
Var.java | 44 | 39 | 0.8863636363636364 | 0 | 0.0 | 5 | 0.11363636363636363 |
Func.java | 45 | 41 | 0.9111111111111111 | 0 | 0.0 | 4 | 0.08888888888888889 |
Term.java | 48 | 42 | 0.875 | 0 | 0.0 | 6 | 0.125 |
Power.java | 54 | 47 | 0.8703703703703703 | 0 | 0.0 | 7 | 0.12962962962962962 |
Lexer.java | 66 | 58 | 0.8787878787878788 | 0 | 0.0 | 8 | 0.12121212121212122 |
Poly.java | 77 | 69 | 0.8961038961038961 | 0 | 0.0 | 8 | 0.1038961038961039 |
Cos.java | 98 | 90 | 0.9183673469387755 | 0 | 0.0 | 8 | 0.08163265306122448 |
Sin.java | 102 | 95 | 0.9313725490196079 | 0 | 0.0 | 7 | 0.06862745098039216 |
Parser.java | 151 | 140 | 0.9271523178807947 | 0 | 0.0 | 11 | 0.0728476821192053 |
StrHandler.java | 245 | 228 | 0.9306122448979591 | 1 | 0.004081632653061225 | 16 | 0.0653061224489796 |
Mono.java | 315 | 295 | 0.9365079365079365 | 0 | 0.0 | 20 | 0.06349206349206349 |
###2.2方法
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Cos.Cos() | 0 | 1 | 1 | 1 |
Cos.derivation(String) | 5 | 3 | 4 | 4 |
Cos.getIndex() | 0 | 1 | 1 | 1 |
Cos.getPoly() | 8 | 1 | 6 | 6 |
Cos.setIndex(String) | 0 | 1 | 1 | 1 |
Cos.setParameter(Factor) | 4 | 1 | 4 | 4 |
Deri.derivation(String) | 0 | 1 | 1 | 1 |
Deri.getPoly() | 0 | 1 | 1 | 1 |
Deri.setExp(Factor) | 0 | 1 | 1 | 1 |
Deri.setFac(String) | 0 | 1 | 1 | 1 |
Expr.Expr() | 0 | 1 | 1 | 1 |
“Expr.addTerm(Term, int)” | 0 | 1 | 1 | 1 |
Expr.derivation(String) | 2 | 1 | 3 | 3 |
Expr.getPoly() | 2 | 1 | 3 | 3 |
Factor.derivation(String) | 0 | 1 | 1 | 1 |
Factor.getPoly() | 0 | 1 | 1 | 1 |
Factor.getSign() | 0 | 1 | 1 | 1 |
Factor.setSign(int) | 0 | 1 | 1 | 1 |
“Func.funcStruct(String, HashMap<String, Func>)” | 2 | 1 | 3 | 3 |
Func.getContent(String[]) | 1 | 1 | 2 | 2 |
Func.getSum() | 0 | 1 | 1 | 1 |
Lexer.Lexer(String) | 0 | 1 | 1 | 1 |
Lexer.getCurToken() | 0 | 1 | 1 | 1 |
Lexer.getNum() | 2 | 1 | 3 | 3 |
Lexer.getPos() | 0 | 1 | 1 | 1 |
Lexer.isSign() | 4 | 3 | 3 | 5 |
Lexer.next() | 9 | 2 | 3 | 8 |
Main.main(String[]) | 2 | 1 | 3 | 3 |
Mono.Mono() | 0 | 1 | 1 | 1 |
Mono.Mono(Mono) | 0 | 1 | 1 | 1 |
“Mono.addCos(String, BigInteger)” | 2 | 1 | 2 | 2 |
Mono.addSame(Mono) | 6 | 1 | 7 | 7 |
“Mono.addSin(String, BigInteger)” | 2 | 1 | 2 | 2 |
Mono.addTris(Mono) | 2 | 1 | 3 | 3 |
Mono.clearHash() | 0 | 1 | 1 | 1 |
Mono.compareTo(Mono) | 0 | 1 | 1 | 1 |
“Mono.getPrint(int, int)” | 28 | 3 | 20 | 22 |
Mono.isFang(Mono) | 34 | 9 | 15 | 15 |
Mono.isSame(Mono) | 4 | 3 | 2 | 4 |
Mono.isSameFang(Mono) | 2 | 2 | 1 | 2 |
Mono.mulMono(Mono) | 8 | 1 | 7 | 7 |
Mono.printTri(int) | 47 | 1 | 17 | 17 |
“Mono.setMono(BigInteger, BigInteger, BigInteger, BigInteger, int)” | 0 | 1 | 1 | 1 |
Mono.turnNeg() | 0 | 1 | 1 | 1 |
Num.Num(String) | 0 | 1 | 1 | 1 |
Num.derivation(String) | 0 | 1 | 1 | 1 |
Num.getPoly() | 0 | 1 | 1 | 1 |
Parser.Parser(Lexer) | 0 | 1 | 1 | 1 |
Parser.paresDer(int) | 0 | 1 | 1 | 1 |
Parser.paresSin(int) | 1 | 2 | 2 | 2 |
Parser.parseCos(int) | 1 | 2 | 2 | 2 |
Parser.parseExpr() | 7 | 1 | 4 | 5 |
Parser.parseFac() | 15 | 8 | 11 | 11 |
Parser.parseTerm() | 1 | 1 | 2 | 2 |
Poly.Poly() | 0 | 1 | 1 | 1 |
Poly.addMono(Mono) | 10 | 5 | 5 | 6 |
Poly.addPoly(Poly) | 1 | 1 | 2 | 2 |
Poly.getPrint() | 3 | 1 | 2 | 3 |
Poly.mulPoly(Poly) | 3 | 1 | 3 | 3 |
Poly.turnNeg() | 1 | 1 | 2 | 2 |
“Power.Power(Factor, String)” | 0 | 1 | 1 | 1 |
Power.derivation(String) | 4 | 2 | 3 | 3 |
Power.getPoly() | 2 | 2 | 2 | 3 |
Sin.Sin() | 0 | 1 | 1 | 1 |
Sin.derivation(String) | 5 | 3 | 4 | 4 |
Sin.getIndex() | 0 | 1 | 1 | 1 |
Sin.getPoly() | 8 | 1 | 6 | 6 |
Sin.setIndex(String) | 0 | 1 | 1 | 1 |
Sin.setParameter(Factor) | 9 | 1 | 6 | 6 |
StrHandler.findBracket(String) | 7 | 5 | 1 | 5 |
“StrHandler.funcHandle(String, HashMap<String, Func>)” | 5 | 1 | 3 | 5 |
StrHandler.isNum(String) | 5 | 4 | 1 | 5 |
StrHandler.isTurnNeg(String) | 9 | 1 | 1 | 9 |
StrHandler.noBracket(String) | 12 | 2 | 1 | 9 |
StrHandler.noSign(String) | 3 | 3 | 3 | 4 |
StrHandler.noSignNum(String) | 8 | 4 | 3 | 8 |
StrHandler.noZero(String) | 3 | 2 | 2 | 4 |
“StrHandler.parseFunc(String, HashMap<String, Func>)” | 12 | 1 | 5 | 7 |
StrHandler.strHandler(String) | 17 | 1 | 4 | 8 |
“StrHandler.strHandler(String, HashMap<String, Func>)” | 17 | 1 | 4 | 8 |
Term.Term() | 0 | 1 | 1 | 1 |
Term.addFac(Factor) | 0 | 1 | 1 | 1 |
Term.derivation(String) | 7 | 1 | 5 | 5 |
Term.getPoly() | 2 | 1 | 3 | 3 |
Var.Var(String) | 0 | 1 | 1 | 1 |
Var.derivation(String) | 2 | 1 | 2 | 2 |
Var.getPoly() | 3 | 1 | 4 | 4 |
Total | 359 | 136 | 247 | 307 |
Average | 4.13 | 1.56 | 2.84 | 3.53 |
###2.3类
Class | OCavg | OCmax | WMC |
---|---|---|---|
Cos | 2.83 | 6 | 17 |
Deri | 1.00 | 1 | 4 |
Expr | 2.00 | 3 | 8 |
Factor | 1.00 | 1 | 4 |
Func | 2.00 | 3 | 6 |
Lexer | 2.50 | 7 | 15 |
Main | 3.00 | 3 | 3 |
Mono | 4.50 | 17 | 72 |
Num | 1.00 | 1 | 3 |
Parser | 3.00 | 9 | 21 |
Poly | 2.83 | 6 | 17 |
Power | 2.33 | 3 | 7 |
Sin | 3.17 | 6 | 19 |
StrHandler | 5.18 | 7 | 57 |
Term | 2.50 | 5 | 10 |
Var | 2.33 | 4 | 7 |
Total | 270 | ||
Average | 3.10 | 5.12 | 16.88 |
二、核心架构
1.表达式
2.多项式
3.数学公式
r e s u l t P o l y = ∑ i = 0 n ∏ j = 0 m f a c t o r P o l y resultPoly = \sum_{i=0}^n \prod_{j=0}^m factorPoly resultPoly=i=0∑nj=0∏mfactorPoly
4.架构设计体验
第一次作业
第一次作业比较简单,但由于考虑到之后的迭代开发,还是选择了递归下降,此时的代码可以处理括号嵌套的表达式。预处理包括:去除多余的正负号仅保留必要的符号、去除空白符和制表符、将**替换为^;词法语法解析的整体思路参考了练习作业提供的代码;化简在计算的过程中进行,Mono的标准形式为 Mono = axiyjzk,在单项式计算的时候采用深克隆
第二次作业
第二次迭代增加了括号嵌套、自定义函数、三角函数因子。递归下降已经解决括号嵌套;对于自定义函数将其放到预处理中,也用递归下降先对自定义函数进行表达式化简,之后替换;在Mono中添加两个HashMap<String, index>分别记录三角函数的参数与指数,Mono的标准形式变为: M o n o = a x i ∗ y j ∗ z k ∏ i = 0 s i n ∏ i = 0 c o s Mono = ax^{i}*y^{j}*z^{k} \prod_{i=0} sin \prod_{i=0} cos Mono=axi∗yj∗zk∏i=0sin∏i=0cos。判断同列项(除了常数)去掉常数项后的字符串比较,为了保证sin(x+y)和sin(y+x)能够化简,在Poly的getPrint方法中,需要先对mono实现comparable接口重写比较方法。
第三次作业
第三次迭代增加了自定义函数的递归调用、求导因子。自定义函数只需要在预处理的过程中像处理第二次表达式的方式处理即可;求导因子我们就当做新加因子,在表达式getPoly的时候处理,利用求导法则分别写出Expr、Term、其他因子的求导方法即可。
由于第二次优化没有做多少,所以第三次最主要是两个优化,sin(y-x)+sin(x-y)和sin2+cos2。第一个比较好做,只需要在打印三角函数参数的时候依据正负号的数量和字符串比较来实现;第二个优化类似同列项判断,在Mono相加的时候遍历两个三角函数HashMap,判断另一个Mono中去掉相应的项之后的是否为同类项。此时便会出现新情况,例如:sin(x)*cos(x)+sin(x)3*cos(x)+cos(x)3*sin(x) 化简时出现新的同类项,对此我的做法是递归化简(乱起的名),以上述为例,先将后两项相加得到Mono:sin(x)cos(x),之后在Poly中删除刚刚化简得到的Mono,再将该Mono加入Poly中,发现同类项合并得到2sin(x)cos(x), 重复上述过程,删除2sin(x)*cos(x),再加到Poly中发现没有同类项,到此合并完成。
5.架构的优缺点
-
优点
- 求导因子,在求导因子生成多项式的时候再处理,可以复用之前的Poly相乘和相加的方法,且不会涉及深浅克隆问题
- 三角函数存储方式为String,这样优点很明显,比如仅需要调用一次内层因子的打印函数
- 边计算边化简,递归化简会比单独写一个化简函数方便,也更符合程序的流程
- 可以处理求导因子的嵌套
-
缺点
- 对于自定义函数应该也当做因子处理,在解析的过程中替换,而不是在预处理中替换
- 由于父类Factor拥有属性sign,但是在解析中真正有正负的只有Num和Term,其他的都默认为符号为1。在我的架构中埋下了隐患,这会在下面谈到
三、Bug
- 第一次
- 互测竟然没有挨刀,在系数为1的时候,特判条件太多,造成漏洞,1 * y * z输出yz
- 0-x+x没有输出,这是因为-x+x合并完之后产生新的同类项,但是这个在互测和强测都没有测出来,自己也没注意到
- 第二次
- 1-x+x,哎,第一次作业埋的雷
- sin(0) ** 0=0 在判断的时候先判断了sin参数是否为0
- x**21 输出为x* * x1 直接对Mono输出的字符串直接进行替换
- 第三次
- dx(sin((-x))) 输出cos(x) 承接上文我的架构,由于我的sin因子里有Factor属性,Poly属性(Factor生成),在提负号到外面的时候只改变了Poly的符号(将所有Mono变号),但是没有把Factor变号。就算变号,由于(-x)是表达式因子,而在我的架构中表达式默认符号为正
- 总结出现的bug
- if特判条件,能否把所有的情况都考虑到,是否会漏到一些情况,应该优点判断那些情况
- 直接对字符串处理是一件愚蠢至极的方法
- 迭代产生的bug,大多数都是复用之前的方法导致错误
四、Hack策略
列举一些hack成功的例子
-
边界条件
-
sin((-x)) --> sin(-x)
-
1 f(x)=cos(cos(cos(cos(x)))) sin(sin(cos(cos(cos(cos(cos(cos(dx(f(x)))))))))) --> TLE 利用cost条件实现TLE
-
-
求导因子出现的位置,对特殊的因子求导
-
1 f(x,y) = ((+x))**2+y -dx(f(y,0))+y
-
2 f(x,y,z)=x-(y+z) g(x,z,y)=-dz((y-z)**2) -(f(0,-1,+1)-g(x**+0,x**+2,x**01))
-
2 f(x) = sin(cos(x)) g(y ) = dy(f(y)) g(y)
五、心得体会
在第一次和第二次我几乎都没怎么做测试,第三次测试也只是把第二次数据改了改添加了求导因子,然后和室友python对拍,结果就是三次作业都有bug,下周说什么也得把评测机写出来。在写代码的时候尽量保证程序正义性,再结合随机测试数据和评测机才是正道!!
复用之前的方法很容易出bug,接口的实现尤为重要,对于每个方法都需要清楚知道输入限制及方法的作用,迭代之后是否还能接着用。慢工出细活,写OO还是得抽出连续大量的时间,且要长时间专注。递归下降确实好用,但就是感觉缺少了一点面向对象的思想,第一单元感觉就像是数据结构哈哈哈哈。感觉评测没有上学期的OOpre覆盖性强,一个点就好几千上万条数据,bug还得靠互测。