OO_Unit1 分析与总结
1. 细分分析
1.1 hw_1
表达式 主要由Expr
,Term
,Factor
三个部分组成 ,在我的实现中,Expr
由多个带符号(sign
)的Term
加法构成,Term
由多个Factor
乘法构成,其中Factor
实现为接口,下属类(Expr
、Term
、Num
、ExprFactor
(多项式幂)、Power
(幂函数))均实现接口以及相关方法。
- Factor
-toString():转化为字符串
-toPoly():转化为多项式
为了正确读取并存储表达式,在之后对该表达式进行细化分析,采用递归下降法解析表达式。此处另生成词法分析器Lexer
类以及语法分析器Parser
类,以及Token
类,用于存储词法分析器分析的各个部分,便于Parser
类取出分析。
- Lexer
-Lexer():根据读入字符串切割字符串分析初始化Lexer类和Token类
-Move():将指针cur后移一位,便于访问Token中存储的下一个词缀
-Now():访问cur当前对应的词缀
-Notend():查看Token是否已经访问到结尾- Token
-Token():初始化Token类(ADD, SUB, MUL, LPAREN, RPAREN, NUM, VAR, POW)
-getType():获得当前cur对应词缀的类型
-getContent():获得当前cur对应词缀的字符串形式- Parser
-Parser():初始化Parser类
-parseExpr():分析表达式,其中调用parseTerm()
-parseTerm():分析项,其中调用parseFactor()
-parseFactor():分析因子,其中调用parseExpr(),-parsePower(),parseNum(),parseExprFactor()(没有-parseTerm()是因为包含在parseExpr()的调用中)
-parsePower():分析幂函数(读取到x)
-parseNum():分析数字(读取到数字)
-parseExprFactor():分析多项式幂(在右括号后发现^号)
为了能够实现运算化简功能,实现两个新类Mono
、Poly
即单项式、多项式。其中Mono
含有两个个属性BigInteger coe
、BigInteger exp
,即单项式系数以及次数,Num
类exp
即为
0
0
0,如此便将所有的因子全部统一为一个相同的类。Poly
即Mono
的Arraylist。这样子在每个因子中均实现toPoly()方法转化为Poly类,即可进行统一计算。
- Mono
-Mono():初始化Mono
-toString():转化为字符串(便于Poly调用组合)
-getCoe():获得coe
-getExp():获得exp
-negate():前面提到Term类实现中含有符号,此处即Term类转化为Mono时若符号sign=-1,会将Mono的coe属性乘上-1
toPoly():省去Factor实现类中多余的生成空Poly插入对应Mono过程- Poly
-Poly():初始化Poly
-addPoly():多项式加法
-mulPoly():多项式乘法
-powPoly():多项式幂
-toString():转化为字符串
-Produce1():toString()的一部分
-Produce2():toString()的一部分
-sort():排序,目的是最终长度优化以及后续可能的Equal()方法实现
-negate():循环调用Mono类的negate
为了去除前导0、空白符等以及最终字符串处理,实现了Processer
类。
- Processer
-Preprocess():预处理
-Zeropre():预处理中的0处理
-Signpre():预处理中的符号处理(±)
-Otherpre():预处理中的空白符等其他可能处理
-Endprocess():最终处理
PS1:根据如上类与方法架构成功实现了第一次作业,但在互测时出现bug,原因为预处理Zeropre()方法中不小心删除了数字结尾0,不具有普遍性不赘述
PS2:有关优化:
- 1 ∗ x 2 = x 2 1*x^2 = x^2 1∗x2=x2
- − 1 ∗ x 2 = − x 2 -1*x^2 = -x^2 −1∗x2=−x2
- x 0 = 1 x^0 = 1 x0=1
- 0 ∗ F a c t o r = 0 0*Factor = 0 0∗Factor=0
- − F a c t o r 1 + F a c t o r 2 = F a c t o r 2 − F a c t o r 1 -Factor_1 + Factor_2 = Factor_2 - Factor_1 −Factor1+Factor2=Factor2−Factor1(此处即为Sort()方法排序优化)
1.2 hw_2
Factor
中增加两个新方法,所有下属类均实现
- Factor
-Equal():判断两个因子的字符串最简形式是否相同
-getNum():递归获取内部因子个数(仅Expr与Term获取会≥1,其余获得均为1)用于判断指数函数是否需要嵌套括号保护内部表达式因子
在hw_1的Factor
基础上增加ExpFactor
类即指数函数类。
- ExpFactor(实现接口Factor)
-ExpFactor():初始化
-MulExp():ExpFactor类乘法
-PowExp():ExpFactor类幂
-getFactor():获取指数内部因子
-getFac():获取指数内部因子的字符串形式
-getExp():获取指数函数的次数用于PowExp()
-toPoly():转化为多项式
-Equal():判断是否相同
-getNum():return (int)1;
-toString():获得字符串,添加必要括号,判断函数为getNum()
-removeExtraParentheses():去除可能的多余嵌套括号,判断函数为getNum()
添加Func
类用于处理自定义函数
- Func
-addFunc():读取定义的函数并存入funcMap和ParaMap(函数hashmap和参数hashmap)
-callFunc():根据输入的实参返回新的字符串并用于分析
PS:生成新字符串时实参外增加括号保护,整体外也增加括号保护
Parser
类中新增方法
- Parser
-parseFunc():分析自定义函数
-parseExp():分析指数函数
-parseSub():分析负号后部分
有关bug:
除去callFunc中的括号保护
在Preprocess()方法中增加了exp替换为e的步骤,防止处理中出现另外问题
这次出现的问题较多:
- 首先是Mono类的exp属性原本用的是int型,但在对于
(((((((((((x^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8
这种数据就会爆上限导致TLE - 由于自定义函数是转化为括号保护的字符串,可能会出现
Factor*-(Expr)
的情况,这其实是不符合课题组规范的输入,会导致TLE的出现,因此后续增加了parseSub()方法额外处理 - 指数函数内部因子双层括号和单层括号处理出现问题,增加ExpFactor.removeExtraParentheses()和Factor.getNum()方法进行判断处理
有关优化:
- 在此次迭代中,通过使用舍友生成的数据
(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((exp(x)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8
发现指数类型相关计算耗时过多,增加了addPoly()、mulPoly()、powPoly()中的对0特判跳过 - 增加了提公因数的方法Optimize(),但最终未使用因为没有方法能在不花费过多时间的情况下较好判断应该提公因数还是不提
1.3 hw_3
Factor
类中增加新方法
- Factor
-Derive():用于求导
Parser
中新增方法
- Parser
-parseDerive():解析求导式
有关优化:
未做优化
有关bug:
仍为exp相关bug,不具有普遍性不做赘述
2. 整体分析
2.1 第三次作业UML图
如图,Main为主类,未有过重构
2.2 复杂度分析
2.2.1 方法复杂度
有一部分方法复杂度较高。
- ev(G) 基本复杂度是用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解。因此,基本复杂度高意味着非结构化程度高,难以模块化和维护。实际上,消除了一个错误有时会引起其他的错误。
- iv(G) 模块设计复杂度是用来衡量模块判定结构,即模块和其他模块的调用关系。软件模块设计复杂度高意味模块耦合度高,这将导致模块难于隔离、维护和复用。模块设计复杂度是从模块流程图中移去那些不包含调用子模块的判定和循环结构后得出的圈复杂度,因此模块设计复杂度不能大于圈复杂度,通常是远小于圈复杂度。
- v(G) 是用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数,即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护,经验表明,程序的可能错误和高的圈复杂度有着很大关系。
思考:事实上,迭代中出现bug的部分也确实多出于以上圈复杂度高的方法中。因此在后续OO学习中要尽量减少方法的各类复杂度。
2.2.2 类复杂度
主要复杂度体现在Poly类上。
结合方法复杂度看,问题应该出在字符串处理的输出部分。
除此之外,ExpFactor类也使用过多方法对括号进行处理。
思考:若继续优化可额外实现一个输出类,降低耦合度。
2.3 Bug相关总结
- 预处理Zeropre()方法中不小心删除了数字结尾0
- Mono类的exp属性hw_1用的是int型,但在hw_2中对于
(((((((((((x^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8
这种数据就会爆上限导致TLE - 由于自定义函数是转化为括号保护的字符串,可能会出现
Factor*-(Expr)
的情况,这其实是不符合课题组规范的输入,会导致无法处理乃至tle,因此后续增加了parseSub()方法额外处理 - 指数函数内部因子双层括号和单层括号处理出现问题,增加ExpFactor.removeExtraParentheses()和Factor.getNum()方法进行判断处理
2.4 优化相关
- 1 ∗ x 2 = x 2 1*x^2 = x^2 1∗x2=x2
- − 1 ∗ x 2 = − x 2 -1*x^2 = -x^2 −1∗x2=−x2
- x 0 = 1 x^0 = 1 x0=1
- 0 ∗ F a c t o r = 0 0*Factor = 0 0∗Factor=0
- − F a c t o r 1 + F a c t o r 2 = F a c t o r 2 − F a c t o r 1 -Factor_1 + Factor_2 = Factor_2 - Factor_1 −Factor1+Factor2=Factor2−Factor1(此处即为Sort()方法排序优化)
- 通过使用数据
(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((exp(x)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8
发现指数类型相关计算耗时过多,增加了addPoly()、mulPoly()、powPoly()中的对0特判跳过 - 增加了提公因数的方法Optimize(),但最终未使用因为没有方法能在不花费过多时间的情况下较好判断应该提公因数还是不提,最终取时间而放弃性能
- 本次实现过于繁琐,在之后的实现中应该提取方法中的同质部分形成新的方法并统一调用,提高可拓展性、简洁性、可读性。
3. 心得体会
在整体实现中由于代码风格不够好导致出现了一系列问题,之后将基于此次Unit 1出现的一系列问题对代码风格进行调整,争取在后续Unit中能实现性能更加优良的程序。在这一章中,递归下降也使我受益良多。除此之外,面向对象的思想也进一步了解与巩固,相对于面向过程,我认为它有以下优点:
- 封装性:面向对象编程将数据和操作封装在对象中,隐藏了对象的内部细节,只暴露必要的接口,提高了安全性和可靠性。
- 继承性:通过继承可以实现代码的重用,子类可以继承父类的属性和方法,减少了重复编码的工作量,提高了代码的可维护性。
- 多态性:多态性允许不同类的对象对同一消息做出响应,提高了代码的灵活性和扩展性,使得代码更容易理解和维护。
- 抽象性:面向对象编程可以通过抽象类和接口来定义抽象数据类型,简化了复杂系统的设计和实现过程,降低了系统的耦合度。
- 代码的可读性和可维护性:面向对象编程使得代码更加模块化,易于理解和修改,降低了代码的复杂性,提高了代码的可读性和可维护性。
总的来说,面向对象编程是一种更加高效和优雅的编程范式。
4. 未来方向
希望能提供更多思路或者方法,尤其是在优化上,因为每个人基础不同,算法能力不同,希望课程组能给一些优化思路。
除此之外,希望课程组能给一些有关代码复杂度的介绍,便于针对性降低代码复杂度,防止出现过多问题。