2024 BUAA OO Unit1 总结

陆辰-22371569 学生 2024-03-20 14:15:29

2024 OO Unit1


OO Unit1主题为表达式化简,需要处理含有加、减、乘、乘方、指数、自定义函数、求导、括号等部分的表达式,并进行展开和计算。主要方法是层次化设计与递归下降。

HW1

架构HW1

img

第一次迭代沿用第一单元训练提供的代码,在 MainClass 中读入输入, Lexer 对输入进行词法分析, ParserLexer 进行语法分析,生成语法树(树根为 Expr )。最后 Expr 调用接口 Calculator 中方法计算表达式,生成 PolyFunc 并输出为字符串。

Lexer 类从输入中解析出最小语法单元,存为 Token 并以容器形式保存在 Lexer 中。

Parser 类对 Lexer 类中保存的 Token 采用 递归下降法 进行解析,构建出语法树 Expr->Term->Factor ,其中 Factor 接口包含 Expr Var Num

Calculator 接口计算语法树中的节点并返回 PolyFunc ,存储格式为 ArrayList 内含 ax^bArrayList 内部按照指数实现合并同类项与排序方法。

目前架构没有单独的输入输出类,且 PolyFunc 实际返回结果为 ArrayList<Var> ,扩展性不足。

在处理过程中将解析与计算过程分离,层次清晰,易于理解与修改。

度量HW1

img

img

ev(G)是基本复杂度,衡量非结构化程度,ev(G)高意味着难以模块化和维护。
iv(G)是模块设计复杂度,衡量模块之间的调用关系,iv(G)高意味着模块之间的耦合性高,难以隔离和复用。
v(G)是圈复杂度,衡量结构的复杂程度,v(G)是说明代码难以测试和维护。
CogC是认知复杂度,衡量代码的难以理解的程度,CogC高说明代码比较难以理解。

Lexer 复杂度较高是因为将所有的 Token 判断都写在里面, PolyFunc.varToString 复杂度较高是因为简化输出进行多种特判,如简化 +1*x^1 = x 等。其他方法复杂度较低,内聚度较高,耦合度较低。

HW2

架构HW2

img

第二次迭代基于第一次迭代,专门提取出 Input 类和 Output 类用于处理输入输出,处理逻辑写在 Processor 类中。新增记录 Calculate 结果的 Item 类和 Poly 类,分别代表单项式和多项式。新增 Exp 类和 Func 类处理自然底数和自定义函数。

Input 类专门读入自定义函数表达式存入 Lexer 中,供 Processor 类解析。

新增 Exp 类和 Func 类实现 Factor 接口,归入语法树中。 Exp 类的指数和 Func 类的参数类型为 Factor

Calculate 接口实现 deFunc 方法,将语法树中的 Func 类根据函数定义 Lexer 替换为 Expr 类。

Item 类和 Poly 类自行定义排序与合并方法,其中 Poly 类以 ArrayList 存储 Item 类, Item 类存储形式为 ax^b*exp(expPoly)

Output 类根据 Poly 类输出字符串,进行一些简化,如 exp((x^2)) = exp(x^2) 等。

度量HW2

img

img

Output.readPoly 和HW1中的 PolyFunc.varToString 类似,复杂度在于简化。其他方法复杂度较低,内聚度较高,耦合度较低。

HW3

架构HW3

img

第三次迭代基于第二次迭代,新增求导因子 Derive ,内含求导内容 Factor 。自定义函数的定义式中出现已定义过的自定义函数,故将 deFunc 方法不再单独使用,在解析过程中即替换 Func 因子为 Expr

Item 类和 Poly 类实现求导方法,返回新的 Poly 类。

度量HW3

img

img

复杂度分析同前两次作业。复杂度来源在于需要考虑 $exp(exp(exp(x)))$ 类似的多重指数,Item 类和 Poly 类的比较排序功能都需要递归。且随着 Factor 种类增加, LexerParse 中相应的 if-else 语句增加。

其他方法复杂度较低,内聚度较高,耦合度较低。

