443
社区成员
发帖
与我相关
我的任务
分享目录
由于表达式具有多层嵌套结构,故采用递归下降法。作业中定义的表达式文法可能会遇到左递归问题,需要对文法进行调整,将左递归的规则转化为右递归的规则。例如,表达式文法中项的定义:项 → [加减 空白项] 因子 | 项 空白项 '*' 空白项 因子,可以改写为 项 → [加减 空白项] 因子 {空白项 '*' 空白项 因子},从而避免递归的无限循环。我为每种非终结符设计一个类,按照文法设计了解析方法。例如,对于表达式中的项,建立一个 Term 类,根据其文法定义,令该类包含一个 private List<Factor> factors 的成员,其解析方法 parseTerm() 如下(其中调用的构造函数和辅助用的其他函数实现简单,在此不用赘述):
public Term parseTerm() {
Term term = new Term();
if (isAddOrSubtractOperator()) {
Operator sign = getAddOrSubtractOperator();
skipBlankTerm();
term.addFactor(parseFactor(), sign);
} else {
term.addFactor(parseFactor());
}
skipBlankTerm();
while (isMultiplyOperator()) {
skipMultiplyOperator();
skipBlankTerm();
term.addFactor(parseFactor());
skipBlankTerm();
}
return term;
}
可以看出利用递归下降法,我们可以根据严格定义的文法,写出非常易读、简单而可靠的解析表达式不同结构的代码。从第一次作业到最后一次作业,解析表达式结构的代码只用极少的修改,或只用按照这种思路为新增的表达式结构的类写解析方法即可。
值得注意的是,虽然解析器可以轻松解析类似于 +-+x**+02 的连续正负号的情况,但是为了方便后续的表达式化简,每个表达式结构中并不单独存储首个嵌套结构的符号,而是遇到负号就对应乘一个 -1 的常数因子。
首先,我设计了 Function 枚举类,包含 F、G、H 三个静态对象。解析表达式定义的时候,生成一个 HashMap<Function, FunctionDefinition> functionDefinitionMap。函数定义 FunctionDefinition 类的对象中存有 List<Variable> parameters 的形参列表,还有定义的函数表达式 Expression expression。我又设计了函数因子 FunctionFactor 类,该类对象中存有成员 List<Factor> arguments 的实参列表。当调用 FunctionFactor 类对象的 substituteFunction() 方法时,通过 functionDefinitionMap 根据其 Function 类型获取对应的 functionDefinition,然后根据 parameters 和 arguments 生成一个 HashMap<Variable, Factor> parametersSubstitutionMap,再对 functionDefinition 中的 expression 的克隆调用substituteVariable()方法,根据 parametersSubstitutionMap 替换表达式中的 Variable 类对象,将这个 expression 构造为一个 ExpressionFactor 类对象作为返回值。上述的实现均是递归的,可以处理嵌套中的自定义函数调用。
对于解析字符串后得到包含嵌套结构的表达式对象,我的设计是调用其 expand() 方法得到展开后的对象,但是 expand() 方法中首先调用嵌套的其他结构的的 expand() 方法,利用递归实现对多层嵌套结构的展开。调用 expand() 后往往会返回一个不同于 this 的类的对象,例如 Term 类对象的 expand() 方法会返回 Expression 类的对象,因为展开项原本是若干个因子相乘,但是项中的表达式因子利用乘法分配律展开后得到了若干个项的和,而这正是一个表达式 Expression。因此,在 Expression 中设计 mergeExpression() 方法,可以将两个表达式中的项合并得到一个新表达式对象。类似地,Factor 类对象调用 expand() 方法得到 Term 类对象,因为可能会按照指数将一个表达式因子得到多个指数为 1 的表达式因子相乘。
关于表达式因子的展开,一种思路是先对项中的因子进行扫描,先统一处理 ()**n,再统一处理 x*(),再统一处理 ()*(),还有一种方式是遇到表达式因子时,将其他因子对表达式因子里的表达式的项做分配,得到新的表达式,对新的表达式重复这一步骤直到不存在可以展开的表达式因子。后者利用了递归的思想,实现起来代码更精简。我在第二次作业是按照前一种思路处理的,而在第三次作业中按照后一种思路处理。
我们知道展开完的表达式通常具有形式 系数 * x**n1 * y**n2 * z**n3 * 若干三角函数因子,那么遍历一个展开后的表达式的项,建立一个 HashSet<Term, ConstantFactor> coefficientMap,将项去除系数后检索 coefficientMap,将同类项系数不断更新,遍历完成后清除表达式中原有的项,遍历 coefficientMap 中的条目,在表达式中根据条目重建项,得到最简的表达式。对于项来说,遍历每个因子,建立一个 HashSet<Factor, ConstantFactor> exponentMap,按照类似的方法完成各同类因子指数的合并。
对于三角函数有一些特别的化简,例如对于 sin(0) 和 cos(0) 可以将其化简为常数因子,还有可以利用很多三角恒等式进行化简,例如 sin(x)**2 + cos(x)**2 = 1、2*sin(x)*cos(x) = sin((2*x)) 等。但三角函数的化简仅仅影响性能分,而不影响结果的正确性,由于我的编程能力较弱,课下时间并不充裕,所以我并没有实现太多利用三角恒等式进行化简的优化。
表达式求导首先依赖于表达式的展开与化简,而对展开和化简完后的表达式求导与表达式的展开很像。因为对于一个项求导根据乘法法则会得到多个项,即一个表达式;而对于一个因子求导,根据链式法则又常常得到若干个因子相乘。根据公式,对每个非终结符对应的类写出 derive() 方法,对嵌套的结构的 derive() 方法调用的返回值常常需要调用相应的 merge() 方法进行合并。因为表达式求导后的新式子与原式子很大概率不相等,最后对于求导完后的结果还需要调用 expand() 方法进行展开和化简。

