BUAA_OO_第一单元总结
OO第一单元总结
1、作业要求回顾
hw1:完成对含有加减乘除的表达式的解析,最多只有一层括号,最后的结果是多项式;
hw2:增加了自定义函数与指数函数,括号可以变成多层;
hw3:增加了求导因子,自定义函数可以嵌套。
2、整体架构分析

上述为我最后一次作业的UML类图,白色框为第一次作业的类,蓝色为第二次新增,红色为第三次新增。
我在整个单元的搭建中,并未进行重构,一直延续第一次架构,整体下来较为平滑。下面介绍我三次的迭代思路。
分析每次作业要求,我们不难发现,因为最终结果是化简表达式,根据题目文法以及其数学性质,我们很容易推断出其实我们的作业对于表达式最后的要求如下:

所以可以将最后求和的项看成我们的结果,作为一个类,不断化简成这种形式便可。而我的Mono与Poly类便承载了这个功能。接下来我对每次作业分析,阐述整个构建过程。
3、迭代过程
3.1 hw1
递归下降的使用:
第一次作业为我们引入了一种新的解析字符串的方法,递归下降法。由于表达式具有一定的文法规则,我们可以根据语法规则,进行拆分,一层一层递归分析。为了使用这个方法,我建立了Parser,Lexer和Token三个类,负责进行文法的解析,后面两个主要用来记录表达式的组成,存下来后放入Parser中递归解析。
按照文法,表达式由表达式因子,幂函数与常数因子组成。因此我在里面建立了一个因子的接口,并将其连接到三个种类的因子(类的名称末尾含F的文件,代表Factor)。不过Expr与ExprF实际上可以归为一类,但出于好理解的考虑,我将其分开。因此第一次作业的分析部分完成,这个结构也一直延续到后面的作业,只需增加因子种类即可。字符串的预处理:
由于输入的表达式一开始并不符合我们人对于表达式的直观感受,我对其进行了一部分预处理。首先是去除空格与制表符,然后是将多余的符号去除,比如连续的正负号,统计负号的奇偶性,进行替换,同时如果正号是可省略正号,即不参与运算,也进行消除,那么传给解析类的符号其实很符合我们平常观察的表达式,更便于理解与解析。不过这个较为长的方法一开始我写在main函数里,后面进行了分离。最终结果的呈现:
如上一章节介绍,最后表达式的结果是有规律与特征的,因此我们只要把我们解析得到的那些因子,转成该形式即可。下面将被求和项叫做单项式,求和的结果叫做多项式。
多项式通过单项式容器形成,彼此之间通过加号连接。因此主要的属性操作在单项式中进行。我们不难发现,单项式的未知值只有系数与指数,我们只要将其记录下来即可。而对于常数因子,系数为自己,指数为0;对于幂函数,系数为1,指数为其次数;对于表达式因子,递归进行解析。以上便可以把单项式全部获得,然后根据规则打印连接即可。
3.2 hw2
架构改变
首先由于输入的自定义函数也需要进行预处理化简,因此我上次作业的预处理函数需要重复使用,不过因为只进行符号于空格处理,无需修改。考虑面向对象设计的性质,我将预处理单拿出来,形成Preset类,进行预处理多次调用。因此在输入时,便可以将处理好的自定义函数表达式存下来。便于后面替换。
其次,因为加入了自定义函数与指数函数因子,我新加了两个相关的因子类,与因子接口相连。
最后,因为exp的加入,我们的单项式形式也发生了改变,加入了exp项,其内部所存的是一个表达式因子,也就是多项式的形式,因此我们的结果从原来的单方面聚合,变成了双向的嵌套。自定义函数解析
我采用了较为朴素的字符串替换,在解析过程中,读到函数名时,对其实参进行解析(与表达式因子解析相同),然后进入函数因子中进行替换,在替换时要注意exp与x参数的相关处理。最后将结果转成字符串,放到解析器中再一次解析,成为表达式因子,然后转成多项式。不过这个过程比较繁琐,但写起来很简短(会造成效率丢失就是了)。指数函数合并方法
上文说到,exp的内部存的是多项式,我们进行合并时,要对其内部进行相等判断。我采用的方法是遍历每一项,只有每一项都对应相等时才相等。这个的时间效率是很低的,不过介于多项式的性质,我认为判等的性质都是相同的,在容器存取效率以及可能会有部分算法完成优化。
3.3 hw3
架构改变
新建了求导因子类,里面存的是表达式因子。求导方法
我的求导也是对单项式操作,并不是按照上课的方法进行,因为单项式的求导是有固定公式的,按照求导的链式法则以及指数函数,幂函数的求导法则书写即可,对指数与系数进行操作即可。
3.4 进一步扩展(迭代新情景)
总的来说,我的整体思想放在了单项式与多项式的形成上,可以说其他的类均是一种存储形式,只有这俩个类才是我进行操作的类,在他们身上完成所有的计算。该架构的好处是有统一规律,耦合度低,不过也会导致单项式与多项式所承担责任过重,占比过大。
不过它依然在表达式方面具有较强的拓展性,比如加入新的函数,三角函数等,它只需在单项式上加入三角函数的属性,按照三角函数的规则操作即可。
4、代码复杂度分析
4.1 代码规模分析


