BUAA-OO-第一单元总结

陆莹锦-21371321 学生 2023-03-19 00:08:48

目录

一、架构设计

HW1

HW2

HW3

二、度量分析

HW1

HW2

HW3

三、debug经历

HW1

HW2

HW3

四、hack策略

五、心得体会


一、架构设计

三次作业的架构基本一致,面向过程地来讲就是

  • 先在Parser中递归下降形成树(树上的结点是expression,term,factor类)

  • 再沿着树一路使用toPoly方法递归下降(每个结点的toPoly方法中返回值都是poly),得到最大的表达式的poly(含有许多明确的unit)

  • 再对这个大poly使用toString方法,即把每个unit的toString方法返回值加在一起。

HW1

任务:读入一个包含加、减、乘、乘方以及括号(其中括号的深度至多为 1 层)的多变量表达式,输出恒等变形展开所有括号后的表达式

第一次作业架构的解析部分是对training_advance的模仿,poly/mono的架构是从学长博客中学来的。

  • 在模仿和学习的过程中,我才理解了接口的作用和意义。作用是让parser中无论是返回NumFactor、PowFactor、ExprFactor哪种类型的对象,都可以被它的爸爸接受,并收入囊中;意义是NumFactor、PowFactor、ExprFactor的行为和数据层次相同,就应该被一个接口统一起来。

  • 一个当时很困扰我的点是模仿training_advance写的Lexer类中poscurToken的关系,在识别**时总是会出问题,最好的解决办法就是在预处理时用^替换掉**,后面作业的三角函数也同理。

  • 关于类图需要解释一点,第一作业在解析过程中我用Mono类概括了NumFactorPowFactor两类因子,后面为了让类之间的关系更加清晰,我选择将两者分开。

 

HW2

任务:+读入自定义函数的定义;+读入表达式包含三角函数;+括号深度增加

  • 第二次作业三角函数类的加入很顺利;关于自定义函数的读入,在获取形参、实参时进行了一些复杂的字符串处理,自定义函数的代入一直让我很纠结,担心字符串替换会出问题,递归又不知道该如何安放实参,还好字符串替换没出问题,且延续到了HW3。

  • 对于我来说的难点是化简,因为第一次作业我来不及考虑任何的优化,第二次作业面对poly/unit的架构,比较两个poly是否相等成了一个有思维难度的问题。

poly判等思考:比较poly相等又涉及到比较poly中每个unit是否相等,比较两个unit是否相等又涉及到比较三角函数内部poly是否相等,这里就涉及到一个小小的递归,最根本的是比较两个不含三角函数的unit是否相等。确保了这点,递归也就成功了。

(不过显然这点优化是不够用的,强测只有几个点不是80分,想了那么久,写了那么久,和完全不优化没什么区别,悲伤,摊手)

  • 另外,在第一次作业的基础上,我增加了预处理,并理顺了符号处理方法。

预处理:**替换为^,sin替换为s,cos替换为c。

 符号处理方法:在parseTerm()方法中,识别term前符号和每个factor前符号,整合后将它作为term的属性。这样一来,除了 带前导零的正整数 前的符号没有处理,其他所有符号信息都集中在term的属性里面啦。

 

HW3

任务:+自定义函数的定义可以调用已定义自定义函数;+可以读入表达式包含求导算子

  • 首先完成了新增任务的主体——求导。在第二次基础上,我增加了一个新Factor类-Derivation,它的属性是要求导的表达式字符串,它的toPoly()方法通过调用Unit的新方法-derive(),实现求导功能。

derive()方法求导思路:我的思路是对每个unit求导,返回值为poly;对每个unit的求导,是先分别对含指数的幂函数部分和三角函数部分求导,再使用乘法原则;对于含指数幂函数的求导,不用多说;对于三角函数部分的求导,遍历sin和cos的hashmap,对选中一项利用链式法则求导,再乘上其他所有未选中项,加入Poly。

  • 接下来,自定义函数的定义由原来的直接存储表达式字符串,变为对表达式字符串进行解析再toString存储,以实现调用已定义函数的功能。

 

 

二、度量分析

HW1

classOCvagOCmaxWMC

