301
社区成员
发帖
与我相关
我的任务
分享



Pre:将输入的已去掉空白字符的表达式中的所有自定义函数替换Lexer:词法分析,并将+/-(符号)转化成1 * / (-1) *,避免符号和运算符混乱Parser:语法分析,构建表达式树Expr,Term,Ex,Factor(interface):递归下降层次,均含toString()方法Expr:任何符合形式化表述的结构Term:外层不含+/-(运算符)的ExprEx:外层不含*的TermFactor:不含^/exp()的ExUnknown:变量Number:常数Derive:求导因子Cal:单项式类Polya:多项式类,含所有计算方法以及toString()方法优点: 采用递归下降,可扩展性强,实现Factor接口,统一管理,将预处理、解析与计算完全分离,实现高内聚低耦合,遵循单一职责原则。
缺点: 单项式的设计上仍然没有实现抽象化层次,违背开闭原则,若要实现sin(),lg()则改动较大。

Expr -> Term -> Power -> Factor(interface)Power处理以^为运算的结构,为二元运算,故多项式类要存在对应“乘方”运算public Poly pow(Poly other) {}
Factor接口只有Unknown(变量),Number(常数)toString()方法,返回后缀表达式Poly类中有一个HashMap,key为变量的指数,value为系数,即结果均可写成{value*x^key}的形式。定义加、减、乘、乘方方法,实现计算功能(Unknown,Number)实例化Poly类,再一一计算public Poly(String base) {
this.polys = new HashMap<>();
if (base.equals("x")) {
this.polys.put(BigInteger.ONE, BigInteger.ONE);
this.maxIndex = BigInteger.ONE;
} else if (base.equals("")) {
this.maxIndex = new BigInteger("-1");
} else if (new BigInteger(base).toString().equals("0")) {
this.maxIndex = new BigInteger("-1");
} else {
this.polys.put(BigInteger.ZERO, new BigInteger(base));
this.maxIndex = BigInteger.ZERO;
}
}
toString()方法,用于将最终计算结果转化为字符串输出
Pre类=分隔自定义函数表达式,再用正则表达式获取形参,并记录形参的顺序String[] postF = fx.split("=");
if (postF.length == 2) {
this.fx = postF[1];
} else {
this.fx = "";
}
String patternTerm = "f\\((.+)\\)";
Pattern re = Pattern.compile(patternTerm);
Matcher matcher = re.matcher(postF[0]);
while (matcher.find()) {
fp = matcher.group(1).split(",");
}
this.numF = fp.length;
for (int i = 0; i <= this.numF - 1; i++) {
mapF.put(fp[i], i);
}
major()主执行函数遍历表达式,依次拓印字符,若检测到f g h,则截取配对的f/g/h(*)字符串作为参数调用f/g/h()函数,该函数保证传入的f/g/h(*)调用自定义函数形式的字符串会返回完全不含自定义函数的替换字符串public String major(String input) {
StringBuilder str = new StringBuilder();
int pos = 0;
while (pos < input.length()) {
if (input.charAt(pos) == 'f' || input.charAt(pos) == 'g' || input.charAt(pos) == 'h') {
pos = pos + 2;
int tmp = pos;
int left = 0;
while (left != -1) { //配对括号
if (input.charAt(pos) == '(') {
left++;
} else if (input.charAt(pos) == ')') {
left--;
}
pos++;
}
int end = pos;
pos = tmp;
str.append("(");
if (input.charAt(pos - 2) == 'f') {
str.append(f(input.substring(pos - 2, end)));
} else if (input.charAt(pos - 2) == 'g') {
str.append(g(input.substring(pos - 2, end)));
} else {
str.append(h(input.substring(pos - 2, end)));
}
str.append(")");
pos = end - 1;
} else {
str.append(input.charAt(pos));
}
pos++;
}
return str.toString();
}
f/g/h()函数:为了保证加传入的字符串完全去除自定义函数,故采用递归调用major()函数的方法,替换时,将实参传入major()的结果去替换,直至实参不再含有自定义函数if ((c.equals("x") && (at == 0 || at == fx.length() - 1 ||
fx.charAt(at - 1) != 'e')) || (c.equals("y")) || (c.equals("z"))) {
str.append("(");
str.append(major(paras.get(mapF.get(c))));
str.append(")");
}
这样就不需要将x y z替换成特殊字符,避免一些bugPower类替换为Ex类,同时处理^,exp(),因为它们都属于指数范畴,运算的优先级相同,故集中处理Ex和Factor,属性mode标记三种情况,用于确定后缀表达式的格式:0 : Unknown / Number / (Expr) —— 本身
1 : a ^ b —— a b ^
2 : exp( c ) —— c e
以及一些形式分类:1. exp(a)^b
2. exp(c)
3. x^d
4. ()^e
5. Unknown / Number / (Expr)
Parser解析Ex时,通过括号配对以及正则表达式确定是哪个分类,并对mode赋值,只要是形式1 2,就反复调用parserEx(),否则调用parserFactor()退出1,则注意做标记,下次不再判定为1,避免死循环Polya、单项式类Cal。注意到结果均可表示成{a * x^b * exp(c)}Cal):x^b * exp(c)private Polya ex; //c
private BigInteger index; //b
Polya):<Cal,a>private final TreeMap<Cal, BigInteger> polys;
exp()public Polya exp() {
if (this.polys.isEmpty()) {
Cal cal = new Cal("3");
Polya polya = new Polya();
polya.addCal(cal, new BigInteger("1"));
return polya;
}
Polya polya = new Polya();
Cal cal = new Cal("3");
cal.setEx(this);
cal.getEx().string = cal.getEx().toString();
polya.polys.put(cal, new BigInteger("1"));
return polya;
}
Cal作为键值的比较,还要重写compareTo()方法public class Cal implements Comparable<Cal> {
//省略
@Override
public int compareTo(Cal o) {
if (this == null && o == null) {
return 0;
} else if (this == null) {
return -1;
} else if (o == null) {
return 1;
} else {
if (this.index.compareTo(o.index) != 0) {
return this.index.compareTo(o.index);
} else {
return this.ex.getString().compareTo(o.ex.getString());
}
}
}
}
这里的getString()原本用的toString(),但由于每次都要调用toString(),时间成本很高,故在Polya中添加字符串属性,记录其上一次toString结果,并在任何发生修改的地方更新该属性,用getString()方法获取其值TreeMap可以自动排序,保证返回字符串的唯一性private String string;
public Polya clone() {
Polya polya = new Polya();
for (Cal cal : this.polys.keySet()) {
polya.polys.put(cal, this.polys.get(cal));
}
return polya;
}
(类图同最终类图)
ArrayList<String> queue //记录顺序
if (!queue.isEmpty()) {
if (queue.get(0).equals("f")) {
fPre(fx);
} else if (queue.get(0).equals("g")) {
//省略
}
}
if (queue.size() >= 2) {
if (queue.get(1).equals("f")) {
fPre(fx);
this.fx = major(this.fx);
}
//省略
}
if (queue.size() >= 3) {
if (queue.get(2).equals("f")) {
fPre(fx);
this.fx = major(this.fx);
}
//省略
}
由于有序,后面自定义函数调用major()时,前面的自定义函数已经填置完毕fPre(),gPre(),hPre()均为hw_2中的初始化内容拆分,没有变化Factor接口,词法解析curToken为"d":if (lexer.pop().equals("d")) {
lexer.next();
Factor derive = new Derive(parseExpr());
lexer.next();
return derive;
}
Polya新增求导算法:public Polya derive() {
Polya polya = new Polya();
if (this.polys.isEmpty()) {
return new Polya();
} else if (this.polys.size() == 1) {
for (Cal cal0 : this.polys.keySet()) {
Polya polya0 = new Polya();//系数
//省略
Polya polya1 = new Polya();//对x^n求导项
if (cal0.getIndex().compareTo(BigInteger.ONE) >= 0) {
//省略
}
Polya polya2 = new Polya();//对exp(a)求导项
if (!cal0.getEx().polys.isEmpty()) {
//省略
}
return polya1.add(polya2).mul(polya0);
}
} else {
for (Cal cal : polys.keySet()) {
Polya polya0 = new Polya();
polya0.polys.put(cal, this.polys.get(cal));
polya0.string = polya0.toString();
polya = polya.add(polya0.derive());
}
return polya;
}
return new Polya();
}
x,增加y zPre完全不需改动,仍可完成自定义函数替换Lexer对解析变量部分稍加改动if (c == 'x' || c == 'y' || c == 'z') {
pos++;
curToken = String.valueOf(c);
ifOp = true;
}
Unknown原本就有name属性以及构造方法,不用改变Parser所有原来分析x的地方,都或上y z即可,如:if (lexer.pop().equals("x")||lexer.pop().equals("y")||lexer.pop().equals("z")) {
lexer.next();
return new Unknown(lexer.pop());
} //ParseFactor
Polya Cal:Cal新增属性,指数分为x y z的指数:private Polya ex;
private BigInteger xIndex;
private BigInteger yIndex;
private BigInteger zIndex;
compareTo随之做适当修改Polya对运算方法进行微调:(y z)相加即可仅有一处bug:对自定义函数表达式未做去空白符处理,公测时是做了处理的,但在改bug时误删了没有发现,样例中也没有这种情况,导致强测才发现。还是测试时捏测试用例不够耐心,没有覆盖很多情况。
仅有一处bug:词法解析时没有处理前导零
return new BigInteger(str.toString()).toString();
还是跟上面一样,测试不够严密。
exp()嵌套,过于频繁调用某种方法exp(-1)和exp((-x))的区别1省略;指数为1,将^1省略;正系数项提前;第一项若为正不加+exp()里括号的减省:exp()内部的多项式转化成字符串String s = cal.getEx().getString();
String patternTerm1 = "^(-?)(\\d*)\\*x(\\^\\d+)?$"; //matcher1
String patternTerm2 = "^(-?)(\\d*)\\*(exp\\(.+\\))(\\^\\d+)?$"; //matcher2
String patternTerm = "^-\\d+$";//负常数,不用加括号
通过括号匹配找外层运算符:
"^-\\d+$" -> return 0;
+/- -> return 1;
* -> keep 2; //上面两个到最后也没有再返回2
default -> return 0;
判定顺序:matcher1.find()或matcher2.find():系数绝对值取出拿到exp()外面做指数,系数若正,就省略括号,否则保留括号这几次作业我体会到了良好架构的重要性,可扩展性。通过作业以及研讨课对SOLID原则有了更深刻的认知。掌握了递归下降法。掌握了当Map容器键值是类对象时,如何重写compareTo()方法以实现键值比较。
此外,当面临需要重构或没有思路时,一定要敢于发散地去想,敢于试错,不存在完美的架构,只存在适合自己的架构,自己摸索出来的才是自己的,不要因为别人的架构好就盲目修改,更不要还没自己构建无脑效仿别人,这就失去了这门课的意义。