如上文所说,单项式Mono与多项式Poly是我两个主要的操作类,也因此他们承担了大多数的计算方法与操作方法,占比较大。解析器由于多层递归嵌套解析,占比也比较大。
4.2 代码复杂度分析

这是所有类的复杂度分析,复杂度较高的几个类,因为其循环调的次数较多。对于解析类,需要不断读取字符,进行选择,因此复杂度较高。对于多项式类,其需要进行遍历Mono以及循环计算等,复杂度也较高。而Mono中由于exp的存在,它里面有Poly最为其属性,因此复杂度也同样高。

这是复杂度最高的几个方法
Preset的handle方法用于预处理字符串,因为涉及循环以及多重嵌套选择,复杂度很高。
解析类的方法同理,不过是选择分支较多,加上循环读取字符,复杂度较高。
Mono与Poly中的打印类方法,选择结构的嵌套与分支较多,复杂度较高。
Mono中的myequal方法用于判断exp里面的多项式是否相等,进行双重循环,加上选择结构,复杂度较高。
5、bug分析
5.1 自己的bug发现
数据的构造
在第一次作业,我采用了python写了数据随机生成器,可以对程序的可行性已经抗压能力进行测试。主要方法是模仿递归下降,采用递归生成。对拍方法
首先对于已经犯错的数据,采用删项法,一项一项化简删除,最后找到错误核心,同时要用上IDEA强大的调试功能。
其次对于不知道的错误,采用讨论区提出的终极对拍法,将自己结果法与其他人的相减,看最后为零即可。后续
对于一些特殊点进行自己测试,比如exp(0)等,后面借用了大佬写的自动测评网站,为我解决不少bug。我出现的bug
在三次的作业测试中我并未出现bug,不过有一个bug是千钧一发改出来的,是关于指数的bigInteger,第一次因为种种限制, 不可能会超出int范围,但第二次出现了括号嵌套以及自定义函数等,指数会超过int,需要进行修改,且一定要修改完全,因为bigInteger与int的许多方法不同。
5.2 他人的bug发现
我的思路是先测试特殊样例,如(0)^0等,然后下载他人代码,进行研究。不过由于不同代码的风格不同,以及架构思路的定势思维,整体过程很低效与枯燥,中间也把大家的代码放到自动测评网站进行测评,不过可能由于大家都去跑了,也很难发现。所以最后我三次互测都未发现他人bug。
不过后面去看了讨论区,以及在我舍友的帮助下,可以从时间复杂度方面入手,通过多层的函数嵌套,很可能会使一些优化不好的同学,运行时间过长(原因应该是多次的递归调用导致)。
如下:

5.3 bug与复杂度
在课下书写时,一般我出现的bug,都是在复杂度较高的方法中,由于多重的循环以及特判选择结构等,很容易出错(比如在打印时对各种情况的特判)。
如何降低复杂度,我认为可以将一个长方法进行拆分,同时寻找每种情况中的同质性。我在我的代码中也采用了类似的方法。将打印exp指数函数与打印幂函数两部分分开,这样不仅便于调整,可读性也大大提高。
6、优化方法
我整体优化并无太多,只要按照文法输出,同时在尽可能的情况下完成合并,其实已经较为简短了,至于一些比较刁钻的优化方法,暂时没有头绪,也有对于时间复杂度的担心。下面介绍我进行的一些额外优化。
调整输出顺序
这个很好理解,优先输出系数为正的单项式,如-x+1与1-x,特别是在结果较短时,比重较大。exp内的括号的增删
其实就是判断exp里面存的多项式是否符合因子的文法规则。这个也很直观,只要里面的多项式非零项且系数不为1且(幂函数或者指数函数存在一个不是常数项即可)if (this.monoList.get(i).getCoe().compareTo(BigInteger.ZERO) != 0 && (this.monoList.get(i).getExp().compareTo(BigInteger.ZERO) != 0 || (this.monoList.get(i).getE() != 0 && this.monoList.get(i).getPoly().findNum() != 0))) { if (this.monoList.get(i).getCoe().compareTo(BigInteger.ONE) == 0 && this.findNum() == 1) { if (this.monoList.get(i).getExp().compareTo(BigInteger.ZERO) != 0 && (this.monoList.get(i).getE() != 0 && this.monoList.get(i).getPoly().findNum() != 0)) { flag = 1; } }如上
提取公因式
这个主要用于后两次作业,因为我前面计算时,将exp的指数全部化成了1,所以可能会导致最后的系数过大,如果能提出最大公因数,成为指数形式,可能会降低式子长度(是有可能造成负优化的,但是当时有点怠惰,没有加特判,第三次在一些地方碰壁了)
下面是求gcd的方法public BigInteger getGcd() { BigInteger gcd = new BigInteger("0"); for (int i = 0;i < this.poly.getMonoList().size();i++) { BigInteger coe = new BigInteger(this.poly.getMonoList().get(i).getCoe().toString()); if (coe.compareTo(BigInteger.ZERO) != 0) { if (gcd.compareTo(BigInteger.ZERO) == 0) { gcd = new BigInteger(coe.toString()); } else { gcd = gcd.gcd(coe); } } } return gcd; }因为Biginteger有内置方法,迭代求下去即可。
7、心得体会
整个第一单元由于在OOpre的帮助下,还是比较顺利的(非常推荐先导课,很有作用)。在这一章中,递归调用应该是我感受最深切的内容,一开始觉得难以理解,但熟练后,发现是一种很方便优雅的方法。除此之外,面向对象设计中面向对象的思想我也进一步了解与巩固,相对于面向过程,我认为它是更好理解的,各司其职,十分具象,也大大提高了我的兴趣。
虽然OO的课下作业过程有点繁琐与焦虑,但看着自己一步一步完成任务,获得结果,还是很有满足感的。
8、未来方向
希望能提供更多思路或者方法,尤其是在优化上,因为每个人基础不同,算法能力不同,希望课程组能给一些优化思路。
对于测试数据的内容更加详细,特别是在时间复杂度上,有时因为对这个没有概念,很难下手。
相关推荐
电梯调度java程序 buaa oo课程
程序能够模拟多部电梯调度,通过输入指令进行运算。可以处理电梯内请求,电梯外请求,顺路捎带,加入停止电梯。用java实现的,内有java工程,还有一份输入输出样例。
elevator3.zip_java_java编程_北航oo_北航电梯_电梯
北航第三次oo作业,在原来傻瓜电梯的基础上增加捎带功能
毕设&课设:自动测评 for BUAA Object-Oriented(OO) 北航 面向对象设计与构造 jav.zip
经导师指导并认可通过的大作业设计项目源码,适用人群:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业或毕业设计,作为“参考资料”使用。
毕设&课设:2017级北航计算机学院面向对象设计与构造作业.zip
经导师指导并认可通过的大作业设计项目源码,适用人群:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业或毕业设计,作为“参考资料”使用。
JEP30-PX100A PartModel Product Guidelines for Electronic-Device Substrates and Assemblies – XML Requ
JEP30-PX100A PartModel Product Guidelines for Electronic-Device Substrates and Assemblies – XML Requirements