类复杂度都处于正常范围,所有没有列出。

methodCogCev(G)iv(G)v(G)
Mono.toString()33.01.013.013.0
Poly.addPoly(Poly)9.04.07.07.0

Mono.toString()的确是bug的重灾区,它需要将单项式转为字符串,虽然我没有进行太多的化简,但也不可避免地增加了复杂度。

HW2

classOCvagOCmax

WMC

Parser3.339.020.0
Unit3.4615.045.0
Poly4.2211.038.0
Definer5.676.017.0

类复杂度由于新增了 三角函数解析 和 poly/unit架构下的化简 增大。

methodCogCev(G)iv(G)v(G)
Unit.toString()40.01.015.015.0
Unit.equalsUnit(Unit, Unit)29.08.015.017.0
Unit.sameUnit(Unit, Unit)29.08.014.016.0
Poly.equalsPoly(Poly, Poly)14.05.04.06.0
Poly.addPoly(Poly)16.07.09.09.0
Poly.mulUnit(Unit, Unit)18.07.011.011.0
Parser.parseFactor()7.05.05.08.0

Poly和Unit新增的方法大多有多层循环,使得复杂度大大增加。

HW3

classOCvagOCmax

WMC 

Parser3.5710.025.0
Unit3.7215.067.0
Poly4.011.040.0
Definer5.666.017.0

类复杂度中Unit的平均圈复杂度和最大圈复杂度基本没变,但是总复杂度大大增加,应该是求导方法导致的,又因为我将它拆分成四个方法实现,所有没有增大其他两者。

methodCogCev(G)iv(G)v(G)
Unit.toString()40.01.015.015.0
Unit.equalsUnit(Unit, Unit)29.08.015.017.0
Unit.sameUnit(Unit, Unit)29.08.014.016.0
Unit.csDerive(int)16.01.09.09.0
Poly.addPoly(Poly)16.07.09.09.0
Poly.mulUnit(Unit, Unit)18.07.011.011.0
Poly.equalsPoly(Poly, Poly)14.05.04.06.0
Parser.parseFactor()8.06.06.09.0

相较于HW2,csDerive()格外复杂,parseFactor()复杂度也稍有增加,因为调用的方法又多了一个parseDri()。

 

三、debug经历

有一个让我很意外的点是,我没有涉及到深克隆的问题,可能的原因是在addPoly,mulPoly等方法中,都是新生成一个poly再返回?

(以下内容为debug过程中留下的笔记,包括自测、强测、互测中发现的bug)

HW1

第一次的bug主要出现在特殊情况(系数为0)时,互测一hack一个准儿。

  1. 当系数为0时,有指数的xyz还会照样输出

  2. 当系数为0时,xyz指数也为0时什么都不会输出

  3. 当系数为0时,0前面缺少一个mono应有的符号(因为只有当系数大于0时我才会添加一个+)

  4. Parser中,在幂函数解析时,没有用到setCoe方法(此方法根据factorsign值调整coe),直接写入1,此种factor前面的符号没有被吸收。

以上bug出现在Unit的toString()和Parser的parseFactor()方法中,它们均为复杂度高且代码行多的方法。

HW2

第二次的bug主要出现在语法问题上,还有一些致命的疏忽。

  1. equalsUnit这个方法的作用是判断两个unit是否完全相等,把答案传给equalsPoly,从而在mulpoly时起到把sin(x)**2sin(x)合并起来的功能。而在addpoly时,也需要判断两个unit是否相等,从而可以相加,比如2xsin(x)和x*sin(x),但是这里的相等是指除了系数以外其他相等。 我把这两种判等当作一种,糊涂糊涂。后来和同学交流时,发现也有人和我犯一样愚蠢的错误。

  2. 调整的时候,只改了sin的equals,忘记改cos的equals。(由于过了中测就没有进行自测,这个错误导致我在强测时寄了6个点,在调整时一定要把涉及到的所有内容都调整好,否则后果很可怕)

  3. 上一次在指数为0时,我没有在powpoly里面new一个unilist,而是直接使用本对象的属性,因为调用powpoly这个方法的时候我直接new了一个全新的poly,所以不会出现错误。这次我对对象理解地更好了,打算直接用现成的对象,但是没有考虑道powpoly在指数为0时需要一个空的unilist,所以造成了这个错误。

  4. 又是一个语法问题,unit中的sinmap<poly,num>保存着sin内的表达式和指数,当匹配相同的表达式时,要增加指数,这时候我用到了replace,我当时想poly和poly2既然已经判等了,那我replace(poly2,...)就可以了,但是不可以!对于hashmap来说这是两个不同的对象,这样replace是不会成功的。

  5. sameUnit中flag写得不好。