最终实现的整个项目 UML 图如上所示,可以看出我几乎对于每种非终结符都建立了对应的类,这些类中重写了toString() , hashCode() , equals() , clone() 等方法,并实现了 expand() , derive() , substituteFunction() , substituteVariable() 方法,当然为了降低单个方法的复杂度,有些步骤被抽出为类中的其他方法,这里不再赘述。值得注意的是为了利用多态来简化代码,我有两个 interface 分别是 Factor 和 HasExponent,之所以使用接口而不是子类继承父类,是因为不同的因子的字段差异巨大。对于解析器,我分别实现了 ExpressionParser 和 FunctionDefinitionParser,均继承自 Parser 抽象类,我实现的 Parser 类其实作用类似于 Lexer,可以分析遍历字符串并获得不同的 token。我还有一些枚举类,比如 Function , Trigonometry , Variable,是为了便于使用 HashMap 和 HashSet。还有其他的类如 TermHash 和 ToStringLengthComparator,是为了辅助表达式的化简。
| Method | CogC | ev(G) | iv(G) | v(G) |
|---|---|---|---|---|
| expression.factors.ExpressionFactor.expand() | 11 | 5 | 6 | 7 |
| expression.parsers.ExpressionParser.parseFactor() | 7 | 5 | 5 | 5 |
| expression.factors.PowerFactor.derive(Derivative) | 6 | 4 | 3 | 4 |
| expression.factors.TrigonometryFactor.expand() | 9 | 4 | 3 | 4 |
| expression.Expression.equals(Object) | 3 | 3 | 2 | 4 |
| expression.Term.equals(Object) | 3 | 3 | 2 | 4 |
| expression.Term.expandEveryFactor() | 4 | 3 | 3 | 3 |
| expression.Term.expandExpressionFactor(Expression) | 3 | 3 | 3 | 3 |
| expression.ancillaries.FunctionDefinition.equals(Object) | 4 | 3 | 4 | 6 |
| expression.ancillaries.TermHash.equals(Object) | 4 | 3 | 6 | 8 |
| expression.factors.ConstantFactor.equals(Object) | 3 | 3 | 2 | 4 |
| expression.factors.DeriveFactor.equals(Object) | 4 | 3 | 3 | 5 |
| expression.factors.ExpressionFactor.derive(Derivative) | 3 | 3 | 3 | 3 |
| expression.factors.ExpressionFactor.equals(Object) | 4 | 3 | 3 | 5 |
| expression.factors.FunctionFactor.equals(Object) | 4 | 3 | 3 | 5 |
| expression.factors.PowerFactor.equals(Object) | 4 | 3 | 2 | 5 |
| expression.factors.TrigonometryFactor.equals(Object) | 4 | 3 | 4 | 6 |
| expression.parsers.Parser.isBlankCharacter() | 2 | 3 | 1 | 3 |
| expression.parsers.Parser.isPositiveOrNegativeSign() | 2 | 3 | 1 | 3 |
| expression.parsers.Parser.isVariable() | 2 | 3 | 1 | 3 |
| expression.Term.expand() | 5 | 2 | 3 | 4 |
| expression.factors.ExpressionFactor.isExponentCombinable() | 3 | 2 | 2 | 3 |
| expression.factors.PowerFactor.expand() | 2 | 2 | 1 | 2 |
| expression.factors.TrigonometryFactor.derive(Derivative) | 7 | 2 | 4 | 4 |
| expression.parsers.ExpressionParser.parsePower() | 2 | 2 | 2 | 2 |
| expression.parsers.Parser.getSignedNumberString(boolean) | 4 | 2 | 3 | 3 |
| expression.parsers.Parser.isComma() | 1 | 2 | 1 | 2 |
| expression.parsers.Parser.isLeftBracket() | 1 | 2 | 1 | 2 |
| expression.parsers.Parser.isMultiplyOperator() | 2 | 2 | 2 | 3 |
| expression.parsers.Parser.isPowerOperator() | 2 | 2 | 2 | 3 |
| Main.main(String[]) | 1 | 1 | 2 | 2 |
| expression.Expression.Expression() | 0 | 1 | 1 | 1 |
| expression.Expression.Expression(Term) | 0 | 1 | 1 | 1 |
| expression.Expression.addCombinedTerms(HashMap<TermHash, ConstantFactor>, LinkedList<Term>) | 7 | 1 | 5 | 5 |
| expression.Expression.addTerm(Operator, Term) | 1 | 1 | 2 | 2 |
| expression.Expression.addTerm(Term) | 0 | 1 | 1 | 1 |
| expression.Expression.clone() | 1 | 1 | 1 | 2 |
| expression.Expression.combine() | 1 | 1 | 2 | 2 |
| expression.Expression.derive(Derivative) | 1 | 1 | 2 | 2 |
| expression.Expression.expand() | 1 | 1 | 2 | 2 |
| expression.Expression.getLikeTermsCoefficient(HashMap<TermHash, ConstantFactor>, LinkedList<Term>) | 4 | 1 | 3 | 3 |
| expression.Expression.getTerm(int) | 0 | 1 | 1 | 1 |
| expression.Expression.getTerms() | 0 | 1 | 1 | 1 |
| expression.Expression.hashCode() | 0 | 1 | 1 | 1 |
| expression.Expression.size() | 0 | 1 | 1 | 1 |
| expression.Expression.substituteFunction(HashMap<Function, FunctionDefinition>) | 0 | 1 | 1 | 1 |
| expression.Expression.substituteVariable(HashMap<Variable, Factor>) | 0 | 1 | 1 | 1 |
| expression.Expression.toString() | 5 | 1 | 5 | 5 |
| expression.Term.Term() | 0 | 1 | 1 | 1 |
| expression.Term.Term(Factor) | 0 | 1 | 1 | 1 |
| expression.Term.addCoefficient(ConstantFactor) | 2 | 1 | 3 | 3 |
| expression.Term.addCombinedPowerFactors(int, int, int) | 3 | 1 | 4 | 4 |
| expression.Term.addCombinedTrigonometryFactors(HashMap<TrigonometryFactor, Integer>) | 3 | 1 | 3 | 3 |
| expression.Term.addFactor(Factor) | 0 | 1 | 1 | 1 |
| expression.Term.addFactor(Factor, Operator) | 1 | 1 | 2 | 2 |
| expression.Term.addFactor(int, Factor) | 0 | 1 | 1 | 1 |
| expression.Term.addOtherFactors(int, LinkedList<Factor>) | 3 | 1 | 3 | 3 |
| expression.Term.clone() | 1 | 1 | 1 | 2 |
| expression.Term.combine() | 9 | 1 | 5 | 7 |
| expression.Term.derive(Derivative) | 6 | 1 | 4 | 4 |
| expression.Term.distributiveExpand(Expression, ExpressionFactor, LinkedList<Factor>) | 3 | 1 | 3 | 3 |
| expression.Term.getCoefficient() | 3 | 1 | 3 | 3 |
| expression.Term.getFactor(int) | 0 | 1 | 1 | 1 |
| expression.Term.getNonCoefficientFactors() | 3 | 1 | 3 | 3 |
| expression.Term.getTermHash() | 8 | 1 | 5 | 7 |
| expression.Term.hashCode() | 0 | 1 | 1 | 1 |
| expression.Term.isCoefficientNegative(int, Factor) | 1 | 1 | 4 | 4 |
| expression.Term.isFirstDisplayFactor(int, boolean) | 2 | 1 | 1 | 3 |
| expression.Term.mergeTrigonometryExponent(HashMap<TrigonometryFactor, Integer>, TrigonometryFactor) | 2 | 1 | 2 | 2 |
| expression.Term.size() | 0 | 1 | 1 | 1 |
| expression.Term.substituteFunction(HashMap<Function, FunctionDefinition>) | 0 | 1 | 1 | 1 |
| expression.Term.substituteVariable(HashMap<Variable, Factor>) | 0 | 1 | 1 | 1 |
| expression.Term.toString() | 5 | 1 | 4 | 4 |
| expression.ancillaries.FunctionDefinition.FunctionDefinition() | 0 | 1 | 1 | 1 |
| expression.ancillaries.FunctionDefinition.FunctionDefinition(Function, LinkedList<Variable>, Expression) | 0 | 1 | 1 | 1 |
| expression.ancillaries.FunctionDefinition.addParameter(Variable) | 0 | 1 | 1 | 1 |
| expression.ancillaries.FunctionDefinition.clone() | 1 | 1 | 1 | 2 |
| expression.ancillaries.FunctionDefinition.getExpression() | 0 | 1 | 1 | 1 |
| expression.ancillaries.FunctionDefinition.getFunctionName() | 0 | 1 | 1 | 1 |
| expression.ancillaries.FunctionDefinition.getParameters() | 0 | 1 | 1 | 1 |
| expression.ancillaries.FunctionDefinition.hashCode() | 0 | 1 | 1 | 1 |
| expression.ancillaries.FunctionDefinition.isExpanded() | 0 | 1 | 1 | 1 |
| expression.ancillaries.FunctionDefinition.setExpandedExpression(Expression) | 0 | 1 | 1 | 1 |
| expression.ancillaries.FunctionDefinition.setExpression(Expression) | 0 | 1 | 1 | 1 |
| expression.ancillaries.FunctionDefinition.setFunctionName(Function) | 0 | 1 | 1 | 1 |
| expression.ancillaries.FunctionDefinition.toString() | 0 | 1 | 1 | 1 |
| expression.ancillaries.TermHash.TermHash(int, int, int, HashSet<TrigonometryFactor>, HashSet<Factor>) | 0 | 1 | 1 | 1 |
| expression.ancillaries.TermHash.hashCode() | 0 | 1 | 1 | 1 |
| expression.ancillaries.ToStringLengthComparator.compare(Object, Object) | 0 | 1 | 1 | 1 |
| expression.enumerations.Derivative.Derivative(String) | 0 | 1 | 1 | 1 |
| expression.enumerations.Derivative.toString() | 0 | 1 | 1 | 1 |
| expression.enumerations.Function.Function(String) | 0 | 1 | 1 | 1 |
| expression.enumerations.Function.toString() | 0 | 1 | 1 | 1 |
| expression.enumerations.Operator.Operator(String) | 0 | 1 | 1 | 1 |
| expression.enumerations.Operator.toString() | 0 | 1 | 1 | 1 |
| expression.enumerations.Trigonometry.Trigonometry(String) | 0 | 1 | 1 | 1 |
| expression.enumerations.Trigonometry.toString() | 0 | 1 | 1 | 1 |
| expression.enumerations.Variable.Variable(String) | 0 | 1 | 1 | 1 |
| expression.enumerations.Variable.toString() | 0 | 1 | 1 | 1 |
| expression.factors.ConstantFactor.ConstantFactor(BigInteger) | 0 | 1 | 1 | 1 |
| expression.factors.ConstantFactor.ConstantFactor(String) | 0 | 1 | 1 | 1 |
| expression.factors.ConstantFactor.ConstantFactor(int) | 0 | 1 | 1 | 1 |
| expression.factors.ConstantFactor.clone() | 1 | 1 | 1 | 2 |
| expression.factors.ConstantFactor.derive(Derivative) | 0 | 1 | 1 | 1 |
| expression.factors.ConstantFactor.expand() | 0 | 1 | 1 | 1 |
| expression.factors.ConstantFactor.hashCode() | 0 | 1 | 1 | 1 |
| expression.factors.ConstantFactor.isLessThan(ConstantFactor) | 0 | 1 | 1 | 1 |
| expression.factors.ConstantFactor.multiply(ConstantFactor) | 0 | 1 | 1 | 1 |
| expression.factors.ConstantFactor.plus(ConstantFactor) | 0 | 1 | 1 | 1 |
| expression.factors.ConstantFactor.substituteFunction(HashMap<Function, FunctionDefinition>) | 0 | 1 | 1 | 1 |
| expression.factors.ConstantFactor.substituteVariable(HashMap<Variable, Factor>) | 0 | 1 | 1 | 1 |
| expression.factors.ConstantFactor.toString() | 0 | 1 | 1 | 1 |
| expression.factors.DeriveFactor.DeriveFactor(Derivative) | 0 | 1 | 1 | 1 |
| expression.factors.DeriveFactor.DeriveFactor(Derivative, Expression) | 0 | 1 | 1 | 1 |
| expression.factors.DeriveFactor.clone() | 1 | 1 | 1 | 2 |
| expression.factors.DeriveFactor.derive(Derivative) | 0 | 1 | 1 | 1 |
| expression.factors.DeriveFactor.expand() | 0 | 1 | 1 | 1 |
| expression.factors.DeriveFactor.hashCode() | 0 | 1 | 1 | 1 |
| expression.factors.DeriveFactor.setExpression(Expression) | 0 | 1 | 1 | 1 |
| expression.factors.DeriveFactor.substituteFunction(HashMap<Function, FunctionDefinition>) | 0 | 1 | 1 | 1 |
| expression.factors.DeriveFactor.substituteVariable(HashMap<Variable, Factor>) | 0 | 1 | 1 | 1 |
| expression.factors.DeriveFactor.toString() | 0 | 1 | 1 | 1 |
| expression.factors.ExpressionFactor.ExpressionFactor(Expression) | 0 | 1 | 1 | 1 |
| expression.factors.ExpressionFactor.ExpressionFactor(Expression, int) | 0 | 1 | 1 | 1 |
| expression.factors.ExpressionFactor.ExpressionFactor(Term) | 0 | 1 | 1 | 1 |
| expression.factors.ExpressionFactor.binomialExpandTo(Expression) | 5 | 1 | 4 | 4 |
| expression.factors.ExpressionFactor.clone() | 1 | 1 | 1 | 2 |
| expression.factors.ExpressionFactor.getExponent() | 0 | 1 | 1 | 1 |
| expression.factors.ExpressionFactor.getExponentCombinedFactor() | 1 | 1 | 2 | 2 |
| expression.factors.ExpressionFactor.getExpression() | 0 | 1 | 1 | 1 |
| expression.factors.ExpressionFactor.hashCode() | 0 | 1 | 1 | 1 |
| expression.factors.ExpressionFactor.isSingleFactor() | 1 | 1 | 2 | 2 |
| expression.factors.ExpressionFactor.normalExpandTo(Expression) | 1 | 1 | 2 | 2 |
| expression.factors.ExpressionFactor.setExponent(int) | 0 | 1 | 1 | 1 |
| expression.factors.ExpressionFactor.substituteFunction(HashMap<Function, FunctionDefinition>) | 0 | 1 | 1 | 1 |
| expression.factors.ExpressionFactor.substituteVariable(HashMap<Variable, Factor>) | 0 | 1 | 1 | 1 |
| expression.factors.ExpressionFactor.toString() | 1 | 1 | 1 | 2 |
| expression.factors.FunctionFactor.FunctionFactor() | 0 | 1 | 1 | 1 |
| expression.factors.FunctionFactor.addArgument(Factor) | 0 | 1 | 1 | 1 |
| expression.factors.FunctionFactor.clone() | 1 | 1 | 1 | 2 |
| expression.factors.FunctionFactor.derive(Derivative) | 0 | 1 | 1 | 1 |
| expression.factors.FunctionFactor.expand() | 0 | 1 | 1 | 1 |
| expression.factors.FunctionFactor.hashCode() | 0 | 1 | 1 | 1 |
| expression.factors.FunctionFactor.setFunctionName(Function) | 0 | 1 | 1 | 1 |
| expression.factors.FunctionFactor.substituteFunction(HashMap<Function, FunctionDefinition>) | 2 | 1 | 3 | 3 |
| expression.factors.FunctionFactor.substituteVariable(HashMap<Variable, Factor>) | 0 | 1 | 1 | 1 |
| expression.factors.FunctionFactor.toString() | 0 | 1 | 1 | 1 |
| expression.factors.PowerFactor.PowerFactor(Variable, int) | 0 | 1 | 1 | 1 |
| expression.factors.PowerFactor.clone() | 1 | 1 | 1 | 2 |
| expression.factors.PowerFactor.getExponent() | 0 | 1 | 1 | 1 |
| expression.factors.PowerFactor.getVariable() | 0 | 1 | 1 | 1 |
| expression.factors.PowerFactor.hashCode() | 0 | 1 | 1 | 1 |
| expression.factors.PowerFactor.isDerivativeMatchinVariable(Derivative) | 4 | 1 | 6 | 6 |
| expression.factors.PowerFactor.setExponent(int) | 0 | 1 | 1 | 1 |
| expression.factors.PowerFactor.substituteFunction(HashMap<Function, FunctionDefinition>) | 0 | 1 | 1 | 1 |
| expression.factors.PowerFactor.substituteVariable(HashMap<Variable, Factor>) | 0 | 1 | 1 | 1 |
| expression.factors.PowerFactor.toString() | 1 | 1 | 1 | 2 |
| expression.factors.TrigonometryFactor.TrigonometryFactor(Trigonometry, Factor, int) | 0 | 1 | 1 | 1 |
| expression.factors.TrigonometryFactor.clone() | 1 | 1 | 1 | 2 |
| expression.factors.TrigonometryFactor.getBase() | 0 | 1 | 1 | 1 |
| expression.factors.TrigonometryFactor.getExponent() | 0 | 1 | 1 | 1 |
| expression.factors.TrigonometryFactor.hashCode() | 0 | 1 | 1 | 1 |
| expression.factors.TrigonometryFactor.setExponent(int) | 0 | 1 | 1 | 1 |
| expression.factors.TrigonometryFactor.substituteFunction(HashMap<Function, FunctionDefinition>) | 0 | 1 | 1 | 1 |
| expression.factors.TrigonometryFactor.substituteVariable(HashMap<Variable, Factor>) | 0 | 1 | 1 | 1 |
| expression.factors.TrigonometryFactor.toString() | 1 | 1 | 1 | 2 |
| expression.parsers.ExpressionParser.ExpressionParser(String) | 0 | 1 | 1 | 1 |
| expression.parsers.ExpressionParser.parseConstant() | 0 | 1 | 1 | 1 |
| expression.parsers.ExpressionParser.parseDerivative() | 0 | 1 | 1 | 1 |
| expression.parsers.ExpressionParser.parseExponent() | 0 | 1 | 1 | 1 |
| expression.parsers.ExpressionParser.parseExpression() | 3 | 1 | 3 | 3 |
| expression.parsers.ExpressionParser.parseFunction() | 2 | 1 | 3 | 3 |
| expression.parsers.ExpressionParser.parseTerm() | 3 | 1 | 3 | 3 |
| expression.parsers.ExpressionParser.parseTrigonometry() | 1 | 1 | 2 | 2 |
| expression.parsers.ExpressionParser.parseVariableFactor() | 3 | 1 | 3 | 3 |
| expression.parsers.FunctionDefinitionParser.FunctionDefinitionParser(String) | 0 | 1 | 1 | 1 |
| expression.parsers.FunctionDefinitionParser.parseFunctionDefinition() | 2 | 1 | 3 | 3 |
| expression.parsers.Parser.Parser(String) | 0 | 1 | 1 | 1 |
| expression.parsers.Parser.getAddOrSubtractOperator() | 2 | 1 | 1 | 2 |
| expression.parsers.Parser.getDerivative() | 3 | 1 | 2 | 3 |
| expression.parsers.Parser.getFunctionName() | 1 | 1 | 1 | 3 |
| expression.parsers.Parser.getSubstring() | 0 | 1 | 1 | 1 |
| expression.parsers.Parser.getTrigonometryType() | 2 | 1 | 1 | 2 |
| expression.parsers.Parser.getVariable() | 1 | 1 | 1 | 3 |
| expression.parsers.Parser.isAddOrSubtractOperator() | 0 | 1 | 1 | 1 |
| expression.parsers.Parser.isDerivative() | 0 | 1 | 1 | 1 |
| expression.parsers.Parser.isEndAt(int) | 0 | 1 | 1 | 1 |
| expression.parsers.Parser.isTrigonometry() | 0 | 1 | 1 | 1 |
| expression.parsers.Parser.isVariableFactor() | 0 | 1 | 1 | 1 |
| expression.parsers.Parser.skipBlankTerm() | 1 | 1 | 1 | 2 |
| expression.parsers.Parser.skipComma() | 0 | 1 | 1 | 1 |
| expression.parsers.Parser.skipEqualMark() | 0 | 1 | 1 | 1 |
| expression.parsers.Parser.skipLeftBracket() | 0 | 1 | 1 | 1 |
| expression.parsers.Parser.skipMultiplyOperator() | 0 | 1 | 1 | 1 |
| expression.parsers.Parser.skipPowerOperator() | 0 | 1 | 1 | 1 |
| expression.parsers.Parser.skipRightBracket() | 0 | 1 | 1 | 1 |
为了满足代码风格的要求和控制调试时确定bug范围的目的,我尽量利用 IDEA 的提取方法的功能,降低一个方法的 CogC(Cognitive Complexity,认知复杂度),但是仍然总是有一些方法的 ev(G) (Cyclomatic Complexity,圈复杂度)过高,例如 expression.parsers.ExpressionParser.parseFactor(),其代码如下:
private Factor parseFactor() {
if (isVariableFactor()) {
return parseVariableFactor();
} else if (isLeftBracket()) {
skipLeftBracket();
Expression expression = parseExpression();
skipRightBracket();
skipBlankTerm();
if (isPowerOperator()) {
return new ExpressionFactor(expression, parseExponent());
} else {
return new ExpressionFactor(expression, 1);
}
} else if (isDerivative()) {
return parseDerivative();
} else {
return parseConstant();
}
}
显然这种解析因子的方法不可避免地会产生大量分支,并没有什么太多降低圈复杂度的方法。不过好在 CogC 并不高,debug 通常也容易发现 bug 在哪里。通过 MetricsReloaded 的数据来重构代码是有益的,但也不必过分追求数据的完美。
| Class | OCavg | OCmax | WMC |
|---|---|---|---|
| expression.Term | 2.55 | 8 | 74 |
| expression.parsers.Parser | 1.96 | 4 | 53 |
| expression.factors.ExpressionFactor | 1.84 | 6 | 35 |
| expression.Expression | 1.72 | 4 | 31 |
| expression.parsers.ExpressionParser | 2.18 | 5 | 24 |
| expression.factors.TrigonometryFactor | 1.75 | 4 | 21 |
| expression.factors.PowerFactor | 1.54 | 4 | 20 |
| expression.ancillaries.FunctionDefinition | 1.14 | 3 | 16 |
| expression.factors.ConstantFactor | 1.14 | 3 | 16 |
| expression.factors.FunctionFactor | 1.36 | 3 | 15 |
| expression.factors.DeriveFactor | 1.18 | 3 | 13 |
| expression.ancillaries.TermHash | 1.67 | 3 | 5 |
| expression.parsers.FunctionDefinitionParser | 1.50 | 2 | 3 |
| Main | 2.00 | 2 | 2 |
| expression.enumerations.Derivative | 1.00 | 1 | 2 |
| expression.enumerations.Function | 1.00 | 1 | 2 |
| expression.enumerations.Operator | 1.00 | 1 | 2 |
| expression.enumerations.Trigonometry | 1.00 | 1 | 2 |
| expression.enumerations.Variable | 1.00 | 1 | 2 |
| expression.ancillaries.ToStringLengthComparator | 1.00 | 1 | 1 |
Term 类从各个指标来看都有着显著的最高的复杂度,大概是因为 Term 里可能含有各种各样的因子,对各种因子进行合并化简非常复杂导致的。另外对单个复杂方法抽出多个简单的小方法以降低 CogC 的做法,可能对类的复杂度统计造成负面影响。
第一次作业我并没有按时完成,一方面是那一周遇上两门上学期课程补考,挤占了本不富裕的课下时间,另一方面是面向对象设计的能力和经验都很欠缺,以及对完成非玩具型的千行级中小型项目所需的时间预判有误,这些方面共同导致的。第一周的作业在提交截止前,我实现了使用递归下降法解析表达式,没能完成对表达式的展开,不过解析器的实现具有充足的可靠性和扩展性,这部分的代码在第二次和第三次作业中都可以可靠的运行,能够通过少量的增改实现新的需求。
第二次作业我在截止时间之前勉强完成提交,通过了所有的弱测与中测样例,不过代码的实现较为丑陋,容易出现 bug,没有使用多态降低代码复杂性、提高可扩展性,对于表达式因子的展开方法也很不简洁,没有利用递归的思想。
第三次作业在第二次作业上重构了许多。对于各种因子的类统一实现Factor接口,减少了大量的 instanceOf 判断,然后更多地利用 List 库中已有的方法实现功能而不是用若干个基础方法的组合来实现,降低了 NullPointerException 和 OutOfIndexException 异常的出现概率。利用递归的思想重写了表达式因子展开的方法,代码更加简洁可靠。在语法糖方面也大量使用了 Lambda 表达式来简化代码。
第二次作业在强测和互测中被发现很多的 bug,根本原因在于缺少有效的测试,对各种边界条件的判断各种不足导致了许多本不该出现的bug,例如一个表达式的各项化简完为0,而表达式合并项后删除了表达式的所有项,导致表达式为空。还有对于嵌套函数调用,忘记了应在代入函数定义表达式时深拷贝,造成化简出错。
做第三次作业时,群里有同学写了评测机,评测机会随机生成符合要求的输入,对程序的输出用外部库验证是否与输入等价。我用同学写的评测机发现了很多bug,但是我疏忽了会造成 TLE 的样例,以为互测时的 cost 限制不会让我 TLE,结果并非如此。为了解决程序运行的性能问题,我又对代码做出了大量的修改。
感觉三次作业做完后,测试程序的 bug 仍然是我最为欠缺的能力,我希望在下个单元的作业中自己试着写评测机,以及手动全面构造测试样例,对每个类的方法进行测试。