301
社区成员
发帖
与我相关
我的任务
分享本次面向对象设计与构造的第一单元内容是对表达式的化简,包括加减乘法运算、幂函数、指数函数、自定义函数及求导运算,最终实现表达式括号展开,以及化简输出。
在指导书中我了解到递归下降这一核心概念,正则表达式法,对于容器等的使用,以及词法分析和文法分析等重要概念,可以通过Lexer类和Pasrer类将表达式解析为表达式、项、因子这一基本层次结构,最终完成表达式的化简工作。

通过预习指导书,我了解到递归下降这一存储表达式的方法,通过Pasrer类中parseExpr、parseTerm、parsePower、parseFactor方法递归调用完成表达式的层层解析,虽然正则表达式也能够完成第一次作业,但是其复杂阅读性差,且不能够很好的完成后续作业的迭代任务,所以本单元的作业核心还是采用递归下降这一方法,在后续计算的工作中,我借鉴了 Training1 的思路——后缀表达式法,通过栈完成计算。总体来说,在三次作业的迭代过程中,我均采用了递归下降加后缀表达式的主体架构。
存储设计:
interface Factor实现。(具体结构如下图所示)
x。 词法分析类(Lexer)
+ -换成一个,^后的+删去。0.语法分析类(Parser)
(则调用parseExpr解析后面遇到的表达式。运算类(Operator)
Stack栈类存储基本项a*x^n,出栈调用运算方法,再将结果入栈最后输出。x,我们完全可以用Hashmap<Integer,BigInterger>这一容器完美的解决计算问题,将指数作为key、系数作为value,在将来的合并同类项中,只需比较key检索合并value系数。(建议在第一次作业中就将指数设置为BigInteger类型,避免第二次作业因指数过大而爆掉)。遇到则表达式入栈,遇到运算符则出栈,并调用运算方法计算再将结果入栈,最终输出栈顶表达式。 -号的处理。阅读指导书我们知道在第一个因子之前,可以带一个正号或者负号,这就会导致多个运算符连续出现的情况,例如x*-x、2*(-x+1)。起初我的解决方法是在进行词法分析时,连带因子前面的符号一起解析,即存入正负数和正负x,看似解决了问题,但是我忽略了-(这种情况,导致强测出错并在互测中被多次hack。最终我选择在表达式字符串预处理阶段将连续符号进行处理(例如str = str.replaceAll("-(", "-1*(")),达到了一劳永逸的成效。在后续的迭代过程中,只需对新加入的符号进行类似的预处理就能避免该类bug。
在第二次作业中引入了指数函数因子、自定义函数因子以及多层嵌套括号。
其基本项变为a*x^b*exp(Expr)
首先对于多层嵌套括号,由于第一次作业采用了递归下降法,能够解析多层括号,所以我们的架构天然的解决了多层嵌套括号的问题。
接下来是指数函数因子,我定义了一个Exponent类存储指数函数因子,该类属性仅包含Factor因子类。
public class Exponent implements Factor {
private ArrayList<Factor> factors;
}
我的思路是重写toString,将exp作为一元运算符输出到后缀表达式中(factor1 exp)在后续的计算中进行处理。
最后是自定义函数的处理,通过定义Function类存入定义的表达式,正则表达式法解析输入的自定义函数字符串
public class Function {
private final HashMap<String,ArrayList<String>> functions;
...
Pattern myPattern = Pattern.compile("([fgh])\\(([uvw,]+)\\)=(.+)");
}
f(x,x^2) -> x+x -> 2*x^2。因此为了解决这一问题,我们干脆将形参x,y,z全部用u,v,w代替(注意exp中的 x )。 本次引用 Poly 这一算子类,该类包括ArrayList存储 Letter类,而其中的Letter类用于存储每一个基本项的系数(BigInteger)、幂(BigInteger)、exp指数(Poly)。Letter为一个基本项a*x^b*exp(Poly),而Poly即为多个基本项的和,注意由于exp指数还可能是表达式的嵌套,所以将指数部分定义为Poly类,即Poly类和Letter类的嵌套循环。
public class Poly implements Cloneable {
private final ArrayList<Letter> letters;
...
}
public class Letter implements Cloneable {
private BigInteger coe; //系数
private BigInteger exp; //幂
private Poly poly; //指数
...
}
运算方法包括
public boolean equals(Poly poly) {
if (this.letters.size() != poly.getLetters().size()) {
return false;
}
for (Letter i : this.letters) {
...
}
return true;
}
public boolean equals(Letter letter) {
if (!this.coe.equals(letter.getCoe())) {
return false;
} else if (this.coe.equals(new BigInteger("0"))) { //递归终止条件
return true;
} else if (!this.exp.equals(letter.getExp())) {
return false;
} else {
return this.poly.equals(letter.getPoly());
}
}
for (Letter i : clonePoly1.getLetters()) {
for (Letter j : clonePoly2.getLetters()) {
if (j.getExp().equals(i.getExp()) && j.getPoly().equals(i.getPoly())) {
...
}
}
}
至此我们解决了HW2的全部难点,但是在我测试样例的时候发现了一个奇怪的bug,就是在powPoly的运算中,我通过调用mulPoly方法进行计算,本应与乘法计算结果相同才对,但是却差距甚大。起初我猜想可能是乘法因子在计算过程中参与计算使其值发生改变,但很遗憾并不是这个问题。我最终发现其罪魁祸首便是深浅克隆问题,在java中对象的赋值是引用赋值,即浅克隆,克隆的是对象的地址,所以在powPoly计算的过程中引用了addPoly方法改变了它的值,所以在乘方的过程中,因子一直在改变导致了结果的错误。
Poly clonePoly1 = new Poly();
Poly clonePoly2 = new Poly();
// 深克隆 poly1
for (Letter letter : poly1.getLetters()) {
clonePoly1.addLetter((Letter) letter.clone());
}
// 深克隆 poly2
for (Letter letter : poly2.getLetters()) {
clonePoly2.addLetter((Letter) letter.clone());
}
...
@Override
public Object clone() throws CloneNotSupportedException {
return (Letter) super.clone();
}
@Override
public Object clone() throws CloneNotSupportedException {
return (Poly) super.clone();
}

在第三次作业中新增了允许调用已定义的自定义函数和求导算子,我们的架构天然满足新增的自定义函数要求,所以本次作业只需着眼于求导算子。
由于架构基本成型,本次作业的代码修改量很小,仅增加了Derivation求导类。
dx作为一元运算符输出到后缀表达式中。dx运算符时,对栈顶表达式poly进行求导运算。 


