一、程序设计思路
1.第一次作业
第一次作业的多项式仅含常数因子与幂函数因子组成的项,具体结构如下:
Term类:描述多项式中一个项。
成员:
BigInteger power:x的幂次数
BigInteger Num:系数
方法:
Term(String): 构造函数,从字符串中读取系数与幂次数,并合并同类项。
Void Derivate(): 将本项求导,结果直接修改本项的参数。
Boolean equals(Term): 判断两项相同
Void add(Term): 将本项的系数与另一项的系数相加,结果直接修改本项参数
String toString(): 生成字符串
PolyDerivater类:入口类。描述一个多项式,和处理输入输出
成员:
LinkedList<Term> termList:保存多项式中所有的项
方法:
PolyDerivater(String): 构造函数,读取一个表示多项式的字符串中的项,合并同类项
Void Derivate(): 对链表中每一项求导
Void Main(): 入口函数,处理输入输出
Boolean checkSyntax(String): 使用正则判断字符串是否合法
第一次作业初步的体现了面对对象的编程思想,其中依然有设计失误,比如将主函数与多项式类合在了一起,这不利于程序的模块化。
2.第二次作业
第二次作业在第一次的基础上加入了sin(x)和cos(x)两种新因子。具体结构如下
Factor类:抽象类,是具体描述幂函数因子和三角函数因子的类的父类
方法:
Void combin(Factor): 将本因子的幂次与另一项的幂次相加,结果直接修改本因子参数
Term Derivate(): 对本因子求导,结果返回一个新的项。
String toString(): 生成字符串
ExpFactor, SinFactor, CosFactor类:继承Factor类,分别描述三种因子
成员:
BigInteger power:该因子的幂次数
方法:
xxxFactor(String): 构造函数,从字符串中读取因子的幂次
Boolean equals(Factor): 判断本因子与另一因子相同
Term类:描述一个项
成员:
LinkedList<Factor> factorList:保存项中的所有因子
BigInteger coff:保存项的系数
方法:
Term(String): 构造函数,从表示一个项的字符串中读取每一项,合并同类因子
LinkedList<Term> drivate(): 根据乘法法则对本项求导,结果返回为新的项的链表
void add(Term): 将本项的系数与另一项的系数相加,结果直接修改本项参数
public void multiply(Term): 将本项的系数与另一项的系数相加,本项的因子链表与另一项的因子链表相连接,合并同类因子,结果直接修改本项参数
String toString(): 生成字符串
PolyDerivater类:入口类。描述一个多项式,和处理输入输出
成员:
LinkedList<Term> termList:保存多项式中所有的项
方法:
PolyDrivater(String): 从表示多项式的字符串中读取每一项,合并同类项
void drivate(): 根据加法法则对本多项式求导,结果直接修改本多项式参数
String toString(): 生成字符串
第二次作业没有使用三元组描述项,而是用因子的集合描述项。这样的结构初步体现了多项式求导中递归的思想,保证了到第三次作业的可拓展性。并且尝试了用抛出异常的方式处理非法输入。
这次作业中暴露出一个严重的问题,是在因子与项的描述中可变对象。在第一次作业中可变对象描述项的危害没有体现,但在第二次作业中,由于项的求导使用乘法原理,求导结果中的每一项是对项中一个因子求导,再链接上项中其余因子所得的项,这造成了对象的复用,对第三次的作业造成的严重的困难。其余还有仍未将多项式类与输入输出分离,多项式类、项类、因子类之间依然存在很多相同的性质,但为对其进行整理等问题。
3.第三次作业
第三次作业在上次的基础上增添了多项式因子,并允许三角函数嵌套因子。具体结构如下
Factor类:抽象类,所有描述因子的类的父类
方法:
static Factor getaFactor(String): 分析字符串模式,调用相应的因子构造函数
void combine(Factor): 将本因子的幂次与另一项的幂次相加,结果直接修改本因子参数
Poly drivate(): 根据链式法则对本因子求导,结果为一个新多项式因子
String toString(): 生成字符串
bollean equals(Factor): 递归判断两因子是否相等
boolean isOne(): 判断该因子是否为常数1
boolean isZero(): 判断该因子是否为常数0
Factor clone(): 对因子递归进行深拷贝
ExpFactor,SinFactor,CosFactor,Poly,NumFactor类:继承Factor类,分别描述幂函数因子,sin因子,cos因子,多项式因子和常数因子
成员:
BigInteger power:表示因子的幂次数
Factor base:表示因子中嵌套的因子
方法:
xxxFactor(String):构造函数,从表示一个因子的字符串中读取因子的底和幂次数
xxxFactor(BigInteger, Factor):构造函数,对成员变量直接赋值。
Factor中抽象方法的实现
Term类:描述一个项
成员:
LinkedList<Factor> factorList:保存项中的所有因子
BigInteger coff:保存项的系数
方法:
Term(String): 构造函数,从表示一个项的字符串中读取每一个因子和系数
Term(LinkedList<Factor>, BigInteger): 构造函数,对成员变量直接赋值。
Poly drivate(): 根据乘法法则对本项求导,结果返回为新的多项式因子
boolean equals(Term): 递归判断两项是否相等
void add(Term t): 将本项的系数与另一项的系数相加,结果直接修改本项参数
void multiply(Term t): 将本项的系数与另一项相乘,合并同类因子,结果直接修改本项参数
String toString(): 生成字符串
boolean isOne(): 判断该项是否为常数1
boolean isZero(): 判断该项是否为常数0
Term clone(): 对项递归进行深拷贝
Main类:入口类,处理输入输出,对输入做初步的判断和预处理
static void main(String[]): 处理输入输出
static void syntaxCheck(String): 对输入做初步的判断
static String stringProcess(String): 对输入做预处理
第三次作业更明显的体现出多项式求导中的递归思想。多项式的结构呈树状,树的深度取决于因子嵌套的次数。程序的求导、判断相等、生成字符串、深拷贝等功能都是通过递归实现的。
很遗憾,做第三次作业时我没有从根本上解决可变对象的问题,而是试图用深拷贝的方式制作“不可变对象”。由于深拷贝需要通过递归实现,这种方式令我在处理嵌套层数多的多项式时花费大量时间进行拷贝,导致程序效率严重下降。
二、我遇到的bug
三次作业中,我遇到的bug分为三种:非法输入判断bug、可变对象的引用被分享的bug和运行时间过长问题。
1. 非法输入判断bug
第一次作业中由于可输入1000个字符,使用大正则一次性判断整个表达式可能爆栈。但第二次作业中输入长度缩短为100字符,让我想尝试用大正则判断输入是否合法,但在写正则表达式时落下了文首的空白字符,导致判断出错。复杂的正则表达式的危险性不仅在于爆栈的可能性,也在于可读性极差导致的可维护性极差上。
2.可变对象的引用被分享导致的bug
从第二次作业起,求导开始使用乘法法则,如( f(x) g(x) h(x) )’ = f’(x) g(x) h(x) + f(x) g’(x) h(x) + f(x) g(x) h’(x)。此时f(x), g(x), h(x)因子的引用就被分享给结果中的多个项,这时若对其中一项进行同类因子合并,由于我的合并方法直接改变因子对象的参数而不是返回一个新因子,被分享的因子就可能被修改,导致bug发生。虽然我以深拷贝的方式回避了这一bug,但若要从根本上解决,必须将Term和Factor修改为不可变对象。
3.运行时间过长
第二次作业互测时我因一组包含了很多连续空白字符的数据而TLE,这是由于我使用了大正则进行匹配,而其中空白字符匹配的效率很低。解决方法是对字符串进行预处理,将连续空白字符替换为一个空格字符,这种处理一定程度也能降低正则表达式的复杂度。
第三次作业中我因嵌套层数过多的多项式而TLE。这是由于我为回避可变对象问题而设置的递归深拷贝方法和我为合并同类项设置的递归判断相等的方法在占用了大量时间导致的。若要解决只能从根本上解决可变对象问题,降低相等判断的复杂度。
三、Debug方案
我没有根据结合被测程序的代码设计结构来设计测试用例,只是根据程序基本功能和自己在设计程序时遇到的可能出现的问题,如非法输入的各种情况、合并同类项等设计测试。
四、可能的优化方案
在第三次作业中,我由于递归深拷贝与递归判断相等的方法,导致算法效率低下,因此需要从这两方面进行修改。
1. 将可变对象修改为不可变对象
将所有修改自身属性的方法改为返回一个新的实例。这样的改动会使包含这个实例的链表的操作更复杂,但整体上,它消除了分享引用带来的风险,不再需要通过递归进行深拷贝。
2. 对链表进行排序
在判断两项或两多项式因子是否相等时我使用双循环,对两项(多项式)的链表中每一对内容进行递归判断,这很占用时间。为此考虑将链表中的元素按toString方式产生的字符串按字典序排序,这样可使相同的项(多项式)的字符串表示唯一化,不会因链表中元素顺序不同而变化,如此可将两项(多项式)的比较转化为两字符串的比较,且避免递归。