BUAA OO Unit1

22230604-艾俊辰 学生 2024-03-21 22:04:41

BUAA OO 第一单元总结

写了依托答辩,简直惨不忍睹不忍直视
最终有几个在cost边缘大鹏展翅的互测数据没能修复,归根结底还是因为架构问题。Hashmap固然有其优点,例如可以很好地利用其键值对相等自动合并的特性进行合并同类项,但是理想的做法是和建树相结合而非直接进行暴力嵌套。

感谢室友的push和大佬的帮助,不然我只会比现在更惨

一、程序结构

主要结构如下:

img

img

其中,第二次作业加入了Exponent和Func类,第三次作业加入了Demi类。

1.代码思路

对于Main读入自定义函数导入Parser类进行解析,最后加入func容器进行管理。对于读入的需要解析的表达式,首先进行Lexer当中对前导0、多个连续+-、自定义函数的预处理,再放入Parser类当中进行解析。
对于解析,首先从末尾开始,根据+/-、*、^、()、exp/dx/x的优先级进行判断,并进行解析。
对于结果,依次存入Term当中,按照数字、x、exp的顺序进行存储,最终返回一个Expr。在后续的运算当中,通过比较Term是否相等来判断是否合并同类项。
最后返回toString,递归打印。

2.分析结果

真是一堆酣畅淋漓的屎(言简意赅)

img

img

img

可以看到,存在不少方法有复杂度高,非结构化程度高,模块耦合度高,架构不清晰,很多方法堆在一个类中但没有很明确的聚类关系,意即我无法回答为什么这个方法应该放在这里而不是其他地方
(真是奇怪hw1的时候感觉明显比oopre的代码架构有进步来着)
在以后的学习当中仍然需要注意这一点。

二、架构设计体验

1.架构成型过程

第一次作业奠定了基本架构。
第二次作业加入了Exponent类和Func类,将Term的属性从单纯的数字or x改变成为了Hashmap,与此同时加入了判断两个Term是否相等的一系列方法(涉及到深浅拷贝问题)。为了优化长度,我选择对Exponent的指数进行提公因数的化简,让第一次作业简单的toString方法变得复杂起来。
第三次作业加入了Demi类,并对Fun自定义函数的处理进行了改变,加入了对引用函数的展开。

2.可扩展性:

若表达式当中不止x一个变量,而是xyz都有:

在Term的生成当中,将“x”变为对应的变量名,并在判断是否相等时在Factor接口的类是否相等时加上判断变量名称是否相等。

三、bug分析

1.TLE

以下是几组tle的互测数据:

0 
dx(exp(exp(exp(exp(exp(exp(exp(exp(x^2))))))))) 
输出:2*exp((exp(exp(exp(exp(exp(exp(exp(x^2)))))))+exp(exp(exp(exp(exp(exp(x^2))))))+exp(exp(exp(exp(exp(x^2)))))+exp(exp(exp(exp(x^2))))+exp(exp(exp(x^2)))+exp(exp(x^2))+exp(x^2)+x^2))*x
0
exp(exp(exp(exp(exp((x+1+exp(exp(x))))))))    
输出:exp(exp(exp(exp(exp((x+1+exp(exp(x))))))))
2
g(z)=exp(exp(exp(exp(z))))
f(y)=exp(exp(exp(exp(g(y)))))
f(exp(exp(exp(exp(x^8)))))
输出:exp(exp(exp(exp(exp(exp(exp(exp(exp(exp(exp(exp(x^8))))))))))))

这几组数据的共同特点是exp嵌套层数极多。对于建树的架构而言,其复杂度是相加,但是对于HashMap嵌套的架构而言,遍历的复杂度不高,但是在toString方法输出时,其每一层的递归遍历复杂度是相乘的。因此最后的复杂度会相当恐怖,也就导致了这几组看起来平平无奇特别简单的数据的运行时间很恐怖。

2.调试与运行结果不一致:

闹鬼啦
以下是一些可能原因:
(1)HashMap的顺序不一定,但是如果某一部分的运行结果依赖HasnMap的遍历顺序就会出现因为运行和调试时的遍历顺序不同导致的结果不同。其奇怪之处就在于HashMap顺序不定因此程序没有依赖遍历顺序。
(2)toString方法在调用时改变了对象,导致toString函数最后的结果在不同快慢的调试和直接运行当中都不同。
(3)特别倒霉遇上了Oracle当中的一些Undefined Behavior,加上BigInteger产生了奇怪的化学反应(……)

解决办法:将所有HashMap改成LinkedHashMap,推测还是因为依赖了迭代器的迭代顺序而不自知

最可能的原因推测:HashMap在利用Iretator进行删除时并没有真正“安全”删除从而导致了遍历时对象改变

3.对于输出结果的格式判断有误

(血泪教训是不要在发烧的时候写代码,会变得不幸QAQ
其实本身的判断很简单,但是之前在发烧的时候写了一堆乱七八糟,在此基础上再改,始终会囿于原本代码框架的影响,最后造出来一个不伦不类的东西。
输出格式的问题在于,对于exp的指数,x与exp(x)被视为因子,只需要打一层括号,而其余所有形式都需要两层。在将exp((n*x))(n为系数)提出变成exp(x)^n的形式时需要特判。

4.深浅拷贝

老生常谈的问题,此处需要记住的是,对于自定义的类(即非String、int等基础类)clone也是浅拷贝。

解决方法借鉴自第二次实验:可以对相应的类重写clone,return以当前参数为参数的一个新的实例化对象。

四、优化策略

为了优化长度,我选择对Exponent的指数进行提公因数的化简,让第一次作业简单的toString方法变得复杂起来。

但是显然,提最大公因数和提使式子最短的公因数都不是最短。选择将一部分式子提出来,剩下的部分提更大的公因数是更好的选择(如果有能力的话)不过也要提防时间复杂度超过限制和TLE。

五、心得体会与未来方向

1.心得体会

好架构决定oo单元的一生

一个好的架构真的会让整个迭代都易如反掌,令人拥有完全不一样的迭代体验。Unit2开始,还是应该先考虑好整体的布局架构再进行代码写作,不可以到处乱塞了!!!

评测机,oo最好的医美

虽然评测机不一定能够完全覆盖掉每一个数据使之没有bug,但是绝大部分数据集评测机都能够覆盖。一个好的评测机会解决大部分的问题,希望Unit2我能够学着写一个属于自己的评测机。

比起上学期架构有进步

起码没有将所有东西塞到一个类里,使之完成了CheckStyle的五百行限制(比上学期有进步)。不过每个方法最多不超过60行的限制有一些方法没有做到,下次需要注意。

2.未来方向

在接下来的Unit2当中,希望我能够在完成必要任务的情况下能够做到:
(1)做好解耦
(2)做好聚类
(3)行有余力地话能够写一个自己的测评机

写在最后

虽然最后的结果不尽如人意,但是也还算是付出都有回报,没有辜负深夜与清晨。轻舟已过万重山,一山放过一山拦。Unit2加油!

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

301

社区成员

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

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