BUAA_OO_unit1

OO_unit1 单元小结

hw1

题目简述:根据形式化定义,化简包含+、-、*、^、()(其中括号的深度至多为 1 层)的单变量(x)表达式,输出恒等变形展开所有括号后的表达式。其中输出的字符串长度越小,性能越好。

架构

结合课堂讲解以及课程组所提供的代码讲解,程序大致分为了四个部分:词法分析表达式解析(语法分析)表达式计算(化简)表达式的输出(化简)。对于词法分析和表达式解析,结合课程组提供的代码demo,选择了递归下降法进行表达式解析。

根据形式化定义需求,构建了Expr类(表达式类),Term类(项类),Num类(常数因子),Variable类(变量因子)。同时我们注意到在因子中还存在表达式因子,同时,上述的类有很多共性,比如都应该具有实现计算的方法(toPoly),都应该具有转换成字符串的方法(toString)。所以特别设置了一个Factor接口,接口中定义了toPoly和toString方法。让上述各类都实施该接口,进一步抽象结构、方法,使程序更加简洁,更有oo的味道。

设置了Poly类(多项式)实现具体的计算。

而对于文法解析,Lexer类进行词法解析,Token类进行存储词法解析的内容,Parser类进行表达式解析。

同时,输入的表达式存在空白符,存在多个加减符号连在一起(最多三个),存在带符号的数字,针对这些情况专门设置Preprocess类进行输入数据的预处理,将空白符替换为空串,从而消去空白;将多个加减符号进行合并,将带正号的数字前的’+'号消去,从而得到一个新的较为“规范”的带化简字符串。

当得到计算化简结束后的字符串时,此时的字符串结果可能不是最短。为了进一步化简结果,又设置了Postprocess类进行进一步输出后处理,得到最终化简后表达式。

优化策略

  • 在toPoly的过程中,进行了合并同类项和去括号的初步优化。
  • 在toString后得到的字符串中可能存在’1*‘,开头可能存在’+'号,可能字符串为空串,以及结果不是最短。为了进一步化简结果,在Postprocess中消去了’1*‘和开头’+'号。同时,可能存在 − E x p r 1 + E x p r 2 -Expr1+Expr2 Expr1+Expr2这类情况,这样并不是最短的结果,所以采用一个较为朴素的做法,将遇到的第一个正表达式截取出来,放在最前,形成最终的表达式 E x p r 2 − E x p r 1 Expr2-Expr1 Expr2Expr1

第一种优化是很容易实现的,在我们toPoly过程中就已经实现。第二种优化策略是在最终toString后得到表达式字符串后进行的操作,不会对前序操作产生影响,所以可以保证正确性。同时因为是最终进行一次字符串的截取和拼接,可能代码不是很“优雅”,但是具有低耦合,高内聚,所以流程是比较清晰的。

程序度量分析

UML如图:其中根据形式化定义,为表达式、项、因子分别建立类,即ExprTermNumVariable类,用于存储解析后的表达式。而因为它们之间存在很多共性,所以设置了Factor接口,来统一管理这几个类,把这几个类抽象(统一)在一起。Lexer类用于存储词法分析后的结果。Parser类实现表达式解析。而Poly类实现了具体的多项式乘法、加法计算,PreprocessPostprocess类为预处理和结果后处理的类,方便程序进行解析。
hw1uml

度量分析

类数:13(枚举)

方法个数:实现了27个方法

代码量:lines1

依赖分析:dependency1

部分高复杂度方法:methodcom1

类复杂度:classcom1

可以看出复杂度主要集中在多项式的加减法以及词法分析、预处理和后处理。这说明在字符串层面的操作(遍历字符串等)是复杂度较高的,同时加减法涉及到比较合并等操作,所以复杂度也会相对较高。

Bug分析

hw1没有出现bug。

Hack策略

主要是基于讨论区的评测机进行hack(自己的评测机太弱了捏)。但可能有时构造的数据过于复杂,产生了TLE等问题,这种数据是无效的。在这一次(hw1)hack中,对部分他人程序进行了阅读,有一个程序没有预处理消除空白符,存在输入空白符结尾将会导致指针越界的问题,基于这种情况设计了样例进行hack。但是阅读他人代码进行hack效率较为低下,考验耐心和跳跃思维。

