301
社区成员
发帖
与我相关
我的任务
分享复杂度分析使用IDEA的MetricsReloaded插件
代码规模分析利用IDEA的static插件
对一个包含加、减、乘、乘方运算,包含单变量x,含有括号的表达式进行等价化简。
表达式解析:由于空白项无贡献,可以预处理将所有空白字符删去。后使用递归下降算法,构建Lexer类和Parser类,根据题面中给出的句法进行解析,这样子做的好处是预处理部分可以只删去空白而不需做更多操作。具体细节可以参考第一次训练中给出的代码。
表达式存储:注意到对于单变量多项式每一项重要的信息只有指数和系数,于是可以用HashMap<int,BigInteger>存储表达式中每一项,并且这样子存储可以自然地同类项合并。考虑到后续的需求添加,我选择新建一个BasicTerm类来存储一个项中非常数部分,也即上面的HashMap为HashMap<BasicTerm,BigInteger>类型,只需要在BasicTerm中实现hashCode与equals方法即可使用,这为后续的新增需求开发增添了很大空间。
表达式处理:本题中的运算有加法、减法、乘法与乘方,于是可以在Expr类中实现Expr.addExpr,Expr.mulExpr和Expr.exp等方法,在解析的过程中调用这些方法,边解析边处理。
表达式输出:在Expr类中实现toString方法,使用StringBuilder构建字符串,并在过程中对输出进行适当优化,如1*x优化为x。
上述过程的具体实现方式可以有很多种。


