301
社区成员
发帖
与我相关
我的任务
分享
一辆坦克,拥有更大的底盘、更大的炮塔尺寸、更通用的配件组,能让她在遥远的将来,甚至是数十年后,依然活力充沛。
本单元作业围绕表达式化简不断增加难度,一上来就给我整了点小小的OO震撼。本次作业学到的最重要的两点就是可拓展性设计与复杂度控制,以及最大的教训:不要相信后人的智慧。
首先上一张UML图(仅展示重要的方法)

最终架构为基本项架构,看起来非常简单对吧,是的,它确实非常简单,越简单的东西越不容易坏。
经历了hw1需要remake的惨痛教训后充分设计了可拓展性。
Function与其它因子有本质性区别,单独将其功能提出
因此,决定将其提出,并且在进Parser之前就完成代换工作。用一张图来展示它是如何构建的:

用了一个小递归(规模非常小)来轻松实现传参与代换,最后返回一个代换完成的新表达式。

首先就是惨痛的第一次迭代。第一次作业制作了一个简单的hashMap<指数,系数>的存储结构:
if (terms.containsKey(key)) {
terms.put(key, (sign.multiply(value)).add(terms.get(key)));
} else {
terms.put(key, sign.multiply(value));
}
这个架构最大的好处就是完全不用设计化简结构,直接用hashMap的一对一的特性完成化简。事实证明这个架构几乎完全不能拓展,它最大的好处变成了最大的劣势,存储结构倍锁死,完全无法扩展到新的因子。
解决方案——诶,大半remake。
在橘子bro助教的大力帮助下完成了对存储结构和化简方法部分的remake,也就有了本次作业的定型架构。
考虑到hw1拓展性的吃席,新的架构直接狂点拓展性。
对于化简方法的重构,选择了每次新加入Unit时就进行化简,保证Expr管理的始终是一个最简表达式。通过对Arraylist的两两遍历完成对是否可以合并的判断。为了可读性,这里返回一条消息(是的,就直接返回一条字符串,我自己都觉得抽象),告诉合并器到底是能不能合并,以及合并后是不是0.
值得一提的时判断两个表达式是否相等可以直接:
for (Unit unit : ((Expr) o).getFactors()) {
if (!this.factorList.contains(unit)) {
return false;
}
}
因为contains会去调用equals方法,而对于Unit和因子的equals,我们已经完成了重写,这样它就会按着我们的意图走。
这次作业由于重构花费了不少时间,但是exp的轻松添加以及第三次作业的轻松完成都说明了这个REMAKE是值得的。
基本没有动架构
如果有一个好的架构的话,本次作业应该在一小时内就可以完成。
性能优化我有两不做,性能太好的我不做,因为太累;性能太差的我不做,因为没用。
于是在考虑了性价比之后选择了进行因子判断去括号,以及公因数提取。
贴合基本项的架构选择用基本项的size来判断是否是因子:
public boolean isFactor() {
int size = 0;
if (!this.getCoefficient().getNum().equals(BigInteger.ONE))
size++;
if (this.getVarFactor().getExponent().compareTo(BigInteger.ZERO) > 0)
size++;
if (!this.getExpFactor().getExpr().getFactors().isEmpty())
size++;
return (size <= 1);
}
与此同时,利用提取公因数的方法进行exp内部的进一步化简,不必担心tle,因为BigInteger自带的gcd方法是辗转相除,时间复杂度为o(1),相比我们其它地方的耗时简直九牛一毛。为了防止负优化,我选择计算提取前后的可变长度,因为x和exp不管怎么都不会变短的,只需要管系数、*号、括号以及^即可,计算方法如下:
带x,e的项:可变长度 = 系数length + 1
常数: 可变长度 = length
if(只有一个基本项 && 该基本项是因子)
then 可变长度 + 0
else 可变长度 + 2
通过比较即可决定是否提取。
在写的时候因为深浅克隆搞出过不少乱子,尤其是addAll的浅克隆,一定要多加注意。
三次作业出现的bug共计1处,主要原因为将exp((-x))中的-x误当作因子。至于为什么有评测机也没测出来,是因为当时评测机没做完,只做了正确性检测,压根没测文法,于是就开席了,下次做评测机一定设计好工期(XD)。
hw3在hack时发现很多同学TLE,主要是因为:exp的cost非常非常小,非常令人费解。以至于第三次作业很多同学直接TLE,甚至overflow,可能是因为存储和运算结构没设计好吧。
每次都被分到铜墙铁壁房,不太走运,HACK体验不太妙,但是做评测机确实是一个很有意思的过程。
自hw2开始与LTC同学合作完成开源高性能自动化评测机。
with multiprocessing.Pool(processes=2) as pool:
async_result1 = pool.apply_async(compare, (jar_names, checker,))
async_result2 = pool.apply_async(compare, (jar_names, checker,))
try:
isSame1,stdin1 = async_result1.get(timeout)
isSame2,stdin2 = async_result2.get(timeout)
最大的教训:不能相信后人的智慧!!!
HW1偷懒快速完成作业时得意洋洋地说相信后人的智慧,结果几天后就吃到了回旋镖。
设计程序时一定要充分设想可拓展性,课程组用两个作业让我充分记住了这一点,HW1到2很难说不是精心设计过的,很棒的作业,给我带来了印象深刻的经验教训。痛定思痛设计出的HW2架构具有非常良好的可扩展性以及非常简单的总体结构,可以说是是非常令我自我满意了,但是细节上可以优化的还很多,期待下次作业写出更好的架构。
复杂度控制的设计更像是给代码结构减负,防止承担太多混乱工作,避免快速成为石山代码。本次作业虽然没有因此吃瘪,但是在之后越来越复杂的代码中,还是要尤其注意这一点。
很喜欢课程组HW1到HW2的过渡,但是建议课程组平衡一下HW2和HW3的难度,这两个工作量有点过于不均了。
感谢橘子bro学长对于架构的指导
感谢LTC同学主导设计的评测机
感谢课程组精心设计的迭代过程