hw2

题目简述:在hw1的基础上进行增量开发,增加了自定义函数的定义以及指数函数、自定义函数调用。存在嵌套括号。根据形式化定义,输出恒等变形包含必要的括号的表达式。其中输出的字符串长度越小,性能越好。

架构

在hw1的架构下,为了进行更少的修改,采用了在解析前将函数调用进行替换的方式进行输入字符串的预处理,然后将不含有函数调用的字符串进行解析。而对于指数函数(exp),与其他因子处理方式相同,设置Exp类,同样使用Factor接口,实现toPoly和toString方法。

但是在hw1架构下,没有独立设置单项式类(Monomial),没有办法存储指数函数,所以需要增加Monomial类将单项式统一管理存储(这一点在研讨课已经讨论过)。同时因为增加了指数函数的加入,没办法继续用Hashmap让变量指数为键值的方式进行存储,改用Arraylist。所以这里有一点点重构(也可以说是增加)。这样就又可以将程序结构统一起来。

而嵌套括号问题,在hw1架构下以及实现,不需要单独处理。

因为存在自定义函数的定义和调用,预处理需要将函数调用进行替换,所以将输入处理设置为Input类,把之前Main里的输入,放在Input里,同时设置Function类进行自定义函数的存储。

优化策略

因为优化情况过于繁杂,我们追求的是长度的最短,对于指数函数笨人着实没法优化到极致(当然佬们会继续深入研究),就没有过于卷性能。主要基于hw1的优化策略下,采取以下几种简单的优化:

  • 在toPoly中的乘法方法里,并没有把所有指数函数的函数放进底数中,并合并为一个exp,而是将相同指数的指数函数相乘(合并),指数函数指数不同则不合并。
  • 在toPoly中的加法方法里,将指数函数的指数放进底数,进行底数比较,相同则相加合并。
  • 在指数函数toString的过程中,将底数因子中相同的常数因子提出,与指数函数的指数相乘(提出相同的常数,如无则不提取)。
  • 在指数函数toString的过程中,通过判断底数是否为表达式因子,消去不必要的括号,比如exp((x))可以化简为exp(x)

第一、二种优化方法是基于计算方式的,相对来说容易实现,也必须实现(因为要化简括号)。第二、三种方式主要在Exp中的toString实现,主要是在字符串层面进行操作。不会影响前序程序操作的正确性,相对独立。但是正是由于是对字符串进行操作,需要遍历字符串,同时在提取相同常数时,涉及很多判断以及转化。代码的简洁性不是很好。

程序度量分析

UML如图:在hw1的基础上,增加了Exp类,实现Factor接口,用于存储解析后的指数函数。设置Function类用于存储自定义函数的定义内容,设置Input类,将输入解析从Main类转移到Input类中。因为指数函数将改变单项式的形式,所以增加Monomial类存储单项式。
hw2uml

度量分析

类数:17个

方法个数:实现了60个方法

代码量:lines2

依赖分析:dependency2

部分高复杂度方法:methodcom2

类复杂度:classcom2

根据复杂度分析可以看见,高复杂度主要出现在递归调用多,字符串层面的操作以及多项式乘法、加减法。因为多项式使用了嵌套容器,效率更低,而涉及嵌套括号后,递归下降的层数也更深入,所以复杂度有所增加。

Bug分析

hw2强测过了,但是被hack了,问题出现在ExpoutExpoStr方法,提取相同数字操作上。原程序实现通过遍历字符串找到相同的数Num,然后通过replace方式将数字进行替换消除,很简洁。问题出现在这里。原字符串中可能存在不为系数的相同的Num,比如:Num为2,需要提取相同系数的字符串为2x+2x2+2*exp((2*x2+x)),这样就会把指数2,exp里面的2都替换掉,产生错误结果。

采取的解决方法就是使用最朴素的方式,进行字符串遍历,跳过exp函数,跳过指数数字。

bug修复前的圈复杂度如图:
pre