| method | CogC | ev(G) | iv(G) | v(G) |
|---|---|---|---|---|
| expr.BasicTerm._mulPow(String, BigInteger) | 2.0 | 1.0 | 3.0 | 3.0 |
| expr.BasicTerm.BasicTerm() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.BasicTerm.BasicTerm(BasicTerm) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.BasicTerm.equals(Object) | 3.0 | 3.0 | 2.0 | 4.0 |
| expr.BasicTerm.getPow() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.BasicTerm.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.BasicTerm.isEmpty() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.BasicTerm.mulBT(BasicTerm) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.BasicTerm.toString() | 5.0 | 1.0 | 4.0 | 4.0 |
| expr.Expr._addExpr(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr._addTerm(Term) | 2.0 | 1.0 | 3.0 | 3.0 |
| expr.Expr._neg() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr.exp(BigInteger) | 5.0 | 3.0 | 3.0 | 4.0 |
| expr.Expr.Expr() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr.Expr(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr.getTerms() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr.mulExpr(Expr) | 3.0 | 1.0 | 3.0 | 3.0 |
| expr.Expr.mulTerm(Term) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr.One() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr.toString() | 12.0 | 4.0 | 6.0 | 8.0 |
| expr.Expr.Zero() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Term.getCoef() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Term.getVar() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Term.mulTerm(Term) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Term.Term() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Term.Term(BasicTerm, BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Term.toString() | 5.0 | 1.0 | 4.0 | 6.0 |
| Lexer.getNumber() | 2.0 | 1.0 | 3.0 | 3.0 |
| Lexer.Lexer(String) | 0.0 | 1.0 | 1.0 | 1.0 |
| Lexer.next() | 3.0 | 2.0 | 2.0 | 3.0 |
| Lexer.peek() | 0.0 | 1.0 | 1.0 | 1.0 |
| Lexer.peekMatch(String) | 0.0 | 1.0 | 1.0 | 1.0 |
| Main.main(String[]) | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.parseExponent() | 2.0 | 2.0 | 3.0 | 3.0 |
| Parser.parseExpr() | 6.0 | 1.0 | 3.0 | 5.0 |
| Parser.parseFactor() | 8.0 | 1.0 | 4.0 | 5.0 |
| Parser.Parser(Lexer) | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.parseTerm(long) | 5.0 | 1.0 | 4.0 | 5.0 |
| Total | 63.0 | 47.0 | 71.0 | 83.0 |
| Average | 1.6578947368421053 | 1.236842105263158 | 1.868421052631579 | 2.1842105263157894 |
| class | OCavg | OCmax | WMC |
|---|---|---|---|
| Parser | 3.8 | 5.0 | 19.0 |
| Main | 1.0 | 1.0 | 1.0 |
| Lexer | 1.6 | 3.0 | 8.0 |
| expr.Term | 1.5 | 4.0 | 9.0 |
| expr.Expr | 2.1666666666666665 | 8.0 | 26.0 |
| expr.BasicTerm | 1.7777777777777777 | 4.0 | 16.0 |
| Total | 79.0 | ||
| Average | 2.0789473684210527 | 4.166666666666667 | 13.166666666666666 |
在我的架构下,代码平均复杂度维持在了一个较低水平。
int、long1*x,-1*x,-1+x,x^0,+1题目中给出了详细的数据句法,可以根据此句法生成符合条件的数据。我使用Python实现,只需要对每种类别新建一个函数并根据句法填充即可,涉及可能出现的可以使用Random或者Xeger库进行随机生成,Xeger库可以根据给出的正则表达式生成符合该正则表达式的随机字符串。
同时,为了限制括号层数,可以给expr函数传入参数dep,每次下降一层则dep-1,若dep为0则不允许因子为表达式因子。
样例代码如下:
from xeger import Xeger
import random
def addsub():
def space():
def blank():
def integer(l, r):
def signed_integer(l, r):
def exp():
def pow():
def expr_factor(dep):
def const_factor():
def var_factor():
def factor(dep):
fl = random.randint(0, 2)
if dep == 0:
fl = random.randint(0, 1)
if fl == 0:
return const_factor()
elif fl == 1:
return var_factor()
else:
return expr_factor(dep - 1)
def term(dep):
def expr(dep):
由于该数据生成器实现了所有句法,每一项随机选取生成方式,所以在数据量足够大(感觉上千应该肯定够了)的情况下可以相信已经遍历完了所有可能句法。
可以使用Python中的subprocess库调用.jar文件,获取程序输出。
可以使用Python中的eval方法对比足够多的点值进行答案检验,或者使用Sympy库进行检验。
相比于第一次作业,第二次作业新增了自定义函数与指数函数相关的内容,需要我们在第一次作业的架构上做一定调整。
在第一次作业中,我的主要类有Main,Parser,Lexer,Expr,Term,BasicTerm,其中Expr类为Term类的和,Term类有系数部分coef与非系数部分BasicTerm,在BasicTerm中存储幂函数以及用其进行相关运算。
自定义函数有2个部分,分别是读入和调用:
一个函数分为三个部分,函数名、函数参数与函数体,据此我们可以设计出处理函数调用的Funct类,其包含了函数名funName、函数参数vars与函数体expr,根据这3个参数,我们可以获取到所需要的信息。
函数名与函数参数的读入较为容易处理,关于函数体的读入,函数体本质上就是一个表达式,于是我们可以用Parser类中写好的parseExpr函数进行读入,这次作业在函数中新添加了yz变量,这部分的处理可以在BasicTerm中用HashMap<String,BigInteger>来存储幂函数,其中一个<String,BigInteger>对就代表相应的变量与指数,如<y,10>就代表y^10,这样就可以完成多变量的储存,修改parseExpr中对自变量的识别部分代码即可自然完成新增yz变量的需求。
调用部分我的方法是将其看做变量的替换,函数的调用实际上等价于将函数表达式中对应变量换成输入的表达式,如有函数f(x,y)=x*y,用f(x^2,exp(x))来调用它,那么实际上我们只需要把式子中的x换成x^2,y换成exp(x)即可完成函数的调用,得到x^2*exp(x)。
考虑如何实现变量的替换,我们由函数参数和传入的表达式可以得到变量的替换关系,该关系类似于一个从String到Expr的映射,于是我用HashMap<String,Expr>存储该关系,之后将其传入Expr类中进行进一步处理。
由于是对Expr进行变量的替换,于是自然可以在Expr中实现一个替换函数,其接受上面的替换关系,返回替换完成的表达式。在具体实现上,用递归实现是一个自然的想法,Expr包含Term,Term中又有系数部分coef与非系数部分BasicTerm,想要对Expr进行替换,只需要把替换的请求下传到Term中,将获得的返回表达式相加就是Expr的替换的结果。Term继续将替换下传到BasicTerm中,返回表达式相乘即是Term的替换结果。在BasicTerm中,由替换关系我们可以识别每个变量应该被替换成什么表达式,于是就可以进行替换操作,对于形如exp(expr)的形式,只需递归地调用expr的替换方法,就可以得到替换结果expr'与该项的正确形式exp(expr')。
由 $e^a*e^b=e^{a+b}$ ,我们可以在一个项中只保留一个指数函数而不破坏其等价性,那么我们可以在BasicTerm(存储一个项中非系数部分的类)中新增一个Expr类型的变量表示该项中指数的幂,后续只需在Expr,Term,BasicTerm这几个类之间的相加相乘运算中加入指数函数部分即可在不改变代码大框架的基础下完成这部分的任务。
比如要对两个BasicTerm作乘法,幂函数部分直接遍历相加,指数函数部分由上面的公式,结果的指数的幂就等于两个BasicTerm中指数的幂之和,伪代码如下:
BasicTerm BasicTermMultiply(BasicTerm a, BasicTerm b):
BasicTerm result = new BasicTerm();
for (i in a.getPow()) result.addPow(i);
for (i in b.getPow()) result.addPow(i);
result.setExpExpr(a.getExpExpr() + b.getExpExpr());
return result
由于题面允许指数函数后接指数,即允许exp(2)^10存在,于是可以用 $\exp(ax+by)=\exp(x)^a*\exp(y)^b$ 这一公式进行任意优化。但是在实际操作过程中,这一优化过程是较为困难的,涉及很多corner case,如:表达式exp((2+100000000*x+100000000*x^2+100000001*x^3+100000000*x^4))的最佳替换为exp((2+x^3))*exp((x+x^2+x^3+x^4))^100000000。以及另一位同学在评论区中提出的情况。
由于无法简易判断哪个化简方向较为优秀,较难设计一个多项式时间复杂度的算法,而搜索等算法则需要一些又臭又长的实现,同时综合了性能分占比等的考量,我选择摆烂,一切以简洁和正确性优先。


| method | CogC | ev(G) | iv(G) | v(G) |
|---|---|---|---|---|
| expr.BasicTerm.toString() | 19.0 | 1.0 | 9.0 | 9.0 |
| expr.Expr.toString() | 12.0 | 4.0 | 6.0 | 8.0 |
| expr.Expr.length() | 9.0 | 1.0 | 4.0 | 7.0 |
| Parser.parseExpr() | 6.0 | 1.0 | 3.0 | 5.0 |
| Lexer.next() | 5.0 | 2.0 | 4.0 | 5.0 |
| Parser.parseFactor() | 5.0 | 5.0 | 5.0 | 5.0 |
| Parser.parseTerm(long) | 5.0 | 1.0 | 4.0 | 5.0 |
| expr.Expr.exp(BigInteger) | 5.0 | 3.0 | 3.0 | 4.0 |
| expr.Term.toString() | 5.0 | 1.0 | 4.0 | 6.0 |
| expr.BasicTerm.equals(Object) | 4.0 | 3.0 | 3.0 | 5.0 |
| Parser.parseConst() | 3.0 | 1.0 | 2.0 | 3.0 |
| expr.Expr.isExpr() | 3.0 | 3.0 | 3.0 | 3.0 |
| expr.Expr.mulExpr(Expr) | 3.0 | 1.0 | 3.0 | 3.0 |
| Lexer.getNumber() | 2.0 | 1.0 | 3.0 | 3.0 |
| Main.main(String[]) | 2.0 | 1.0 | 3.0 | 3.0 |
| Parser.parseExponent() | 2.0 | 2.0 | 3.0 | 3.0 |
| expr.BasicTerm._mulPow(String, BigInteger) | 2.0 | 1.0 | 3.0 | 3.0 |
| expr.Expr._addTerm(Term) | 2.0 | 1.0 | 3.0 | 3.0 |
| expr.Term.isExpr() | 2.0 | 1.0 | 1.0 | 3.0 |
| Parser.parseFunct() | 1.0 | 1.0 | 2.0 | 2.0 |
| Parser.parseFunctFactor() | 1.0 | 1.0 | 2.0 | 2.0 |
| expr.BasicTerm.isOne() | 1.0 | 1.0 | 2.0 | 2.0 |
| expr.Expr.coefGcd() | 1.0 | 1.0 | 2.0 | 2.0 |
| expr.Funct.apply(ArrayList) | 1.0 | 1.0 | 2.0 | 2.0 |
| Lexer.Lexer(String) | 0.0 | 1.0 | 1.0 | 1.0 |
| Lexer.peek() | 0.0 | 1.0 | 1.0 | 1.0 |
| Lexer.peekMatch(String) | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.Parser(Lexer) | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.addFunct(String, Funct) | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.parseExpFactor() | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.parseExprFactor() | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.parsePowFactor() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.BasicTerm.BasicTerm() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.BasicTerm.BasicTerm(BasicTerm) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.BasicTerm._mulExp(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.BasicTerm.getExpVar() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.BasicTerm.getPow() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.BasicTerm.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.BasicTerm.mulBT(BasicTerm) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.BasicTerm.substitute(HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr.Expr() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr.Expr(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr.Expr(Term) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr.One() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr.Zero() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr._addExpr(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr._coefDiv(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr._coefMul(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr._mulExpr(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr._mulTerm(Term) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr._neg() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr.addExpr(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr.equals(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr.getTerms() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr.mulTerm(Term) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Expr.substitute(HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Funct.Funct() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Funct.addVar(String) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Funct.setExpr(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Term.Term() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Term.Term(BasicTerm, BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Term.getCoef() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Term.getVar() | 0.0 | 1.0 | 1.0 | 1.0 |
| expr.Term.mulTerm(Term) | 0.0 | 1.0 | 1.0 | 1.0 |
| Total | 101.0 | 80.0 | 120.0 | 137.0 |
| Average | 1.5538461538461539 | 1.2307692307692308 | 1.8461538461538463 | 2.1076923076923078 |
| class | OCavg | OCmax | WMC |
|---|---|---|---|
| Parser | 2.5 | 5.0 | 30.0 |
| Main | 3.0 | 3.0 | 3.0 |
| Lexer | 1.8 | 4.0 | 9.0 |
| expr.Term | 1.7142857142857142 | 4.0 | 12.0 |
| expr.Funct | 1.25 | 2.0 | 5.0 |
| expr.Expr | 1.9583333333333333 | 8.0 | 47.0 |
| expr.BasicTerm | 2.0 | 9.0 | 24.0 |
| Total | 130.0 | ||
| Average | 2.0 | 5.0 | 18.571428571428573 |
这次复杂度相较第一次反而有所降低,是方法总数变多导致的,同时降低也说明了我新增部分的实现不复杂。
在这一单元中,程序运行包含三个阶段,根据文法解析字符串,对表达式进行运算以及输出表达式。第3阶段实际上是不需要特别测试的,因为如果完成了对第一阶段的测试,那么第三阶段中所有可能用到的输出分支也基本走过一遍了。
在我看来,前2阶段的测试实际上就是有关文法解析的测试与极限数据的压力测试,文法解析的测试是比较好进行的,因为只要测试数据覆盖了题目中给出的文法,我们就可以确信对文法解析完成了测试,生成测试数据可以简单地根据题目给出文法写一个数据生成器,在生成足够多数据的情况下,我们可以相信覆盖了所有的文法。而在极限数据的测试方面,则可能需要手动构造一些数据,来测试相应的问题点,或者还可以直接用数据生成器生成大数据,也能达到一定的测试效果。
数据生成器的部分代码如下:
def expr(dep, depDist, functDist, vars, maxNum, maxExp, funGen):
"""根据参数生成表达式
Args:
dep (int): 当前深度
depDist (list int): 不同深度下停止的概率分布
functDist (dist {names: vars}): 可选函数
vars (list str): 可选变量
maxNum (int): 每一层最大项数
maxExp (int): 最大指数
funGen (bool): 是否正在生成自定义函数
Returns:
str: 生成的表达式
"""
这次作业在正确性检验方面具有较大的困难,随机数据生成器会生成形如exp(exp(73218947219))的数据,而这种数据Sympy解析速度非常之慢,同时也不太好用eval校验点值的方法进行检验。或者可以通过在生成器中加入一些限制如在exp中限制常数的大小来排除掉这类难以判断的数据,不过这会增加生成器的复杂性。不过就算不加限制Sympy仍然可以处理大部分的数据,依然可以满足测试的需要,所以我选择继续使用Sympy来做正确性检验,遇到上面的数据就大眼看一下对不对。
感觉这次作业没啥好说的,课程组难度放得还挺低的,连多变量求导都没有,随便来水写一点吧。
这次作业新增了允许函数嵌套和求导的内容,在较好完成第二次作业的基础上完成这两个部分应该是不难的,能在百行代码内搞定。
在上次作业中,我把自义定函数分为了函数名、函数参数与函数体三个部分,以及调用时的变量替换操作。在parser中储存时使用HashMap<String, Funct>存储对应函数名和函数类。
这次作业允许了函数嵌套,考虑其带来的影响,我们发现允许函数嵌套只会影响函数体部分表达式的解析,而其他部分是可以不用修改直接使用的的,于是你只需要在parser中边读入函数,边把该函数加入到parser的函数列表里,后再正常调用parseFucnt即可正确读入函数体。
伪代码如下:
// in main.java
Parser parser = new Parser();
for (String input : funcInputs){
parser.setInput(input);
String funcName = input[0];
parser.addFunc(funcName, parser.parseFunct());
}
parser.setInput(exprInput);
Expr expr = parser.parseExpr();
应用链式法则我们可以得到:
$$
\begin{aligned}
expr=\sum term & \Longrightarrow (expr)'=\sum(term)' \
term=a_ix^{e_i}\exp(expr) & \Longrightarrow (term)'=a_ie_ix^{e_i-1}\exp(expr)+a_i*x^{e_i}\exp(expr)(expr)'
\end{aligned}
$$
于是可以根据上述公式我们可以自然使用递归实现求导功能,伪代码如下:
// in Expr.java
public Expr deravative() {
Expr res = 0;
for (Term term : this.terms) {
res += term.deravative();
}
return res;
}
// in Term.java
public Expr deravative() {
Expr res = 0;
res += this.a * this.e * (new Term(1, this.e - 1, this.expr));
res += this.a * (new Term(1, this.e, this.expr)) * this.expr.deravative();
return res;
}
无新增类,和第二次作业一样

| method | CogC | ev(G) | iv(G) | v(G) |
|---|---|---|---|---|
| BasicTerm._mulExp(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| BasicTerm._mulPow(String, BigInteger) | 2.0 | 1.0 | 3.0 | 3.0 |
| BasicTerm.BasicTerm() | 0.0 | 1.0 | 1.0 | 1.0 |
| BasicTerm.BasicTerm(BasicTerm) | 0.0 | 1.0 | 1.0 | 1.0 |
| BasicTerm.chkExpr(ArrayList, ArrayList) | 4.0 | 2.0 | 1.0 | 5.0 |
| BasicTerm.derivative() | 2.0 | 1.0 | 3.0 | 3.0 |
| BasicTerm.equals(Object) | 4.0 | 3.0 | 3.0 | 5.0 |
| BasicTerm.gcd() | 2.0 | 1.0 | 2.0 | 2.0 |
| BasicTerm.getExpr(ArrayList, ArrayList) | 19.0 | 3.0 | 11.0 | 15.0 |
| BasicTerm.getExpVar() | 0.0 | 1.0 | 1.0 | 1.0 |
| BasicTerm.getPow() | 0.0 | 1.0 | 1.0 | 1.0 |
| BasicTerm.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
| BasicTerm.isOne() | 1.0 | 1.0 | 2.0 | 2.0 |
| BasicTerm.min(String, String) | 3.0 | 2.0 | 2.0 | 3.0 |
| BasicTerm.mulBT(BasicTerm) | 0.0 | 1.0 | 1.0 | 1.0 |
| BasicTerm.origin() | 2.0 | 1.0 | 2.0 | 2.0 |
| BasicTerm.pivot() | 19.0 | 4.0 | 8.0 | 10.0 |
| BasicTerm.sipmExp() | 11.0 | 5.0 | 5.0 | 7.0 |
| BasicTerm.substitute(HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
| BasicTerm.toString() | 10.0 | 2.0 | 7.0 | 8.0 |
| Expr._addExpr(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr._addTerm(Term) | 2.0 | 1.0 | 3.0 | 3.0 |
| Expr._divInt(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr._mulExpr(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr._mulInt(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr._mulTerm(Term) | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr._neg() | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr.addExpr(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr.coefGcd() | 1.0 | 1.0 | 2.0 | 2.0 |
| Expr.derivative() | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr.divInt(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr.equals(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr.exp(BigInteger) | 5.0 | 3.0 | 3.0 | 4.0 |
| Expr.Expr() | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr.Expr(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr.Expr(Term) | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr.getTerms() | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr.isExpr() | 3.0 | 3.0 | 3.0 | 3.0 |
| Expr.length() | 9.0 | 1.0 | 4.0 | 7.0 |
| Expr.mulExpr(Expr) | 3.0 | 1.0 | 3.0 | 3.0 |
| Expr.mulInt(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr.mulTerm(Term) | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr.One() | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr.substitute(HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr.toString() | 14.0 | 5.0 | 7.0 | 10.0 |
| Expr.Zero() | 0.0 | 1.0 | 1.0 | 1.0 |
| Funct.addVar(String) | 0.0 | 1.0 | 1.0 | 1.0 |
| Funct.apply(ArrayList) | 1.0 | 1.0 | 2.0 | 2.0 |
| Funct.Funct() | 0.0 | 1.0 | 1.0 | 1.0 |
| Funct.setExpr(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| Lexer.getNumber() | 2.0 | 1.0 | 3.0 | 3.0 |
| Lexer.Lexer(String) | 0.0 | 1.0 | 1.0 | 1.0 |
| Lexer.next() | 6.0 | 2.0 | 5.0 | 6.0 |
| Lexer.peek() | 0.0 | 1.0 | 1.0 | 1.0 |
| Lexer.peekMatch(String) | 0.0 | 1.0 | 1.0 | 1.0 |
| Main.isEnd() | 0.0 | 1.0 | 1.0 | 1.0 |
| Main.main(String[]) | 2.0 | 1.0 | 3.0 | 3.0 |
| Parser.addFunct(String, Funct) | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.parseConst() | 3.0 | 1.0 | 2.0 | 3.0 |
| Parser.parseDerivative() | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.parseExpFactor() | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.parseExponent() | 2.0 | 2.0 | 3.0 | 3.0 |
| Parser.parseExpr() | 6.0 | 1.0 | 3.0 | 5.0 |
| Parser.parseExprFactor() | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.parseFactor() | 6.0 | 6.0 | 6.0 | 6.0 |
| Parser.parseFunct() | 1.0 | 1.0 | 2.0 | 2.0 |
| Parser.parseFunctFactor() | 1.0 | 1.0 | 2.0 | 2.0 |
| Parser.parsePowFactor() | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.Parser() | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.parseTerm(long) | 5.0 | 1.0 | 4.0 | 5.0 |
| Parser.setLexer(Lexer) | 0.0 | 1.0 | 1.0 | 1.0 |
| Term.getCoef() | 0.0 | 1.0 | 1.0 | 1.0 |
| Term.getVar() | 0.0 | 1.0 | 1.0 | 1.0 |
| Term.isExpr() | 2.0 | 1.0 | 1.0 | 3.0 |
| Term.mulTerm(Term) | 0.0 | 1.0 | 1.0 | 1.0 |
| Term.Term() | 0.0 | 1.0 | 1.0 | 1.0 |
| Term.Term(BasicTerm, BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
| Term.toString() | 7.0 | 2.0 | 5.0 | 8.0 |
| Total | 160.0 | 109.0 | 162.0 | 195.0 |
| Average | 2.0253164556962027 | 1.379746835443038 | 2.050632911392405 | 2.4683544303797467 |
| class | OCavg | OCmax | WMC |
|---|---|---|---|
| BasicTerm | 3.2 | 12.0 | 64.0 |
| Expr | 1.9259259259259258 | 10.0 | 52.0 |
| Funct | 1.25 | 2.0 | 5.0 |
| Lexer | 2.0 | 5.0 | 10.0 |
| Main | 2.0 | 3.0 | 4.0 |
| Parser | 2.357142857142857 | 6.0 | 33.0 |
| Term | 2.0 | 6.0 | 14.0 |
| Total | 182.0 | ||
| Average | 2.3037974683544302 | 6.285714285714286 | 26.0 |
这次复杂度相较第二次作业有了一定提升,其原因是我在这次作业中加入了一些输出优化,导致toString方法复杂度过高,进而拉高了平均复杂度。
在第二次作业中,部分同学在输出的时候产生了错误,不符合题目的输出条件,会输出形如exp(-x^3)这样子的表达式,但是输出格式不能通过判断表达式等价判断出,需要新的方法。
实际上,题目的合法输出是符合一定文法的,我们只需要写出输出的文法,后用递归下降分析输出表达式,就可以判断输出表达式是否符合了题目的要求。
以下是我总结的输出文法:
$$
\begin{aligned}
expr ::=&, [\pm]term ,|, expr \pm term \
term ::=&, [\pm]factor ,|, term* factor \
factor ::=&, [\pm]int ,|, x^{int} ,|, \exp(factor)^{int} ,|, \exp((expr))^{int}
\end{aligned}
$$
只需对这一文法实现一组Lexer和Parser即可检验输出格式。
这次作业新增的求导也可以使用Sympy处理,可以使用Sympy.diff(expr, x)函数完成。
在第一单元中我实现了表达式展开的功能,并根据每周新增的需求迭代我的代码。
由于初始架构选择较为普适,我在2次迭代过程中均没有进行重构,只是增加了相关代码就完成了对应功能,同时,我的架构也可以适应比较多的新增需求,比如如果加入多变量的需求,在我的架构下只需要修改BasicTerm类中的一些实现,就可以完成需求,如果加入新的运算,只需在Expr类中添加相应的运算代码即可完成需求,在面对新增需求时我的架构适应性比较强。
究其原因,我认为是我的架构实现了表达式存储和运算的良好分离导致的,BasicTerm主要负责存储相关,而Expr主要负责运算相关,Expr是运算的基本单元,在这种架构下,无论是新加不同的格式,或者新加不同的运算,均能在不改变原有代码的情况下完成新增需求。
由于我三次作业强测互测均无bug,我写点我发现的别人的bug吧,一个是乘法实现没有考虑乘0的问题,还有就是实现比较差,用了过多字符串替换,导致在一些极端数据上会出现问题。
在发现别人程序bug方面,由于我几次互测遇到的同学代码都挺长的,所以也并没有去仔细看,主要依靠对拍撞概率来实现查错。
优化方面我主要做了提取公因式,以及写了一些拆exp规则,但是最后可惜没有起到作用。
感觉这一单元总体还是挺不错的,从递归下降的引入,再到后面新增运算的引入,难度设置的还是比较低的,感觉下学期其实可以上点难度,主要是hw2到hw3感觉过于平缓,好歹加个多变量吧x),嘛,不过虽然我感觉难度稍微低了点,但是总体上应该还是合理的,总之第一单元我认为设计得还是不错的。
然后关于性能分,我认为性能分占比减少是一个正确的趋势,因为表达式化简部分实际上是一个非常困难的问题,我认为不适合作为oo课程里的一个小内容进行扩展,我感觉进行分段赋分可能会是比较好的一个选择,就是给几个baseline,超过了baseline就给多少分这种,这样子同学们也能少卷一点,把时间多放在有意义的事情上。