以上bug出现在PolyUnit类中,这两个类的方法的圈复杂度都很高。

HW3

第三次自测时用评测机虽然测出来但是没de出来,进入的互测房间又很和平,所以只记录了一条过样例时出现的bug。

  1. 极其愚蠢!自定义函数代入时用i\j\k先替换x\y\z,但是这字母选的不好,sin中有i,会出现奇怪的错误,在HW2中没出现的奇怪错误。

 

四、hack策略

只有最后一次有余富时间,使用了讨论区的评测机,其余都是手搓数据。我的手搓方法主要是:复杂到某种程度 or 特殊情况。

  • 复杂到某种程度:括号嵌套三层,函数调用三次,表达式乘方三次,三个连续正负号等

  • 特殊情况:指数为0,系数为0,结果为0等

     

五、心得体会

第一次oo作业对于没上过先导课、没写过java程序的人来说是一个极大的挑战。周中不知所措、无从下手,迷茫地听着其他人讨论;周五还不知天高地厚,在oo作业没动的情况下选择去打球,又听来一件足以难过一周的糟心事;周六在training_advance基础上,一边查语法,一边费力地尝试自己写递归下降,晚上看到两篇很有启发性的博客后,才知道除了parser和lexer其他部分该怎么写。

写好后一直在debug,听着舍友的呼吸声,嚼着续命的牛肉干,de到第二天早上七点,样例过了,交上去,十个点也神奇般地过了。漫漫长夜(试图强调半夜一个人debug,那种希望渺茫的孤独),感觉做了一件对自己来说了不得的事。

现在看来,de那几个bug本不需要那么久,但是由于对IDEA调试的不熟悉,对类和对象的不熟悉,当然还有基本功不好,深夜脑子转不动等等原因,也算正常。如果从中汲取教训的话,那大概是

  • 任务提前完成,尤其是未知难度的任务

  • 听取建议,为未来的自己多考虑(也不是没在公众号看到要提前学,就是拖拖拖,又要准备缓考,结果最后只有一点时间看一点皮毛)

  • 多交流,多看博客(感谢那些愿意和我交流思路的朋友们,还有半夜趴在走廊教我怎么用git的肖秋)

经此一夜,我认识到了ddl的力量、实践的力量和我的力量,如果放到平时,没有人push,没有一个清楚、明确的任务要求,这些东西学一个月都有可能,但是它居然,它居然不到两天就被完成了!amazing

后面的两次作业的难度小些,但是我对自己的要求还是完成即可,优化浅尝辄止,几乎不追求程序的鲁棒性,现在有些后悔,下个单元争取上进一点,而且要注意以下几点

  • 写代码要在效率严谨之间找到一种平衡,太谨慎会打断对整体架构思考的推进,太迫切地想实现某个功能又容易疏忽大意、埋下祸根。在架构时就把一切想清楚当然是最好的,可惜大多数时候我做不到,只有在写的过程中才知道有哪些细节问题还欠考虑。下一次可以在时间充裕的时候尝试再想清楚一点。

  • 写好后尽量不要马上就面向样例盲目debug,而是要有计划地验证各个模块功能的正确性。比如在第二次作业中,可以先验证自定义函数addFunc的正确性,自定义函数由简单到复杂,确保没有差错。

  • 把抽象的关系和行为画给自己看,在初期架构时很有帮助。

 

 

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

444

社区成员

发帖
与我相关
我的任务
社区描述
2023年北京航空航天大学《面向对象设计与构造》课程博客
java 高校 北京·海淀区
社区管理员
  • 被Taylor淹没的一条鱼
  • 0逝者如斯夫0
  • Mr.Lin30
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

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