OO_Unit1_Blog
优点:简单,拟合度高。
缺点:每个因子有许多无用属性,耗费大量空间。
Part1 三次迭代
第一次作业
一、表达式解析
在处理前,先将表达式 化简 ,包括删除空白字符,合并连续加减号两步。
对表达式组成的解析通过 递归下降 的方法实现。
第一步,构造词法分析器(Lexer),将表达式字符串拆分为一系列最小单元。如下所示。(通过Lexer类中的方法实现)
第二步,得到由最小单元组成的序列,根据作业给出的文法进行语法匹配。Factor类的子类为Expr和MonoFactor。其中,常数因子、变量因子都使用MonoFactor表示。(通过Parser类中的方法来实现)
public class Parser {
private Lexer lexer;
public Parser(Lexer lexer);
public Expr parseExpr(); //匹配表达式
public Term parseTerm(); //匹配项
public Factor parseFactor(); //匹配因子
}
第三步,在创建Term、Expr实例后,立即对Term、Expr进行 计算 与 初步化简 ,此时其储存的有实际使用价值的属性为polyTerm、polyExpr(即计算结果)。此步骤存在的 原因 是考虑到表达式的值终究是要计算的,所以我选择了尽早将其计算,除此以外没有其他特殊考虑。
public class Term {
private ArrayList<MonoFactor> polyTerm;
}
public class Expr implements Factor {
private ArrayList<MonoFactor> polyExpr;
}
二、表达式计算
- 因子相乘
- 项相加
关于这两个功能,我设计了两个类来实现。实际实现的是MonoFactor实例间的加法与乘法。
在化简表达式时,我选择使用 待化简表达式+0 的方式来实现,用于合并表达式中各种形式的0。
该步骤将每个表达式中的所有项化简,化简值由一个类别为 ArrayList<MonoFactor> 的列表,即一个 多项式 。(因为任意多个多项式间的加法、乘法运算的结果依然是多项式)
十分关键的,每个多项式中的项的基本形式为 a ∗ x b a*x^b a∗xb 。
public class MonoFactor {
private BigInteger coe;//a
private BigInteger exp;//b
}
三、表达式处理
为Expr增加一个属性expE,用于记录表达式因子的指数。
若表达式所包含的某两个项之间的运算符号为“-”,则将该运算符右边的Term进行取反,实质为对该Term对应的多项式中的每一个项进行取反。
对于表达式因子,其结果为对应表达式与自己相乘对应次数的结果。对于非因子的表达式,可以认为它是指数为1的表达式因子,以便统一表达式的处理。
四、输出化简
考虑系数为1或-1等特殊情况,对MonoFactor的输出进行化简,并且优先输出系数为正的项。
第二次作业
一、表达式解析——更新
对于新增的指数函数,我选择沿用第一次作业的思路,对MonoFactor进行修改,添加指数函数项。现在MonoFactor的基本形式为 a ∗ x b ∗ e x p ( e x p r ) c a*x^b*exp(expr)^c a∗xb∗exp(expr)c。之所以不使用Factor来存指数函数内部的因子,而选用ArrayList,是因为后者在判断是否为0时较为方便,有利于后续计算。当然这种突兀的储存方法也有其 弊端 ,我会在后续 分析自己程序的bug 部分讨论。
public class MonoFactor {
private BigInteger coe;//a
private BigInteger exp;//b
private ArrayList<MonoFactor> expr;//expr
private BigInteger expE;//c
}
对于新增的自定义函数,我新建了FunDefine类来实现。实现思路: 定义 自定义函数时读取函数名称与函数的表达式,生成FunDefine实例; 调用 时完成字符串替换后进行表达式解析,返回一个Expr实例。
由于一个自定义函数可能在多个场景被使用,所以不宜改动其属性。我选择了在FunDefine类中构建一个方法,用于接收实参并返回运算结果。
public class FunDefine {
private String name;//函数名称
private String expression;//函数原表达式
private ArrayList<String> formalParameters;//函数形参
public Expr process(ArrayList<String> actualParameters,/*实参*/
ArrayList<FunDefine> funDefineArrayList/*自定义函数列表*/) {
String expression = replace(actualParameters);//字符串替换
//表达式解析
Lexer lexer = new Lexer(expression);
Parser parser = new Parser(lexer);
Expr expr = parser.parseExpr(funDefineArrayList);
return expr;
}
}
关于这一段代码,process方法中传入的funDefineArrayList参数最初是我用于解析表达式而加入的,但仔细思考会发现,这么做能够满足函数定义中调用其他自定义函数的要求,为第三次作业中要求的微调铺平了道路。
二、表达式计算——更新
由于乘法运算改变的也只是两个指数函数间的乘法,实质上是它们的指数因子的加法,所以本次更新的点为加法运算。
幂函数部分的相等判断十分简单,仅需判断指数的值是否相等。
指数函数部分,考虑到同一表达式可能有多种表达形式,如 2 ∗ x 2*x 2∗x 与 x + x x+x x+x, x 2 + 1 x^2+1 x2+1 与 x 2 + 1 + 0 + 0 + 0 + 0 x^2+1+0+0+0+0 x2+1+0+0+0+0 这类。所以我在Add类中构造了一个判断多项式是否相等的方法。先将表达式化为最简(我所认为的最简),再对两个多项式中的项判断是否都能在另外一个多项式中找到等价的项,递归判断。
public class Add {
public boolean isSamePoly(ArrayList<MonoFactor> a, ArrayList<MonoFactor> b);//判断多项式是否相等
public boolean isSameMono(MonoFactor a, MonoFactor b);//判断项是否相等
public boolean isCommonMono(MonoFactor a, MonoFactor b);//判断项是否为同类项
}
三、表达式处理——更新
为了方便运算时判断指数函数的指数因子是否相等,将指数函数的指数放进指数因子内。
自定义函数处理完后返回的Expr也是因子(Expr为Factor的子类),所以其他处理与第一次无异。
四、输出化简——更新
第二次作业时并没有提取指数函数内的公因数,化简策略与第一次作业相比并没有改进。
第三次作业
一、表达式解析——更新
第三次作业新增了求导因子dx。对于求导因子,我对他的处理和指数函数这类包含表达式(或因子)的因子类似,即遇见该类因子后,利用parseExpr方法对其内部的表达式(或因子)进行匹配,再根据该因子的要求对其内部的表达式进行处理,然后返回该因子。
lexer.next();//dx
lexer.next();//(
Expr expr = parseExpr(funDefineArrayList);
lexer.next();//)
···
return expr求导后的值对应的表达式;
二、表达式计算——更新
本次新增了求导运算。我对Expr进行了如下求导操作。
涉及乘法法则和链式求导法则,递归使用求导方法。
三、表达式处理——更新
无特别更新或改动。
四、输出化简——更新
本次作业由于由于一些问题(在bug部分会具体解释),导致时间紧张。所以输出化简功能完成得十分简陋,仅能将指数函数的指数因子为 多个 MonoFactor时才能提取它们的最大公因数,未能完成数字与幂函数或指数函数相乘时的系数提取,也未能实现提取简单的公因式,如提取 x x x , x 3 x^3 x3 等。
关于多个项提取公因数的实现,利用辗转相除法实现两个数之间的提取公因数,再使用该方法将两个数的处理结果与另一个数进行处理,以此类推处理完所有数,得到所有数之间的公因数。
关于提取简单公因式的实现,我认为通过找幂函数的最小指数就可以判断提取什么,实际操作应该较为轻松。
Part2 Bug分析
第一次作业中我的bug是带符号的整数,以及幂函数前有符号的情况。由于我消除了连续正负号,并且认为除了必要的加减号之外的正负号是包含在因子之内的。但是我一开始只考虑了数字前带正负号的情况,忽略了正负号后面是其他因子的情况。所以遇到 + x +x +x , − x 2 -x^2 −x2 等情况时,会出现错误。并且强测和互测中都没有问题。
第二次作业中,课下完成时没有遇到什么困难,虽然花的时间比较多,但没有遇到大问题。互测中没有被他人hack,在强测中 (((((((((((x8)8)8)8)8)8)8)8)8)8)8)8 这个数据出现了CPU_TIME_LIMIT_EXCEED问题,后来通过 “IntelliJ Profiler” 方法分析了我的程序,发现是因为我会在每次乘法操作得到 result 后,都会利用 0 + result 的方法对 result 中的项进行合并。就是这一步导致了运行时间过长。将其删除后,把化简留到最后执行,效率提高了。
第三次作业中,在对exp求导时,由于指数因子不是一个Expr,而是与其等价值的ArrayList,所以对ArrayList的求导无法统一,模块化程度降低。第三次作业的强测与互测中均无问题。
Part3 互测中对他人Bug的发现策略
构造特殊的数据,如:前导零、带符号因子、带指数的表达式因子、指数为零、多重嵌套等。
观察他人代码,针对模块化低、代码复杂的部分进行观察。
Part4 优化
将所有因子用同一种格式存储,耦合度高。
多处地方进行合并同类项,减少了无用项之间的计算,提高了运行速度。
Part5 心得体会
不能取巧对一些功能进行特殊化处理,一定要保证每项功能都有普适性。
要充分利用java语言的特性,如接口、父类等等,从原本c语言的面向过程编程思维转换到面向对象编程思维。
多在线下做测试,充分利用test。
Part6 未来建议
p;将所有因子用同一种格式存储,耦合度高。
多处地方进行合并同类项,减少了无用项之间的计算,提高了运行速度。
Part5 心得体会
不能取巧对一些功能进行特殊化处理,一定要保证每项功能都有普适性。
要充分利用java语言的特性,如接口、父类等等,从原本c语言的面向过程编程思维转换到面向对象编程思维。
多在线下做测试,充分利用test。
Part6 未来建议
可以适当展示一点优秀的作业架构,让我们能够从中学习。