bug修复后的圈复杂度如图:
post

显然修复bug后的代码更复杂,因为进行优化的操作是在字符串层面上进行的,考虑的情况更多。若是要使代码方法复杂度降低,提高简洁性,可能可以考虑在toPoly之后,toString之前进行优化(可以专门设立一个类,或者实现方法),因为这样就不是在字符串层面上进行操作了,相对而言会更好。

Hack策略

hw2的hack主要借助评测机,hack出的问题主要为:

  1. exp内部未加括号:exp(-x)
  2. 未考虑到常数大小可能超过Integer,而运行时错误:exp(5555555555555555)

通过评测机还是能测出一些问题的,但是有效性没有很高。

hw3

题目简述:在hw2的基础上进行增量开发,增加了求导因子。根据形式化定义,输出恒等变形包含必要的括号的表达式。其中输出的字符串长度越小,性能越好。

架构

在hw2的架构下,在Factor接口中新增定义求导方法(toDerivation),然后在各类中实现。基于上机实验的代码思路,利用接口抽象这一利器,很容易实现求导功能。

对于自定义函数的定义时支持调用其他函数,在hw2的架构下稍加修改就可以实现(准确地讲是hw2实现时,没有把递归写好,有点粗糙)。

优化方法

在hw2的优化基础下,将提取指数函数底数中相同的常数系数这一策略,修改为提取公因数(主要考虑到BigInteger有gcd()方法)。

程序度量分析

UML如图:在hw2的基础上,没有增加类,求导在解析表达式时就进行。因为最终我们会将自定义函数以及求导因子进行化简或计算,使最终表达式不含有自定义函数和求导,单项式的形式中也不包含这两者,所以没有必要像其他因子一样设置专门的类进行存储。
在这里插入图片描述

度量分析

类数:17个

方法个数:实现了75个方法

代码量:lines3

依赖分析:denpendency3

部分高复杂度方法:methodcom3

类复杂度:classcom3

复杂度原因与hw2类似,同时因为涉及了自定函数可以相互定义,所以在替换函数时,也会导致递归过深,复杂度更高。

Bug分析

hw3没有出现bug。

Hack策略

这次几乎大家都没有bug了,毕竟经历了前两次的迭代修复,出现bug的概率极低。仍然主要采用评测机测试方法,进行hack。仍找出了一处bug:当求导、自定义函数、指数函数、幂函数进行多层嵌套时,最终结果会出现指数函数前的常系数丢失。

在经过多次迭代后,继续使用评测机进行hack的有效性将变得更低。而结束后看见讨论群里的hack数据,觉得其实主要还是通过评测机初筛找出问题后,根据问题根源进行手捏具有代表性的数据。

可扩展性

若是出现三角函数如sin(<因子>),只需要在程序架构下新建一个三角函数类,或者在三角函数类下面继续细分sin类,cos类等等。然后再这些类里实现Factor接口,新增三角函数的解析。可扩展性良好。

难点可能是计算和优化,计算方面需要将单项式的属性进行增加,包括了三角函数,乘法和加法需要对应修改。优化不一定能做到最优,但是应该能够进行部分优化。

心得体会

通过unit1的洗礼,学废了递归下降。

第一单元的学习还是很有收获的,hw1主要是对递归下降法的熟悉和理解,对表达式的化简计算的方法进行学习,为以后的迭代打下知识基础。而hw2、3主要就是考验大家的架构设计,以及java各种语法的了解。感觉有些问题属于java特性上的,比如深浅拷贝,容器,类型转换带来的精度损失等等。

在完成作业作业的过程中,不断积累经验,在研讨课的讨论中学习大家好的方法,在优化性能上进行深入的思考等等,都是很不错的学习机会,收获颇丰。同时课程组为我们提供的代码、讲解,实验课的代码提示,以及讨论群里的及时回复,都为我们完成第一单元的内容带来了许多帮助。

未来方向

整体设计还是很不错的。如果可能的话,希望课程组减小优化的比重,可以给出多个阈值,根据达到的阈值进行给分。感觉优化好难TT,跟面向对象好像关系不大TT

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值