BUAA OO第一单元总结——多项式求导

BUAA OO第一单元总结

第一次作业

在经过pre的实战后,笔者依然没有很好的面向对象的编程习惯,第一次作业要求简单,因子只含有幂函数,因此并没有设置很多类,只有简单的Ploy类,因此第二次作业直接重构了

UML类关系图

BUAA OO第一单元总结——多项式求导

实际上由于常数求导为0,笔者由于想偷懒就只是设置了一个Constant类但并没有将其加入到容器里,第一次作业也没有判WF的情况,所以就直接通过正则表达式匹配字符串(正则表达式放在Construct类中),再将匹配到的幂函数创建一个Ploy类并丢入PloyArray这个自定义容器(基于Hashmap,key为exp)中里进行合并。

基于度量的结构分析

代码规模

BUAA OO第一单元总结——多项式求导

本次作业由于较为简单,因此没有代码行数过多的类。

类复杂度

BUAA OO第一单元总结——多项式求导

PloyArray类和Construct类均有对容器遍历和容器内元素处理的操作,因此操作复杂度较高。

优缺点分析

  1. 优点

    • 第一次作业的架构可以说是主要为面向过程的思想,对于本次作业来说可以很好的完成要求并且符合正则表达式匹配的出发点

    • 同时Construct类将构造方法集为一体,并对幂函数类建立一个管理变量的类,也是面向对象的一种思路体现

  2. 缺点

    • 本次作业由于没有使用工厂模式,加上面向对象的思想并未很好的建立起来,导致在后续迭代版本时出现难以弥补的困境,以至于第二次作业几乎的重构

    • 同时对于字符串性能的优化,没有考虑将x**2替换为x*x导致在强测中的性能分为拿满

Bug分析

在强测中,并未被找出Bug,但在互测环节中被hack了一个较大的数据,原因为在优化过程中将正项放于表达式第一位输出的时候,为了方便判断,在Ploy类中加入了一个IsPositive的标签,对BigInteger对象的正负判断是通过toLongValue()的方法导致数据溢出产生Bug。

同时在自测环节,发现IsPositive标签经常未在系数发生改变时进行更新,导致出现不少Bug。

出于对后续迭代的考虑,并未删出该标签,导致Ploy类在toString()方法行数和圈复杂度较高。

互测策略分析

由于笔者水平有限,未写出对拍机/评测机,在互测中均为手动测试样例,侧重点主要为本次作业中容易出错的点包括对大数的存储和计算,以及正则表达式的匹配,尤其是连续相邻符号的读入判断。在互测屋内利用正负符号的连续情况hack了一位同学。而对于大数的有关处理,房间内只有笔者被hack了一个点快逃

因为只有一个,在这里给出被hack数据,供大家参考。应该是被dalao写的对拍机构造的:

--841738115753590071644*-7178070*x**8008674800*x*x**602-x**5436*x*98222585016701497757*219069808957276*x**2309829728887*-93961970*879*x**-332*148723412636058*29*623490672133*x**86617631*-2803050168396336*+52145214298369*+15323224321*x**+46278231088778430757*696550546*x+x*x**+1530001383*x*x**+9726893624*18470536657797*x**+532929558*210977403*3530*x**6413815700*x*+8502477709*x**82448148958*210416265649180197737+x*x**470035*-600026880347849758*x**+54163970*370370877495183383*-3917092263972451*356685*2639211*+568*x*x**+7339*11770-+305315530244360696*-580*x*x*+4035497404818305*+4557988*x**48*x*+544*-4340101960837*x**+87410282365101*x*9539516703997264*x*x*x**+2511847356701341*x*x**-3607*11*x**61912577015045934234*x

 

第二次作业

本次作业较第一作业有较为重大的改变,将详细介绍类及构造方法。

UML类关系图

BUAA OO第一单元总结——多项式求导

重要的类及方法介绍

  • Factor抽象类:

    • 包括重要的toString()derivation()求导方法

    • clone()方法继承自Cloneable接口。

  • Ploy类:

    • 表达式类

    • 继承自Factor抽象类,为了满足表达式因子

    • 重要方法有构造方法以及addTerm()方法,内含Term容器

    • 实质为加减关系

  • Term类:

    • 继承自Complexdiff接口,实现不同于Factorderivation()的求导方法——diff()

    • 重要方法有构造方法以及addFactor()方法,内含Factor容器

    • 实质为乘法关系

  • SinFac类、CosFac类:

    • 三角函数的构造类,继承自Factor

  • Power类:

    • 幂函数的构造类,继承自Factor

基于度量的结构分析

代码规模

BUAA OO第一单元总结——多项式求导

可以看出第二次作业相比于第一次作业代码量增加很多,但主要是集中在Ploy类和Term类中,这两个类因为是不同因子之间的关系即加减和乘除,所以有很多构造和合并方法,代码行数较多。

