”望涔阳兮极浦,横大江兮扬灵。“
BUAA 面向对象 第一单元 总结
前言
不要再为Unit1结束而悲伤了,马上到达的是Unit2,本次总结将从以下部分展开:
-
程序结构分析与迭代架构
-
程序测试方法及BUG分析
-
优化策略
-
反思与总结
好,oo总结,启动!
程序结构分析与迭代架构
HW1
作业要求:
读入一个包含加、减、乘、乘方以及括号(其中括号的深度至多为 1 层)的单变量表达式,输出恒等变形展开所有括号后的表达式。
基本思路:
第一次作业本质上是对读入的表达式解析后运算。
解析方法笔者采用课程组推荐的递归下降法,如下图,其中Factor包含常数、幂函数(底数可以是任意因子)因子,表达式因子:
即对读入的母表达式Expression,进行语法分析(Parser)后得到若干Term的Arraylist,然后对Term逐个Parser得到Factor的Arraylist。如果最后Factor作为表达式因子出现,则进行递归解析。虽然递归下降看起来比较复杂高深,一开始可能无从下手,但实际上手写一个ParserExpr(对表达式解析,笔者这里采取了do while的循环结构),便可以写出ParserTerm,然后对Factor进行分析,如此大体框架就开发好了。
运算方法在HW1中,仅仅涉及幂函数的加减乘三种运算,于是笔者采用了指数到系数的映射Hashmap<String, String>
(此处为HW2的巨大重构埋下伏笔orz)。对解析后的母表达式进行解析后,运算便十分简单了,就是进行
∑
i
n
T
e
r
m
[
i
]
\sum_{i}^{n} Term[i]
∑inTerm[i] 进行Key的比较,Value相加减,以及
∏
i
n
F
a
c
t
o
r
[
i
]
\prod_{i}^{n} Factor[i]
∏inFactor[i] 进行Key相加,Value相乘。
UML图:
类名称 | 类设计思考 | 优缺点分析 |
---|---|---|
Factor | 作为因子父类 | 这一父类在HW1中冗余(未有实例) |
Const | 常数因子,继承自Factor | 字符串表示数字,完全可以使用BigInteger |
Pow | 幂函数因子,继承自Factor,此时底数是传入底数的字符串表达式 | 底数的数据类型需要再考虑,后续迭代十分困难 |
Expr | 表达式因子,继承自Factor,内有Term的容器 | / |
Term | 项,内有Factor的容器 | / |
Lexer | 词法分析器,可取得当前字符以及移动位置 | / |
Parser | 语法分析器,处理递归下降以及因子识别 | / |
Calculator | 进行多项式乘法、加法以及减法,获取最终输出,简化表达式 | 考虑将输入、输出、计算分开,不应过度耦合 |
Main | 主类 | / |
复杂度分析:
方法复杂度:
Method | ev(G)基本复杂度 | iv(G)模块设计复杂度 | v(G)圈复杂度 |
---|---|---|---|
Calculator.getAddExpression(String, String) | 4.0 | 6.0 | 7.0 |
Calculator.getDivided(String) | 1.0 | 5.0 | 5.0 |
Calculator.getoutput(HashMap) | 6.0 | 12.0 | 13.0 |
Calculator.merge(String[], HashMap) | 1.0 | 2.0 | 2.0 |
Calculator.multiple(String, String) | 1.0 | 7.0 | 7.0 |
Calculator.pairMul(String[], String[]) | 1.0 | 1.0 | 1.0 |
Calculator.shorten(String) | 1.0 | 13.0 | 13.0 |
Calculator.simplify(String) | 1.0 | 6.0 | 6.0 |
Const.Const(BigInteger) | 1.0 | 1.0 | 1.0 |
Const.getExpression() | 1.0 | 1.0 | 1.0 |
Expr.addTerm(Term, boolean) | 1.0 | 1.0 | 1.0 |
Expr.Expr() | 1.0 | 1.0 | 1.0 |
Expr.getExpression() | 1.0 | 2.0 | 2.0 |
Factor.getExpression() | 1.0 | 1.0 | 1.0 |
Factor.getNegative() | 1.0 | 1.0 | 1.0 |
Factor.setNegative(boolean) | 1.0 | 1.0 | 1.0 |
Lexer.backward() | 1.0 | 1.0 | 2.0 |
Lexer.forward() | 1.0 | 1.0 | 2.0 |
Lexer.getConst() | 3.0 | 6.0 | 7.0 |
Lexer.getLength() | 1.0 | 1.0 | 1.0 |
Lexer.getPeek() | 2.0 | 2.0 | 2.0 |
Lexer.getPosition() | 1.0 | 1.0 | 1.0 |
Lexer.Lexer(String) | 1.0 | 1.0 | 1.0 |
Main.main(String[]) | 1.0 | 1.0 | 1.0 |
Parser.Parser(Lexer) | 1.0 | 1.0 | 1.0 |
Parser.parserExpr() | 1.0 | 5.0 | 5.0 |
Parser.parserFactor() | 6.0 | 9.0 | 9.0 |
Parser.parserTerm() | 1.0 | 6.0 | 6.0 |
Pow.getExpression() | 3.0 | 4.0 | 5.0 |
Pow.Pow(String, BigInteger) | 1.0 | 1.0 | 1.0 |
Term.addFactor(Factor, boolean) | 1.0 | 1.0 | 1.0 |
Term.getExpression() | 1.0 | 3.0 | 4.0 |
Term.setNegative(boolean) | 1.0 | 1.0 | 1.0 |
Term.Term() | 1.0 | 1.0 | 1.0 |
Total | 52.0 | 107.0 | 114.0 |
Average | 1.5294117647058822 | 3.1470588235294117 | 3.3529411764705883 |
注意到Calculator.getoutput(HashMap)
和Calculator.shorten(String)
这两个方法的复杂度较高,这两个作为最后的输出和简化长度函数,需要整体遍历因子以及未处理的字符串,导致递归以及循环次数过多,这也是后续HW3、HW2导致TLE问题的关键原因。
代码规模:485行
HW2
作业要求:
在HW1基础上,实现括号嵌套、exp()指数因子以及自定义函数。
基本思路:
在HW2中,需要加入对自定义函数的翻译以及对指数运算的支持。
对于自定义函数的翻译,笔者通过字符串解析,将变量x,y,z映射到因子,将自定义函数的表达式视作表达式因子,然后吸取递归下降的方法, 递归终点为”x“或者常数,此时只需要将x(指数因子)进行换底即可。
但对指数运算的支持,由于在HW1中采用了Hashmap<String, String>
这种简单粗暴的表达方式。导致在加入指数运算后,不得不重构。考虑到最终表达式为
[
a
∗
]
[
e
e
x
p
r
e
s
s
i
o
n
]
[
∗
x
[
b
]
[a*][e^{expression}][*x[^b]
[a∗][eexpression][∗x[b] 的形式,我最后采用了更加简单粗暴的方法:Hashmap<Factor,TrieMap<BigInteger, BigInteger>>
将exp指数作为Hashmap
的Key,然后将幂函数表达式作为Value,幂函数表达式同HW1一样处理。
在对数据结构进行重构后,需要对乘法、加减法的运算进行重构和迭代,由于Factor实质上是指针,这导致Factor插入的时,如果不是以常数因子0(作为指数因子不存在的Key),必然作为全新的Key插入Hashmap
(这导致了TLE的出现,在后续会讨论解决方法)。加减乘法就是两个Hashmap
的合并问题,这个在HW1中以及实现过,即对Key进行加法即可。(在TLE了强测第2个点后,发现是在运算时候没有及时进行删“0”的操作,即TrieMap
的Value为0时需要立即删除,在考虑到这个后,就不会TLE了)
UML图:
类名称 | 类设计思考 | 优缺点分析 |
---|---|---|
Factor | 作为因子父类 | 此时Factor作为’x’,‘y’,'z’的实例,但仍感觉可以和Pow合并 |
Const | 常数因子,继承自Factor | / |
Pow | 幂函数因子,继承自Factor,此时底数是传入底数的字符串表达式 | 底数的类型现在只有Expr和Factor,常常因为底数是Expr使递归层数很高 |
EXtype | 指数因子,实际上可以视作另一形式的Pow | 与Pow一样,递归层数在计算时候很高,区别是一个为加法,一个为乘法。 |
Expr | 表达式因子,继承自Factor,内有Term的容器 | / |
Term | 项,内有Factor的容器 | / |
Lexer | 词法分析器,可取得当前字符以及移动位置 | / |
Parser | 语法分析器,处理递归下降以及因子识别 | / |
Calculator | 进行多项式乘法、加法以及减法 | 采用的计算方法复杂度很高,影响运行时间 |
Input | 进行输入解析,输入简化 | Input与Output在缩短符号时候共用一个方法,或许该方法可以同时在这两个类中移除。 |
Output | 进行字符串输出,同类项合并,字符串缩短 | 同上 |
Main | 主类 | / |
复杂度分析:
方法复杂度:
Method | Cog© | ev(G)基本复杂度 | iv(G)模块设计复杂度 |
---|---|---|---|
Calculator.Add(HashMap>, HashMap>) | 1.0 | 6.0 | 7.0 |
Calculator.del(HashMap>) | 1.0 | 4.0 | 4.0 |
Calculator.FactorAddFactor(Const, Const) | 1.0 | 1.0 | 1.0 |
Calculator.FactorAddFactor(Expr, Expr) | 1.0 | 3.0 | 3.0 |
Calculator.FactorAddFactor(Factor, Factor) | 5.0 | 9.0 | 9.0 |
Calculator.Mul(HashMap>, HashMap>) | 1.0 | 3.0 | 3.0 |
Calculator.TreeAddTree(HashMap, HashMap) | 1.0 | 4.0 | 4.0 |
Calculator.TreeMulTree(HashMap, HashMap) | 1.0 | 4.0 | 4.0 |
Const.add(Const, boolean) | 1.0 | 2.0 | 2.0 |
Const.Const(BigInteger) | 1.0 | 1.0 | 1.0 |
Const.getExpression() | 1.0 | 1.0 | 1.0 |
Const.getNumber() | 1.0 | 1.0 | 1.0 |
Const.getType() | 1.0 | 1.0 | 1.0 |
Expr.addTerm(Term, boolean) | 1.0 | 1.0 | 1.0 |
Expr.Expr() | 1.0 | 1.0 | 1.0 |
Expr.Expr(ArrayList) | 1.0 | 1.0 | 1.0 |
Expr.getExpression() | 1.0 | 2.0 | 2.0 |
Expr.getTerms() | 1.0 | 1.0 | 1.0 |
Expr.getType() | 1.0 | 1.0 | 1.0 |
Expr.mergeTerm() | 5.0 | 8.0 | 8.0 |
Expr.renew(HashMap) | 1.0 | 2.0 | 2.0 |
EXtype.EXtype(Pow) | 1.0 | 1.0 | 1.0 |
EXtype.getExpr() | 1.0 | 3.0 | 3.0 |
EXtype.getExpression() | 1.0 | 1.0 | 1.0 |
EXtype.getType() | 1.0 | 1.0 | 1.0 |
EXtype.isEquivalent(Factor) | 1.0 | 1.0 | 1.0 |
EXtype.renew(HashMap) | 1.0 | 1.0 | 1.0 |
Factor.add(Const, boolean) | 1.0 | 1.0 | 1.0 |
Factor.getEle() | 1.0 | 1.0 | 1.0 |
Factor.getExpression() | 1.0 | 2.0 | 2.0 |
Factor.getNegative() | 1.0 | 1.0 | 1.0 |
Factor.getType() | 1.0 | 1.0 | 1.0 |
Factor.isEquivalent(Factor) | 1.0 | 1.0 | 1.0 |
Factor.renew(HashMap) | 1.0 | 1.0 | 1.0 |
Factor.setEle(String) | 1.0 | 1.0 | 1.0 |
Factor.setNegative(boolean) | 1.0 | 1.0 | 1.0 |
Function.Function(ArrayList, ArrayList) | 1.0 | 2.0 | 2.0 |
Function.getExpr() | 1.0 | 1.0 | 1.0 |
Function.getExpression() | 1.0 | 1.0 | 1.0 |
Function.getType() | 1.0 | 1.0 | 1.0 |
Input.getExpression() | 1.0 | 1.0 | 1.0 |
Input.getFunction() | 1.0 | 2.0 | 2.0 |
Input.Input() | 1.0 | 1.0 | 1.0 |
Input.shorten(String) | 1.0 | 13.0 | 13.0 |
Lexer.backward() | 1.0 | 1.0 | 2.0 |
Lexer.forward() | 1.0 | 1.0 | 2.0 |
Lexer.getConst() | 3.0 | 6.0 | 7.0 |
Lexer.getLength() | 1.0 | 1.0 | 1.0 |
Lexer.getPeek() | 2.0 | 2.0 | 2.0 |
Lexer.getPosition() | 1.0 | 1.0 | 1.0 |
Lexer.Lexer(String) | 1.0 | 1.0 | 1.0 |
Main.main(String[]) | 1.0 | 1.0 | 1.0 |
Output.Consolidate(String) | 1.0 | 1.0 | 1.0 |
Output.getExpExpression(String, String, HashMap, BigInteger) | 1.0 | 11.0 | 14.0 |
Output.getMonomial(String, HashMap, BigInteger) | 2.0 | 8.0 | 10.0 |
Output.getOutPut(HashMap>) | 1.0 | 7.0 | 8.0 |
Output.shorten(String) | 1.0 | 13.0 | 13.0 |
Parser.getConstFactor(boolean) | 1.0 | 1.0 | 1.0 |
Parser.getExpressionFactor() | 2.0 | 3.0 | 3.0 |
Parser.getEXtypeFactor() | 2.0 | 1.0 | 2.0 |
Parser.getFunctionFactor() | 5.0 | 6.0 | 6.0 |
Parser.getPowFactor(boolean) | 1.0 | 2.0 | 2.0 |
Parser.Parser(Lexer, HashMap) | 1.0 | 1.0 | 1.0 |
Parser.parserExpr() | 1.0 | 5.0 | 5.0 |
Parser.parserFactor() | 8.0 | 13.0 | 13.0 |
Parser.parserTerm() | 1.0 | 6.0 | 6.0 |
Pow.getBase() | 1.0 | 1.0 | 1.0 |
Pow.getExponent() | 1.0 | 1.0 | 1.0 |
Pow.getExpression() | 1.0 | 5.0 | 5.0 |
Pow.getType() | 1.0 | 1.0 | 1.0 |
Pow.isEquivalent(Factor) | 1.0 | 1.0 | 1.0 |
Pow.Pow(Factor, BigInteger) | 1.0 | 1.0 | 1.0 |
Pow.renew(HashMap) | 1.0 | 4.0 | 4.0 |
Term.addFactor(Factor, boolean) | 1.0 | 1.0 | 1.0 |
Term.getExpression() | 1.0 | 3.0 | 4.0 |
Term.getFactors() | 1.0 | 1.0 | 1.0 |
Term.getNegative() | 1.0 | 1.0 | 1.0 |
Term.isEquivalent(Term) | 5.0 | 2.0 | 5.0 |
Term.merge(Term) | 1.0 | 3.0 | 3.0 |
Term.renew(HashMap) | 1.0 | 2.0 | 2.0 |
Term.setNegative(boolean) | 1.0 | 1.0 | 1.0 |
Term.Term() | 1.0 | 1.0 | 1.0 |
注意到在与获得输出相关的方法的复杂度较高,笔者分析,大概由于因为采用了解析表达式一致的递归下降方法,导致复杂度的急剧上升。这个也在HW3中导致TLE问题,现按下不表,会在HW3中分析解决方案。
代码规模:1008行
重构体验:
这一次HW2由于HW1中采用了不太好的数据类型,主要问题是Pow的底数类型~~~(HW1中采用String,有点抽象)~~~、计算多项式的表达方式、将因子拆开采用正则的方法,导致HW1中近一半的代码均需要重构。但重构下来,对于面向对象的理解,确实有所提高。对于一些方法,感受到可以不必拘泥于小方法的细节,可以先实现大方法,形成框架,然后逐步往里面填“肉”。
HW3
作业要求:
在HW2的基础上,实现求导算子,以及在自定义函数中实现定义时调用已有定义的函数。
基本思路:
对于求导算子,笔者的实现类似于自定义函数的实现,同样采用相似的递归下降方法。但不同的是,对于Term的求导,产生结果可能是一堆的Term(乘法求导导致),但无伤大雅。这个的实现十分简单,五十多行就可以解决。
对于实现自定义函数的迭代,这个在HW2中已经实现了,这里考虑到在一些测试样例下导致递归层数十分高,需要对表达式解析进行优化。在调试中观察到在HW2中,为了避免解析错误,笔者直接将函数中的因子,全部抽象为表达式因子,这是导致递归层数增加的最主要原因。(如下图所示)
所以,采用parserFactor方法进行函数解析。但这导致了HW2中未发现的Bug暴露,直接原因是原先Pow类中的底数因子不是单个的x就是一个表达式(不论是常数还是指数因子,都会被视作一个表达式因子),但此时Pow类的底数可以是任意的因子。这直接导致程序在函数因子解析的时候无法正确换底,导致bug的出现~~(被hack的时候,发现这个问题还没被完全修复)~~。UML图与HW2一致,不再贴出。
复杂度分析:
方法复杂度分析:
Calculator.Add(HashMap>, HashMap>) | 9.0 | 1.0 | 6.0 | 7.0 |
---|---|---|---|---|
Calculator.del(HashMap>) | 4.0 | 1.0 | 4.0 | 4.0 |
Calculator.FactorAddFactor(Const, Const) | 0.0 | 1.0 | 1.0 | 1.0 |
Calculator.FactorAddFactor(Expr, Expr) | 2.0 | 1.0 | 3.0 | 3.0 |
Calculator.FactorAddFactor(Factor, Factor) | 9.0 | 5.0 | 9.0 | 9.0 |
Calculator.Mul(HashMap>, HashMap>) | 3.0 | 1.0 | 3.0 | 3.0 |
Calculator.TreeAddTree(TreeMap, TreeMap) | 5.0 | 1.0 | 4.0 | 4.0 |
Calculator.TreeMulTree(TreeMap, TreeMap) | 7.0 | 1.0 | 4.0 | 4.0 |
Const.add(Const, boolean) | 2.0 | 1.0 | 2.0 | 2.0 |
Const.Const(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
Const.differentiate() | 0.0 | 1.0 | 1.0 | 1.0 |
Const.getExpression() | 0.0 | 1.0 | 1.0 | 1.0 |
Const.getNumber() | 0.0 | 1.0 | 1.0 | 1.0 |
Const.getType() | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.addTerm(Term, boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.differentiate() | 5.0 | 2.0 | 4.0 | 4.0 |
Expr.Expr() | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.Expr(ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.getExpression() | 1.0 | 1.0 | 2.0 | 2.0 |
Expr.getTerms() | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.getType() | 0.0 | 1.0 | 1.0 | 1.0 |
Expr.mergeTerm() | 17.0 | 5.0 | 8.0 | 8.0 |
Expr.renew(HashMap) | 1.0 | 1.0 | 2.0 | 2.0 |
EXtype.differentiate() | 2.0 | 2.0 | 2.0 | 2.0 |
EXtype.EXtype(Pow) | 0.0 | 1.0 | 1.0 | 1.0 |
EXtype.getExpr() | 3.0 | 1.0 | 3.0 | 3.0 |
EXtype.getExpression() | 0.0 | 1.0 | 1.0 | 1.0 |
EXtype.getIn() | 0.0 | 1.0 | 1.0 | 1.0 |
EXtype.getType() | 0.0 | 1.0 | 1.0 | 1.0 |
EXtype.isEquivalent(Factor) | 0.0 | 1.0 | 1.0 | 1.0 |
EXtype.renew(HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
EXtype.setIn(Factor) | 0.0 | 1.0 | 1.0 | 1.0 |
Factor.add(Const, boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
Factor.differentiate() | 0.0 | 1.0 | 1.0 | 1.0 |
Factor.getEle() | 0.0 | 1.0 | 1.0 | 1.0 |
Factor.getExpression() | 2.0 | 1.0 | 2.0 | 2.0 |
Factor.getNegative() | 0.0 | 1.0 | 1.0 | 1.0 |
Factor.getType() | 0.0 | 1.0 | 1.0 | 1.0 |
Factor.isEquivalent(Factor) | 0.0 | 1.0 | 1.0 | 1.0 |
Factor.renew(HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
Factor.setEle(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Factor.setNegative(boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
Function.Function(ArrayList, ArrayList, HashMap) | 1.0 | 1.0 | 2.0 | 2.0 |
Function.get() | 0.0 | 1.0 | 1.0 | 1.0 |
Function.getExpr() | 0.0 | 1.0 | 1.0 | 1.0 |
Function.getExpression() | 0.0 | 1.0 | 1.0 | 1.0 |
Function.getType() | 0.0 | 1.0 | 1.0 | 1.0 |
Input.getExpression() | 0.0 | 1.0 | 1.0 | 1.0 |
Input.getFunction() | 1.0 | 1.0 | 2.0 | 2.0 |
Input.Input() | 0.0 | 1.0 | 1.0 | 1.0 |
Input.shorten(String) | 18.0 | 1.0 | 13.0 | 13.0 |
Lexer.backward() | 1.0 | 1.0 | 1.0 | 2.0 |
Lexer.forward() | 1.0 | 1.0 | 1.0 | 2.0 |
Lexer.getConst() | 8.0 | 3.0 | 6.0 | 7.0 |
Lexer.getPeek() | 2.0 | 2.0 | 2.0 | 2.0 |
Lexer.Lexer(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Main.main(String[]) | 0.0 | 1.0 | 1.0 | 1.0 |
Output.Consolidate(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Output.getExpExpression(String, String, TreeMap, BigInteger) | 16.0 | 1.0 | 8.0 | 10.0 |
Output.getMonomial(String, TreeMap, BigInteger) | 16.0 | 2.0 | 8.0 | 10.0 |
Output.getOutPut(HashMap>) | 18.0 | 1.0 | 7.0 | 8.0 |
Output.getString(String) | 4.0 | 1.0 | 5.0 | 6.0 |
Output.shorten(String) | 18.0 | 1.0 | 13.0 | 13.0 |
Parser.getConstFactor(boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.getDifferentiation() | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.getExpressionFactor() | 3.0 | 2.0 | 3.0 | 3.0 |
Parser.getEXtypeFactor() | 2.0 | 2.0 | 1.0 | 2.0 |
Parser.getFunctionFactor() | 10.0 | 5.0 | 6.0 | 6.0 |
Parser.getPowFactor(boolean) | 2.0 | 1.0 | 2.0 | 2.0 |
Parser.Parser(Lexer, HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.parserExpr() | 5.0 | 1.0 | 5.0 | 5.0 |
Parser.parserFactor() | 14.0 | 9.0 | 14.0 | 14.0 |
Parser.parserTerm() | 8.0 | 1.0 | 6.0 | 6.0 |
Pow.differentiate() | 7.0 | 4.0 | 4.0 | 5.0 |
Pow.getBase() | 0.0 | 1.0 | 1.0 | 1.0 |
Pow.getEle() | 0.0 | 1.0 | 1.0 | 1.0 |
Pow.getExponent() | 0.0 | 1.0 | 1.0 | 1.0 |
Pow.getExpression() | 11.0 | 1.0 | 5.0 | 5.0 |
Pow.getType() | 0.0 | 1.0 | 1.0 | 1.0 |
Pow.isEquivalent(Factor) | 0.0 | 1.0 | 1.0 | 1.0 |
Pow.Pow(Factor, BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
Pow.renew(HashMap) | 5.0 | 1.0 | 6.0 | 6.0 |
Term.addFactor(Factor, boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
Term.differentiate() | 10.0 | 1.0 | 5.0 | 5.0 |
Term.getExpression() | 4.0 | 1.0 | 3.0 | 4.0 |
Term.getFactors() | 0.0 | 1.0 | 1.0 | 1.0 |
Term.getNegative() | 0.0 | 1.0 | 1.0 | 1.0 |
Term.isEquivalent(Term) | 6.0 | 5.0 | 2.0 | 5.0 |
Term.merge(Term) | 5.0 | 1.0 | 3.0 | 3.0 |
Term.renew(HashMap) | 1.0 | 1.0 | 2.0 | 2.0 |
Term.setNegative(boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
仍然注意到,由于重复遍历元素,在关于表达式输出Output类中以及表达式优化的方法复杂度极高。这时常在exp嵌套层数高的时候,导致TLE,仍有极大的优化空间。
代码规模:1136行,可以看到改动相比于第一次迭代少了很多很多。
程序测试方法及BUG分析
程序测试方法
在HW1中,感谢czx和Chatgpt的帮助从而生成一台自动评测机,在测试过程中,采用纯random的数据生成方式:
在HW2和HW3中,感谢DPO OJ V2网站的建设同学,评测平台确实能够帮助发现一些Bug。除了借助该平台之外,笔者也针对自己的程序,通过手动构造递归层数较高的测试样例,从而降低总体运行时间,避免tle。
在互测环节中,主要是结合本人在测试时发现的bug捏数据,比如HW1中捏0,HW2中exp的指数为0的情况进行hack。
BUG分析
自己的BUG
1.在HW2中,由于递归层数过高导致第二个点TLE,并且在互测中由于对表达式输出的理解错误,导致format error
2.在HW3中,对形如 f ( x ) = g ( x 2 ) f(x) = g(x^2) f(x)=g(x2) 的函数定义式,由于在优化运行时间是漏判(这很不oo),导致在互测中被刀了一次。
互测中他人的BUG
1.在HW1中,有同学因为对幂函数底数的解析有误,导致(x+0)^2
会输出x^2+1
2.在HW2中,主要是exp的0次会出现一些bug
优化策略
在三次作业中,优化主要分为两个方面:运行时间优化、输出长度优化
运行时间优化:
对于笔者架构,其主要问题是递归层数过高导致TLE问题。显而易见,计算/输出一个表达式因子的递归深度远深于一个等价的其他类型因子,即对函数中因子、幂函数底数、指数进行精准分类,会大大降低运行时间。所以,在HW2到HW3中,针对HW2中无脑采用parserExpr将嵌套的因子一概而论地视作表达式因子,笔者进行了细化分类,从而大大降低运行时间。
输出长度优化:
输出长度可以通过合并同类项、提取(最优的)公因子、符号调整等方面考虑。由于笔者实在愚钝,为了避免因为优化导致WA,所以仅进行了合并同类项,具体实现是根据我的输出表达方式: a ∗ e e x p r e s s i o n ∗ x b a*e^{expression}*x^b a∗eexpression∗xb (如果a不存在,则对这个Term添加一个+1),从而实现Term的比较(逐个因子判断是否等价),但实际来看,由于expression中仍可能继续嵌套Term,此方法在部分情况下会失效,导致性能分极大丢失。
反思与总结
虽然上过oopre,但HW1点开的一瞬间,才感觉到真正的oo并不是pre中的“造玩具”行为。在HW1中,因为没有良好的架构,导致HW2中大量的重构。虽然有些难绷,但确实在重构的时候,才感受面向对象实质上是一种宏观的模型,即一个类要实现能做什么,而不是用冗长的代码实现怎么做,要实现宏观布局,而不能局限于某一小方法。
总体来说,oo第一单元的体验还是很好的,确实能够感受到自己编程思想上的转变。但是,对于代码性能分,我有一些自己的看法:个人认为可以如同五级制度一般进行分档,从而减少同学们(尤指我这样的懒人)在优化结果的内卷行为(比如将exp(2)提出来这种绝活哥掀起新的内卷浪潮)。
Unit 2 启动!