迭代体验

从HW1到HW2,因为HW1的输入输出没有包装,都挤在 MainClass 里,迭代起来手感观感很差,不如重新包装一遍。同时加入指数与自定义函数后解析与计算复杂度上升,更需要层次化封装添加新类 ItemPoly ,降低脑力消耗。

从HW2到HW3,求导因子的增加很简单。但是HW2解析自定义函数时默认了定义式中不存在函数,而HW3中可以。最初的想法是在读入函数定义式的时候就进行字符串展开,但是后来发现直接将解析和去函数两步合一更简单。

假如次回迭代中不给出全部函数定义式,那么就需要区分可展开函数和不可展开函数。只需要修改 Item 加入函数容器,修改比较排序逻辑,并在 Output 中原样输出函数并展开可展开因子即可。

bug分析

使用指导书和公测给出的简单样例,排查出代码中的手误后就没有bug了。按照输入格式试探边界情况与一般情况,使用 BigInt 处理数字。

强测与互测中未出现bug。

仅在HW1中使用 $(x-x)^0 = 1$ 成功hack过一次。其余时候均无成功hack。推测是因为自己使用了 BigInt 就没有考虑大数,而别人的代码可能会因为未使用 BigInt 而溢出。

减少bug的关键在于代码逻辑与层次清晰,高内聚低耦合

优化

最简单的是正项提前,省下一个 + ,以及多项式的第一项如果是 + 则不输出。

因为 Poly 中使用 ArrayList 储存 Item ,不论什么操作都可以简化为创造新的 ArrayList 内含新的 Item ,最后合并同类项。

合并同类项在 Poly 中实现,依靠自定义的 Item 比较方法。具体的比较谁大谁小的方法不重要,只需要能比出顺序即可。但是判断相等必须严格,因为涉及合并同类项。

对于单因子外面还可以少一层括号,这个只需要对 ItemPoly 实现是否单因子的判断方法即可。

对于形似 $+1x^0exp(0)$ 的化简输出最复杂。x指数、exp指数、系数、正负都需要考虑。程序判断起来很复杂,行数和分支很多,但是逻辑是一目了然的。

我没有考虑实现类似 $exp(a) = exp(b)^c$ 类型的优化。这种优化依赖因数、因式分解以及提取公因数、公因式,实现难度很大。此外,我觉得这种优化以及偏离了正常的优化,会破坏多项式的统一感。

心得体会

递归下降 这种思想最开始看不懂,要上手写了才开始慢慢懂。

代码一定要做好层次化,要有可扩展性,否则迭代的时候会感觉还不如重写一遍。尽量避免把类型写死。

但是有的时候为扩展预留空间太多,就需要进行很多额外转换和处理,工作量和难度会上升,比如未知数如果不止 x 还有 yz 那么比较、排序、输出、求导就会麻烦很多,我就没有去考虑。

周一晚上7点拿到指导书会先想几个小时,然后再试着写一点,周二再边落实边思考,到中午或者晚上基本完成,然后用简单数据修正笔误。这个时候有可能发现自己之前的想法有缺漏,就要在周三重新走一遍上面的流程。一般能在周四中午12点前完成。然后对着中测修正小失误和代码格式,等周日互测构造一些数据点。目前还没出过bug就没修过。

一周这么过,基本上前半周或者三分之二周都是有空就在想在写,过了中测就基本结束了。前半周的压力尤其大,到了后半周精力又拿来处理其他课作业,就没有想着搭和用评测机,因为的确没用到,我自己习惯还是简单数据加代码审查。

听说有人能一个晚上就从构思到写出代码到测试完毕,再一天写出评测机,我感觉我没这个水平和速度,只能慢慢想,慢慢写,慢慢改,不去做激进优化,稳扎稳打。

未来方向

我觉得相比起前几年的强度,现在的强度就比较好,既没有多个未知数写起来那么复杂,也可以很好地展示 递归下降 和层次化设计的优点。


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

301

社区成员

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

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