442
社区成员




目录
由于表达式具有多层嵌套结构,故采用递归下降法。作业中定义的表达式文法可能会遇到左递归问题,需要对文法进行调整,将左递归的规则转化为右递归的规则。例如,表达式文法中项的定义:项 → [加减 空白项] 因子 | 项 空白项 '*' 空白项 因子
,可以改写为 项 → [加减 空白项] 因子 {空白项 '*' 空白项 因子}
,从而避免递归的无限循环。我为每种非终结符设计一个类,按照文法设计了解析方法。例如,对于表达式中的项,建立一个 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 仍然是我最为欠缺的能力,我希望在下个单元的作业中自己试着写评测机,以及手动全面构造测试样例,对每个类的方法进行测试。