BUAA-OO-2024 第一单元总结(表达式解析与化简)

沈锎-22373080 学生 2024-03-23 15:12:38

目录

  • 架构设计与迭代
  • 架构设计总览与迭代概述
  • 架构分析与思考
  • 具体实现的度量与评价
  • 程序度量数据
  • 各种复杂度
  • 类的耦合关系(纸上谈兵)
  • Bug,Debug 与 Hack
  • 心得体会与未来方向

架构设计与迭代

架构设计总览与迭代概述

第一单元的核心内容有二:表达式解析表达式化简。类图如下(已忽略不那么重要的方法、属性,标有 <<Parsed>> 的类由 Parser 生成对象)

img

我的设计的核心在于,将因子直接并全部视为多项式,同时 ExprTerm均展开为多项式。全部的计算(加、减、乘、合并、展开等)均在单项式与多项式类间完成。下图具体展示了多项式类是如何组织的。值得注意的是,单项式中出现的 e 指数,实际也为多项式,这里产生了递归的关系。

img

第二次作业 自定义函数 的处理中,我通过 Parser 解析出函数入口,并在调用时通过 函数入口 生成 函数调用,将实参作为 多项式 乘入,我认为这样的设计是非常好的,既避免了字符串替换的种种问题,也使其可以直接适用第三次作业中的嵌套调用

img

第三次作业 求导因子 的处理中,我将其简化,直接在多项式与单项式两个类中增添方法进行计算,上图直观、简洁地展现了整个计算过程。

架构分析与思考

在这里我通过几个问题思考并分析了一下自己的设计。

几个因子为什么是继承而不是接口? 因为我考虑到它们要想参与运算,就必须要得到一个统一的对象(也就是我们计算得到的多项式),既然这样,与其利用接口规定一个 “展开成多项式” 的方法,倒不如直接让他们成为多项式的子类,在创建对象时,即成为多项式。这样设计更加清晰、统一,架构也更间接、易懂。

为什么不将求导因子视为因子? 经过研讨课的讨论,我才意识到还可以把求导当成因子()当时写的时候有点上头,没经过仔细地思考,便直接添加了两个方法完成第三次作业。现在想一想,将它们视为因子能够简化单项式与多项式类的功能,也使它们的功能更统一,有利于后续的拓展。

为什么不采用字符串替换的方式调用函数? 当时觉得使用字符串替换不够优雅,现在觉得使用字符串替换比较危险,容易出事。

深浅拷贝的问题如何解决? 我将多项式与单项式类设计成了 几乎不可变,“几乎” 是因为单项式类有一个方法会改变自身状态,但属特殊情况。此外,一切的加、乘、乘方、合并同类项,都会返回新的对象

再增加一点内容还能应对嘛? 如果还是像求导这种可以直接计算的,比如求和符号,直接新增因子类,直接展开为多项式,无需过多处理。如果是像 e 指数这种新增内容的,比如 sin,需要对单项式新增属性,并且修改相应的合并检测。

具体实现的度量与评价

程序度量数据

文件总行数代码行注释行方法数
Monomial.java3032532021
Polynomial.java175154017
Parser.java13612309
Expr.java493714
Total89875423--

可以看到,重点还是在于 计算与解析,单项式类与多项式类承载了较多的计算方法以及构造方法,因此代码较长、方法较多。

各种复杂度

MethodCogCev(G)iv(G)v(G)
expr.factor.Monomial.toExpString()28131315
expr.factor.Monomial.toOptimalExpString(Polynomial)207811
expr.factor.Monomial.toString()1521012
expr.factor.Polynomial.equals(Polynomial)2012512
expr.factor.Polynomial.mergeLikeMonomial()8455
expr.factor.Polynomial.toString()8266
Lexer.Lexer(String)14199
Parser.parseFactor()9699
Average2.131.592.172.39

可以看到,单项式类的 toExpString 方法复杂度异常高,是因为里面的分支太多exp(a * x^b),里面的 ab 都有很多情况,后续可以考虑再将它们进行拆分。

单项式类的 toOptimalExpString 方法也很复杂,因为它需要承载指数的优化(提公因数),其实没有什么很好的优化想法,仅仅是找一些公因数,尝试提取,比较长度。为了防止优化超时,通过静态属性限制计算次数。

另一个比较复杂的方法是多项式合并相关的(判断相等与合并同类项),因为前期设计使用 ArrayList 进行每一项的存储,在判断相等时需要使用嵌套循环,以致于复杂度较高的代码。这也导致了一些 Bug 的产生,所幸及时发现。

类的耦合关系(纸上谈兵)

我认为我结构设计的耦合度还是相对较低的,因子们被 Parser 解析后便直接成为 Polynomial 的对象,全部的计算也包含在多项式与单项式中,耦合度较低。

例外是 e 指数,因为指数上也可能是多项式(即使不是,也当成多项式),这里的耦合较高,但似乎不可避免。

整个架构,以 Polynomial 为核心,从解析到各因子,最终归结为多项式类,整体架构较为清晰,耦合度较低。

Bug,Debug 与 Hack

在三次作业中,我的代码没有在强测与互测中出现错误。那就说一说在编程时遇到的 Bug 吧。

  • 第一次作业 输出为空?很好弄,随便来点数据便发现了,给最后的结果特判一下。
  • 第二次作业 exp(0); exp(1); exp(2) 都是错的?还是不同的错误?Git 提交差异如下,很难评,当时还调试了半天,以为是算错了啥的。

img

  • 第二次作业 过了中测却发现多项式合并全错?! 是这样的,经过数据生成器的狂轰乱炸,发现在判断多项式相等时,循环变量 i 写成了 jthis 写成了 that,在某些情况下会越界。调试也很简单,定位越界位置,一下子就发现笔误了。
  • 第二次作业 exp(..)^-1?? 因为我的自动测评姬没有文法检查,要不是多看了一眼,说不定就爆炸了。。。适度优化益脑,沉迷优化伤身。。。

在互测中,我刀人的力度不大,比较难蚌的是 第二次作业 盲刀刀中两人(我真的只是想随便交一个样例看一看自己在哪一组的 ˚‧º·(˚ ˃̣̣̥᷄⌓˂̣̣̥᷅ )‧º·˚)

具体的 Bug 在于,exp 中为空(也就是 0)时输出异常,指数相同的 exp 被消去后,输出时越界。后通过自动测评姬轰炸时,没有发现异于此的 Bug,但事实上,有同学会出现 exp(-x) 这样的输出,我没有发现(因为这个在数学上是对的())。

第三次作业,核心在于借助 cost 的计算卡 TLE,很考验同学们代码的设计,据此刀中一人。

心得体会与未来方向

第一次作业整体迭代还是比较顺利的,整个过程也没有遇到很棘手的问题,我觉得可能得益于 前期架构设计 得较好,以及对于 不可变对象的设计 使得浅拷贝的问题不存在。后者是我这一次作业学到的一种设计思路。同时以及 层次化设计 的思路与架构,让我对这些设计理念也有了一个新的认识。

我觉得这一次作业对我的另一个帮助是让我对于 递归下降 有了更加深刻的理解,以及实践经验。我觉得以后还可以在这方面有更多的体会。

关于课程之后的建议,我觉得可以平衡一下几次作业之间的难度与复杂度,比如第二次与第三次作业之间的难度差距较大(当然这个可能也是课程组的考虑与设计)。

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

301

社区成员

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

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