BUAA OO 第一单元总结

吴瑜-22230605 学生 2024-03-19 23:58:55

目录

  • 0. 主要知识点
  • 1. homework_1
  • 1.0 题目要求
  • 1.1 UML图及架构分析
  • 1.2 代码量
  • 1.3 复杂度分析
  • 1.4 优点
  • 1.5 缺点
  • 2. homework_2
  • 2.0 题目要求
  • 2.1 UML图及架构分析
  • 2.2 代码量
  • 2.3 复杂度分析
  • 2.4 优点
  • 2.5 缺点
  • 3. homework_3
  • 3.0 题目要求
  • 3.1 UML图及架构分析
  • 3.2 代码量
  • 3.3 复杂度分析
  • 3.4 优点
  • 3.5 缺点
  • 4. 出现的bug
  • 5. hack策略
  • 6. 我的优化
  • 7. 架构设计体验
  • 8. 心得体会
  • 9. 未来方向

0. 主要知识点

递归下降

img

1. homework_1

1.0 题目要求

读入一个包含加、减、乘、乘方以及括号的单变量表达式,输出恒等变形展开所有括号后的表达式。

1.1 UML图及架构分析

img

数据存储类:

  • Expr
  • Term
  • Factor
    • Num 常数因子
    • Powernum 变量因子
    • Expr 表达式因子

解析类:

  • Token 存储数据类型
  • Lexer
    • replace:删去输入字符串中的空白字符;对字符中的++,--,+-,-+,^+,*+简化替换为+,+,-,-,^,*
    • delete:删去输入字符串中的前导零
    • lexer:将加一个负数转化为+0-abs(num),将乘一个负数转化为*(-1)*abs(num);读取字符并判断类型,存入tokens中
  • Parser 按照Expr -> Term -> Factor进行递归下降解析
  • Operation 输出结果

计算类:

  • Parser 以a*x^b为基本单元计算,并将计算结果通过Factor -> Term -> Expr向顶层传递(Term层完成Factor的乘法运算,Expr层完成Term的加减法运算)。

1.2 代码量

img

1.3 复杂度分析

类复杂度

img

方法复杂度(截取部分)

img

Lexer两次遍历字符串(删去前导零、按字符类型将其存入tokens)。
Operation类通过两次按指数大小遍历hashmap对结果进行输出。
Parser类耦合了解析与计算。

1.4 优点

  1. 对于^,我在parserTerm中将其替换为...* ...* ...的形式,使得^*统一起来。
  2. ExprTerm Factor中均采用Hashmap存储a*x^b中的ab
  3. 相较于-1+x,字符串x-1的长度更短,于是在输出中先输出正数项,后输出负数项。
  4. 由于本次作业架构良好,目前程序已支持嵌套括号,减轻了第二次作业任务量。

1.5 缺点

  1. 解析类与计算类混杂在一起(Parser类承担了解析、计算两种功能),降低了代码的可读性和可扩展性,不利于后续迭代。
  2. 判断前导零时使用索引值+1,但未考虑索引值+1是否会超出字符串范围。(该问题直至第二次互测时才被发现)

2. homework_2

2.0 题目要求

支持嵌套多层括号;新增指数函数因子;新增自定义函数因子。

2.1 UML图及架构分析

img

数据存储类:(其余架构同homework_1)

  • Factor
    • ExpNum 指数函数因子 (新增)
  • Result (新增)
    将homework_1中用Hashmap存储a*x^b改为使用类存储

操作类(预处理、输出):(其余架构同homework_1)

  • Operation
    • delete 将homework_1中Lexer类中的replace函数挪到Operation类中 (简单重构)
    • print exp(...)提取系数公因数,使之尽可能短 (新增优化方法)

函数类:

  • Func 对字符串中的函数进行替换 (新增)

解析类:(其余架构同homework_1)

  • Token 新增ExpNum类型

计算类:

  • Calculation (新增)
    • Result数组的乘法和加法
    • 判断两个Result是否相同

2.2 代码量

img

2.3 复杂度分析

类复杂度

img

方法复杂度(截取部分)

img

Calculation类完成乘法、加法运算,遍历两个Result、判断其是否相同。
Func类不断遍历字符串直至所有函数名被替换。
Lexer一次遍历字符串(按字符类型将其存入tokens)。
Operation类对字符串进行预处理,多次递归寻找exp(...)的最短输出,并按先正系数项、后负系数项的顺序输出Result。
Parser类进行解析。