类复杂度

BUAA OO第一单元总结——多项式求导

  • 可以看出以上两类的加权复杂度很高,这两者都包含了字符串读入处理的方法,拉高了复杂度。

  • 同时Ploy类由于构造方法中有许多判断语句,因此平均方法复杂度也较高。

方法复杂度

BUAA OO第一单元总结——多项式求导 

  • 节选了复杂度最高的一些方法,可以看到最高复杂度的是Ploy类的构造方法。原因如下:

    • 本次作业有括号嵌套,同时三角函数也有括号,简单的正则匹配无法满足正确的读入。

    • 笔者不熟悉递归下降的方法,因此并没使用递归下降的方法。但现在看来思路与递归下降很像。思路大致为以下几点:

      1. 当字符串非空,则循环,否则结束,随后新建Term类对象

      2. 将字符串进行首个字符的判断,若符合某种特征,再用正则表达式匹配对应的因子,将因子parse进此时的Term

      3. 若遇到括号,则进行括号匹配,将括号中的字符串parse进一个新的Ploy对象,并将该对象parse进此时的Term

      4. 将匹配过的字符串删掉,继续判断

      5. 若遇到*,则跳转至2步骤继续循环

      6. 若遇到+-,则将上面得到的Termparse进该Ploy对象中,随后跳转至1步骤。

    • 本次作业没有判断WF,因此匹配方法写的较为丑陋和冗杂

  • 剩下的就是各种类的toString(),由于没有在内部实行更多的优化合并,因此为了稍微简化输出,在各类的toString()中使用了很多判断语句

优缺点分析

  1. 优点

    • 由于将第一次作业进行了重构,基于一个抽象类管理所有的因子,再基于Term类实现函数之间的关系,在一定程度上,实现了高内聚、低耦合的构造思想

    • 字符串匹配的方法符合递归下降的思路,为第三次作业WF判断提供了较大的便利

    • 对于乘除关系Term类的求导中,很好的实现了深克隆,根本上避免了复合函数求导过程中出现的问题

  2. 缺点

    • 依旧没有实现真正的工厂模式,没有一个工厂类返回各因子类,也没有一个统一的管理方式,使得某一些类中的方法较为混杂

    • Ploy类以及Term类的代码行数过多,复杂度也很大

    • 由于重构花了较多时间,对于优化代码性能即表达式化简部分几乎没有考虑和实现,其中的容器也用的是Hashset,并不适合进行优化合并,只对每个Term类的每个因子的系数进行了合并

Bug分析

  1. 自测

    • 在未对每个项中每个因子进行系数合并之前,会输出形如x*-sin(x)这种项,是因为在每个类中的toString()方法已经固定,调整因子的顺序显然无法解决,因此只能对系数进行合并。然而在合并的过程中,笔者出现了一般人不会出现的困惑,即将合并的系数放在Term类中变成所拥有的属性,还是将其随意丢入一个Factor类的原本拥有的系数属性中。一开始,笔者不想对原有的Term类架构进行改变,于是采用了后者,但是在后期debug过程中发现,这会导致复合函数求导过程中系数的混乱,若每次求导完成后都进行遍历合并,这显然会加重递归的复杂度。因此最后采用了前者。

    • 在递归构造的过程中,发现括号嵌套较多的时候,程序会运行过慢TLE,发现每次一个括号就会产生一层Ploy类。如果一个括号内只有一个项,若不去除多余的括号,就会导致函数的构造很深。因此笔者特判了该类型的情况去除了多于的括号。

  2. 强测

    • 由于新增三角函数类型,在对cos函数的derivation()重写的时候,新建三角函数类的符号位出错,导致在强测中没hack了三个点。

    • 由于构造架构的原因,在一些只有一个因子组成一个项的时候,并没有跳过项这一层,导致递归深度依旧很大,尽管在自测中已经优化了一种情况,但由于Term类并不是继承自Factor抽象类的,因此笔者在当时并没有想到很好的解决办法,时间紧迫就交了上去。因此又有三个点T了。

  3. 互测

    • 互测所被hack的代码都是以上两类bug

  4. 分析

    • 由于第一类bug是因为粗心导致的低级错误,所以在代码行数和圈复杂度上,修复之前和修复之后并没有什么差别。

    • 第二类bug是TLE,修复此类bug非常痛苦,如果无法很好的修复意味着自己的架构出现了严重的错误。

      • 笔者并没有自己找出很好的解决办法,又不想重构,所以采用了限制递归深度,换句话说就是括号嵌套到一定程度就拆括号来防止继续递归的方法。但是这样也带来了一些优化方面的问题,有兴趣的读者可以自行思考。

      • 最后笔者发现有很多同学也有类似的问题,最终在讨论区找到了答案,是每个类中的toString()方法中进行判断的时候,不断地调用下一层类的toString()方法,递归的情况下复杂度为几何倍数增长,在解决了这个问题之后TLE的问题也随之修复了。

