OO_UNIT1 单元总结
总体分析
hw_1 是对表达式的简单解析,hw_2 加入了多层阔号嵌套,指数函数(exp
) ,自定义函数(f
),hw_3 新增了自定义函数的嵌套和求导因子,以递归下降为主要的思路串通三次作业。
基于度量对程序结构的分析
unit1_hw_1
- UML 类图
设计遵从递归下降的方法,从 Lexer
和 Parser
的解析出发,构建以 Expr
->Term
->Factor
的表达式树,清晰地体现了递归下降的思想,有比较强的可迭代性和内聚性。
- 具体方法复杂度
再结合对具体方法复杂度的分析,可以发现 Expr
和 Term
中对表达式和项的解析过程尤其是
P
a
r
s
e
r
.
p
a
r
s
e
T
e
r
m
Parser.parseTerm
Parser.parseTerm和
T
e
r
m
.
a
d
d
F
a
c
t
o
r
Term.addFactor
Term.addFactor复杂度较高,递归调用多,容易造成进程的阻塞。这是由于这其中涉及到表达式的展开与重新组成,有较长的循环和递归的调用,导致最终CogC
(圈复杂度)较高。
unit1_hw_3
- UML 类图
第三次作业相比第二次作业迭代的不多,故将二三次作业合并阐述。第二次作业新添了指数函数和自定义函数调用,故增加了Exp
和Func
类,并提前在Main
中加入了对Func
的调用,方便后续进行自定义函数在最终表达式中的替换。重新回顾 hw_2 的需求,可以发现括号多层嵌套已经在递归下降的过程中天然实现了,而自定义函数的调用也已经实现,那么如何处理指数函数呢,可以发现的是指数函数很显然来自Factor
这个接口,直接一并实现即可,最后用
H
a
s
h
m
a
p
<
I
n
t
,
H
a
s
h
m
a
p
<
E
x
p
r
,
I
t
e
m
>
>
p
o
l
y
Hashmap<Int, Hashmap<Expr, Item>> poly
Hashmap<Int,Hashmap<Expr,Item>>poly 来表示最后的基本项以便最后进行合并即可,这个 HashMap
的意思是最终表达
a
∗
x
b
∗
e
x
p
E
x
p
r
a * x ^ {b} * exp ^ {Expr}
a∗xb∗expExpr 的基本项。而 hw_3 对于自定义函数的嵌套已经在递归下降中实现,我们只要关注求导,由于求导作用于所有的因子中,我们只需要在Factor
接口中定义derive()
函数并在各个因子类中重定义即可,这样在递归下降的过程中就会天然对所有的dx()
进行解析展开。
- 具体方法复杂度
架构设计体验
- 递归下降
将程序分为解析和计算合并两个部分,解析由Lexer
和Parser
和Term
类实现,按照形式化定义解析表达式,再对最终的Expr
进行化简。但是第一次作业时我的计算合并只是停留在字符串层面,并没有深化计算和类的思想,在第二次作业引入基本项之后,对Expr
的计算进行了类上和方法上的整合,即符合递归下降的思想,也使得对表达式树的计算变得方便简单。
- 迭代场景
新添三角函数。由于三角函数的拓展也只是新添因子种类,只需要拓展Factor
接口即可。
Bug
因为递归下降也就是对表达式的解析十分公式化出现 bug 的几率较低,我的程序主要出现两次 bug 分别是在化简最终表达式和追求最短表达式长度时考虑情况不完全,出现了负优化甚至是错误优化的情况,比如我为了优化1*
却造成了11*x
变成了1x
的错误,另一个错误是在对因子求导的过程出现深浅克隆考虑不清楚的状况,从而导致求导最终结果出现错误,也在求导途中就产生RE
的情况。
发现 Bug 的方法
主要是通过评测机的对拍发现错误。
学习心得
首先是掌握了递归下降的方法,其次是认识到良好的程序架构对于面向对象编程的重要性,一个好的架构不仅能避免很多bug,而且具有的可拓展性和可维护性可以大大方便我们的代码实现与迭代。