2.4 优点

  1. 本次作业我将解析和计算解耦,Parser类完成解析,新建Calculation类进行计算,利于第三次作业的求导迭代。
  2. 新建Func类,在解析之前,通过字符串替换的方式,将自定义函数代入表达式。
  3. 关于exp(...)内括号问题,总结出如下条件:
    if (该项为常数项) { 
         exp(...)中的...两侧不加括号
     } else if (该项是x或exp(...)) {
         exp(...)中的...两侧不加括号
     } else {
         exp(...)中的...两侧加括号
     }
    
  4. 相较于exp((20+30*x+40*x^2)),exp((4+6*x+8*x^2))^5更短,由此根据讨论区同学的想法,我提取公因数,不断比较,使exp(...)的输出尽可能短。

2.5 缺点

  1. 为使exp(...)的输出尽可能短,我不断递归调用获取字符串的同时,时间复杂度增加。
  2. 寻求最短exp(...)时,没有考虑到如下情况:
    exp((3*x+2*x^2+2*x^3+2*x^4+2*x^5)) (34个字符)
    <==>
    exp(x)*exp((x+x^2+x^3+x^4+x^5))^2 (33个字符)
    
  3. 判断前导零时使用索引值+1,但未考虑索引值+1是否会超出字符串范围。(本次作业互测环节被hack)

3. homework_3

3.0 题目要求

新增求导算子;支持调用其他“已定义的”函数。

3.1 UML图及架构分析

img

数据存储类:(其余架构同homework_2)

  • Factor 新增求导因子 Derivative 类

解析类:(其余架构同homework_2)

  • Token 新增Derivative类型。

函数类:

  • Func
    对字符串和自定义函数中的函数进行替换

计算类:

  • Calculation 增加求导运算

操作类:(同homework_2)

3.2 代码量

img

3.3 复杂度分析

类复杂度

img

方法复杂度(截取部分)

img

本次作业只有Func类和Calculation类各增加了一个函数,复杂度分析基本同homework_2。

3.4 优点

本次作业所体现出来的优点完全得益于前两次作业架构(尤其是第二次作业)的可扩展性搭建:

  1. homework_2将计算与解析解耦,使得本次作业只添加了求导函数就完成了新增运算。
  2. homework_2将自定义函数的代入放到了字符串预处理部分完成,使得本次作业在完全不修改解析的前提下、就可以完成自定义函数的嵌套。

3.5 缺点

  1. 尽管homework_2结束后,我了解到了“最短exp(...)”的情况,但即便经过很久思考,我也无法找到平衡输出性能和运行时间的实现方法,最终害怕强测互测出现TLE,舍去了这种性能优化,强测第九点又又又涉及这种方式,于是性能分没了……
  2. 本次作业强测阶段还有一个点我的性能分较低,原因是exp多层嵌套相等条件判断不足,导致两项无法合并、有效输出较长。第二次作业的强测数据并没有考察到这一点,侥幸拿了高分,而后续测试不足且自己的测试更加注重正确性,导致这一问题未被发现。(以后测试还是要多构造多种多样的数据点)

4. 出现的bug

第二次作业互测阶段败在了如下数据,原因是删除前导0时有读取"字符串索引值+1"的操作,但没有判断是否越界。

0
0

做测试时一味追求长、难、多层嵌套的数据,忽略了这类基础数据。

5. hack策略

我着重hack了长数据点(包括多层嵌套、超大指数等)、exp(...)内括号问题。但三次hack同房间的同学的代码都很完美,只有第二次作业采用多重exp嵌套hack到了人,下载同学代码发现是TLE问题。

6. 我的优化

  1. 采用专门的类来存储a*x^b项,有效解决数组、Hashmap多层嵌套不直观的问题;自己写数据克隆,有效解决数据类型自带克隆的深浅克隆问题。
  2. 采取遍历公因数找尽可能短的exp(...)有效输出。

7. 架构设计体验

  1. 很感谢助教组提供的递归下降教程和源代码,通过修改源代码,我很快上手了本单元作业,后续为使结构清晰,我将解析与计算解耦。整个迭代过程有将函数从A类移到B类的过程,但没有大规模的重构。
  2. 对于可能的新的迭代,我的架构具有良好的可扩展性:面对新的计算类型,修改解析与计算类,而面对自定义函数的变动,则修改Func类。

8. 心得体会

凭着OOpre多次迭代的经验,我在写hw1和hw2时就在有意识地搭建结构清晰、可读性强的架构:没有将大量代码堆在Main中,而是建立单独的Operation类,完成预处理和输出;hw2及时将解析与计算解耦,方便第三次迭代。

我在测试部分做的不好,一方面依赖于同学的测评机、自主性不强,另一方面自己构造数据时没有着重构造最简洁、最易错、最麻烦、最大值的数据。

9. 未来方向

希望能对 exp(...) = exp(...)*exp(...) 这种性能优化提供一些思路orz

...全文
109 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