互测策略分析

这次作业笔者依旧没有写评测机,但是由于此次作业的复杂程度较第一次作业增加许多,因此简单的构造测试样例显然不能符合需求。因此我查看了部分同学的代码,发现其构造结构与我的类似,因此我提交了一些在我自测环节中发现的bug,以及我自己可能出现的括号嵌套。

互测中部分数据hack掉了一些同学,同时修改自己的代码。

第三次作业

UML类关系图

BUAA OO第一单元总结——多项式求导

新增的类和修改的方法

  • 本次作业的架构与第二次作业的架构可以说是几乎一样,只是在三角函数类中新增了构造别的因子类的构造方法。

  • 同时新增了异常类FormatException类,继承Exception类,在第二次作业的Ploy的构造方法基础之上,对字符匹配的格式进行了相应的迭代,若遇到非法数据,则抛出自定义异常FormatException类,三角函数中的括号内的数据同理。

基于度量的结构分析

代码规模

BUAA OO第一单元总结——多项式求导

由于新增了格式判断和三角函数嵌套,在代码行数方面较第二次有部分增多

类复杂度

BUAA OO第一单元总结——多项式求导

  • 同样可以看出加权复杂度很高的类,由于都包含了字符串读入处理和格式判断的方法,拉高了复杂度。

  • 同时MainClass类、Ploy类中有许多构造关系的方法,因此平均方法复杂度也较高。

方法复杂度

BUAA OO第一单元总结——多项式求导

  • 同样因为三角函数的嵌套,笔者直接将三角函数内部的表达式解析放在了其构造方法上,导致二者的构造方法复杂度都较高。

  • 与第二次作业一样,一些parse方法和toString方法复杂度都较高,原因也不再过多赘述。

优缺点分析

  • 优点:

    • 本次作业最大的优点就是没有进行重构,也是第二次作业最成功的地方,虽然笔者的代码架构以及实现方法依然有很多不足,但第二次的架构足以满足第三次甚至更多的迭代需求,这也是笔者在这几次作业中的重要收获之一。

    • 同时重写了三角函数类的equals方法和hashCode方法,对求导后产生的多种新三角函数因子进行合并,减少输出的部分长度。

  • 缺点:

    • 第二次作业的部分缺点依旧遗留

Bug分析

  1. 中测

    • 中测中一直有一个点过不去,在自测环节中不断尝试,构造非法数据以及合法数据,找到了该点以外的bug,包括将合法数据输判断为WF

  2. 强测

    • 在强测中,有两个点均为格式判断的问题,且都与三角函数因子的括号匹配有关,形如sin()以及sin())。。

  3. 互测

    • 由于优化三角函数的合并过程中,对hashCode方法重写的策略不当,导致合并出现问题。

互测策略分析

这次作业笔者还是没有写评测机,按照第二次作业一样,我查看了部分同学的代码,研究其构造方法和格式判断方法,提交了一些在我自测环节中发现的bug,以及我自己可能出现的合法数据判WF的情况。

互测中部分数据hack掉了一些同学,同时修改自己的代码。

重构经历总结

个人认为,本单元最难的部分就在于第一次作业到第二次作业的迭代。由于第二次增添了括号嵌套,所以原有的正则表达式匹配将会变得非常困难,以及第一单元的架构问题,导致笔者在作业截至当天的上午才完成通过中弱测。重构前后具体的细节也已经在第一二次作业中阐述,此不再过多赘述。

但是,若需求增加更多的函数组合规则包括反函数、表达式因子作为幂函数的底甚至求偏导等等,笔者也不能说一定不会重构。因此,这也是笔者需要提升的地方。

心得体会

总结三次作业,收获的方面可分为以下两点:

  • 对于第一次进行较大规模的面向对象式的程序设计的挑战,笔者还算顺利的通过了本单元作业,也逐步理解了面向对象思维在程序设计中的重要性,也逐渐学习如何合理的构造自己的架构。

  • 对于java语言的内置容器、正则表达式、继承与多态,相较于假期的pre作业,更加理解和掌握。

遗憾的方面也可总结为两点:

  • 每次作业遗留下来的复杂度没有解决,包括方法之间的复杂度以及方法的逻辑不是很好,尽管代码风格以及命名可以起到很好的引导作用,但笔者也认为别人在看自己的代码时候可能还是会出现无法理解的地方。

  • 三次作业均没有写评测机,失去了学习自动评测的机会,也失去了强化python学习的机会,笔者会尽力在之后的作业中写出属于自己的评测机。

上一篇:BUAA_OO_第四单元


下一篇:BUAA_OO 暑假总结