面向对象设计与构造第一单元 代数表达式的展开与化简 架构设计|程序结构分析|项目迭代体验感悟

陈宇风-20373216 学生 2023-03-19 19:42:35

目录

一、架构设计

表达式的解析

自定义函数调用

表达式的展开

表达式的化简

表达式的求导

二、程序结构分析

UML 图

复杂度分析

方法复杂度分析

类复杂度分析

三、项目迭代体验

关于重构

关于 Debug


一、架构设计

表达式的解析

由于表达式具有多层嵌套结构,故采用递归下降法。作业中定义的表达式文法可能会遇到左递归问题,需要对文法进行调整,将左递归的规则转化为右递归的规则。例如,表达式文法中项的定义:项 → [加减 空白项] 因子 | 项 空白项 '*' 空白项 因子,可以改写为 项 → [加减 空白项] 因子 {空白项 '*' 空白项 因子},从而避免递归的无限循环。我为每种非终结符设计一个类,按照文法设计了解析方法。例如,对于表达式中的项,建立一个 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 枚举类,包含 FGH 三个静态对象。解析表达式定义的时候,生成一个 HashMap<Function, FunctionDefinition> functionDefinitionMap。函数定义 FunctionDefinition 类的对象中存有 List<Variable> parameters 的形参列表,还有定义的函数表达式 Expression expression。我又设计了函数因子 FunctionFactor 类,该类对象中存有成员 List<Factor> arguments 的实参列表。当调用 FunctionFactor 类对象的 substituteFunction() 方法时,通过 functionDefinitionMap 根据其 Function 类型获取对应的 functionDefinition,然后根据 parametersarguments 生成一个 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 = 12*sin(x)*cos(x) = sin((2*x)) 等。但三角函数的化简仅仅影响性能分,而不影响结果的正确性,由于我的编程能力较弱,课下时间并不充裕,所以我并没有实现太多利用三角恒等式进行化简的优化。

表达式的求导

表达式求导首先依赖于表达式的展开与化简,而对展开和化简完后的表达式求导与表达式的展开很像。因为对于一个项求导根据乘法法则会得到多个项,即一个表达式;而对于一个因子求导,根据链式法则又常常得到若干个因子相乘。根据公式,对每个非终结符对应的类写出 derive() 方法,对嵌套的结构的 derive() 方法调用的返回值常常需要调用相应的 merge() 方法进行合并。因为表达式求导后的新式子与原式子很大概率不相等,最后对于求导完后的结果还需要调用 expand() 方法进行展开和化简。

二、程序结构分析

UML 图

 

最终实现的整个项目 UML 图如上所示,可以看出我几乎对于每种非终结符都建立了对应的类,这些类中重写了toString() , hashCode() , equals() , clone() 等方法,并实现了 expand() , derive() , substituteFunction() , substituteVariable() 方法,当然为了降低单个方法的复杂度,有些步骤被抽出为类中的其他方法,这里不再赘述。值得注意的是为了利用多态来简化代码,我有两个 interface 分别是 FactorHasExponent,之所以使用接口而不是子类继承父类,是因为不同的因子的字段差异巨大。对于解析器,我分别实现了 ExpressionParserFunctionDefinitionParser,均继承自 Parser 抽象类,我实现的 Parser 类其实作用类似于 Lexer,可以分析遍历字符串并获得不同的 token。我还有一些枚举类,比如 Function , Trigonometry , Variable,是为了便于使用 HashMapHashSet。还有其他的类如 TermHashToStringLengthComparator,是为了辅助表达式的化简。

复杂度分析

方法复杂度分析

MethodCogCev(G)iv(G)v(G)
expression.factors.ExpressionFactor.expand()11567
expression.parsers.ExpressionParser.parseFactor()7555
expression.factors.PowerFactor.derive(Derivative)6434
expression.factors.TrigonometryFactor.expand()9434
expression.Expression.equals(Object)3324
expression.Term.equals(Object)3324
expression.Term.expandEveryFactor()4333
expression.Term.expandExpressionFactor(Expression)3333
expression.ancillaries.FunctionDefinition.equals(Object)4346
expression.ancillaries.TermHash.equals(Object)4368
expression.factors.ConstantFactor.equals(Object)3324
expression.factors.DeriveFactor.equals(Object)4335
expression.factors.ExpressionFactor.derive(Derivative)3333
expression.factors.ExpressionFactor.equals(Object)4335
expression.factors.FunctionFactor.equals(Object)4335
expression.factors.PowerFactor.equals(Object)4325
expression.factors.TrigonometryFactor.equals(Object)4346
expression.parsers.Parser.isBlankCharacter()2313
expression.parsers.Parser.isPositiveOrNegativeSign()2313
expression.parsers.Parser.isVariable()2313
expression.Term.expand()5234
expression.factors.ExpressionFactor.isExponentCombinable()3223
expression.factors.PowerFactor.expand()2212
expression.factors.TrigonometryFactor.derive(Derivative)7244
expression.parsers.ExpressionParser.parsePower()2222
expression.parsers.Parser.getSignedNumberString(boolean)4233
expression.parsers.Parser.isComma()1212
expression.parsers.Parser.isLeftBracket()1212
expression.parsers.Parser.isMultiplyOperator()2223
expression.parsers.Parser.isPowerOperator()2223
Main.main(String[])1122
expression.Expression.Expression()0111
expression.Expression.Expression(Term)0111
expression.Expression.addCombinedTerms(HashMap<TermHash, ConstantFactor>, LinkedList<Term>)7155
expression.Expression.addTerm(Operator, Term)1122
expression.Expression.addTerm(Term)0111
expression.Expression.clone()1112
expression.Expression.combine()1122
expression.Expression.derive(Derivative)1122
expression.Expression.expand()1122
expression.Expression.getLikeTermsCoefficient(HashMap<TermHash, ConstantFactor>, LinkedList<Term>)4133
expression.Expression.getTerm(int)0111
expression.Expression.getTerms()0111
expression.Expression.hashCode()0111
expression.Expression.size()0111
expression.Expression.substituteFunction(HashMap<Function, FunctionDefinition>)0111
expression.Expression.substituteVariable(HashMap<Variable, Factor>)0111
expression.Expression.toString()5155
expression.Term.Term()0111
expression.Term.Term(Factor)0111
expression.Term.addCoefficient(ConstantFactor)2133
expression.Term.addCombinedPowerFactors(int, int, int)3144
expression.Term.addCombinedTrigonometryFactors(HashMap<TrigonometryFactor, Integer>)3133
expression.Term.addFactor(Factor)0111
expression.Term.addFactor(Factor, Operator)1122
expression.Term.addFactor(int, Factor)0111
expression.Term.addOtherFactors(int, LinkedList<Factor>)3133
expression.Term.clone()1112
expression.Term.combine()9157
expression.Term.derive(Derivative)6144
expression.Term.distributiveExpand(Expression, ExpressionFactor, LinkedList<Factor>)3133
expression.Term.getCoefficient()3133
expression.Term.getFactor(int)0111
expression.Term.getNonCoefficientFactors()3133
expression.Term.getTermHash()8157
expression.Term.hashCode()0111
expression.Term.isCoefficientNegative(int, Factor)1144
expression.Term.isFirstDisplayFactor(int, boolean)2113
expression.Term.mergeTrigonometryExponent(HashMap<TrigonometryFactor, Integer>, TrigonometryFactor)2122
expression.Term.size()0111
expression.Term.substituteFunction(HashMap<Function, FunctionDefinition>)0111
expression.Term.substituteVariable(HashMap<Variable, Factor>)0111
expression.Term.toString()5144
expression.ancillaries.FunctionDefinition.FunctionDefinition()0111
expression.ancillaries.FunctionDefinition.FunctionDefinition(Function, LinkedList<Variable>, Expression)0111
expression.ancillaries.FunctionDefinition.addParameter(Variable)0111
expression.ancillaries.FunctionDefinition.clone()1112
expression.ancillaries.FunctionDefinition.getExpression()0111
expression.ancillaries.FunctionDefinition.getFunctionName()0111
expression.ancillaries.FunctionDefinition.getParameters()0111
expression.ancillaries.FunctionDefinition.hashCode()0111
expression.ancillaries.FunctionDefinition.isExpanded()0111
expression.ancillaries.FunctionDefinition.setExpandedExpression(Expression)0111
expression.ancillaries.FunctionDefinition.setExpression(Expression)0111
expression.ancillaries.FunctionDefinition.setFunctionName(Function)0111
expression.ancillaries.FunctionDefinition.toString()0111
expression.ancillaries.TermHash.TermHash(int, int, int, HashSet<TrigonometryFactor>, HashSet<Factor>)0111
expression.ancillaries.TermHash.hashCode()0111
expression.ancillaries.ToStringLengthComparator.compare(Object, Object)0111
expression.enumerations.Derivative.Derivative(String)0111
expression.enumerations.Derivative.toString()0111
expression.enumerations.Function.Function(String)0111
expression.enumerations.Function.toString()0111
expression.enumerations.Operator.Operator(String)0111
expression.enumerations.Operator.toString()0111
expression.enumerations.Trigonometry.Trigonometry(String)0111
expression.enumerations.Trigonometry.toString()0111
expression.enumerations.Variable.Variable(String)0111
expression.enumerations.Variable.toString()0111
expression.factors.ConstantFactor.ConstantFactor(BigInteger)0111
expression.factors.ConstantFactor.ConstantFactor(String)0111
expression.factors.ConstantFactor.ConstantFactor(int)0111
expression.factors.ConstantFactor.clone()1112
expression.factors.ConstantFactor.derive(Derivative)0111
expression.factors.ConstantFactor.expand()0111
expression.factors.ConstantFactor.hashCode()0111
expression.factors.ConstantFactor.isLessThan(ConstantFactor)0111
expression.factors.ConstantFactor.multiply(ConstantFactor)0111
expression.factors.ConstantFactor.plus(ConstantFactor)0111
expression.factors.ConstantFactor.substituteFunction(HashMap<Function, FunctionDefinition>)0111
expression.factors.ConstantFactor.substituteVariable(HashMap<Variable, Factor>)0111
expression.factors.ConstantFactor.toString()0111
expression.factors.DeriveFactor.DeriveFactor(Derivative)0111
expression.factors.DeriveFactor.DeriveFactor(Derivative, Expression)0111
expression.factors.DeriveFactor.clone()1112
expression.factors.DeriveFactor.derive(Derivative)0111
expression.factors.DeriveFactor.expand()0111
expression.factors.DeriveFactor.hashCode()0111
expression.factors.DeriveFactor.setExpression(Expression)0111
expression.factors.DeriveFactor.substituteFunction(HashMap<Function, FunctionDefinition>)0111
expression.factors.DeriveFactor.substituteVariable(HashMap<Variable, Factor>)0111
expression.factors.DeriveFactor.toString()0111
expression.factors.ExpressionFactor.ExpressionFactor(Expression)0111
expression.factors.ExpressionFactor.ExpressionFactor(Expression, int)0111
expression.factors.ExpressionFactor.ExpressionFactor(Term)0111
expression.factors.ExpressionFactor.binomialExpandTo(Expression)5144
expression.factors.ExpressionFactor.clone()1112
expression.factors.ExpressionFactor.getExponent()0111
expression.factors.ExpressionFactor.getExponentCombinedFactor()1122
expression.factors.ExpressionFactor.getExpression()0111
expression.factors.ExpressionFactor.hashCode()0111
expression.factors.ExpressionFactor.isSingleFactor()1122
expression.factors.ExpressionFactor.normalExpandTo(Expression)1122
expression.factors.ExpressionFactor.setExponent(int)0111
expression.factors.ExpressionFactor.substituteFunction(HashMap<Function, FunctionDefinition>)0111
expression.factors.ExpressionFactor.substituteVariable(HashMap<Variable, Factor>)0111
expression.factors.ExpressionFactor.toString()1112
expression.factors.FunctionFactor.FunctionFactor()0111
expression.factors.FunctionFactor.addArgument(Factor)0111
expression.factors.FunctionFactor.clone()1112
expression.factors.FunctionFactor.derive(Derivative)0111
expression.factors.FunctionFactor.expand()0111
expression.factors.FunctionFactor.hashCode()0111
expression.factors.FunctionFactor.setFunctionName(Function)0111
expression.factors.FunctionFactor.substituteFunction(HashMap<Function, FunctionDefinition>)2133
expression.factors.FunctionFactor.substituteVariable(HashMap<Variable, Factor>)0111
expression.factors.FunctionFactor.toString()0111
expression.factors.PowerFactor.PowerFactor(Variable, int)0111
expression.factors.PowerFactor.clone()1112
expression.factors.PowerFactor.getExponent()0111
expression.factors.PowerFactor.getVariable()0111
expression.factors.PowerFactor.hashCode()0111
expression.factors.PowerFactor.isDerivativeMatchinVariable(Derivative)4166
expression.factors.PowerFactor.setExponent(int)0111
expression.factors.PowerFactor.substituteFunction(HashMap<Function, FunctionDefinition>)0111
expression.factors.PowerFactor.substituteVariable(HashMap<Variable, Factor>)0111
expression.factors.PowerFactor.toString()1112
expression.factors.TrigonometryFactor.TrigonometryFactor(Trigonometry, Factor, int)0111
expression.factors.TrigonometryFactor.clone()1112
expression.factors.TrigonometryFactor.getBase()0111
expression.factors.TrigonometryFactor.getExponent()0111
expression.factors.TrigonometryFactor.hashCode()0111
expression.factors.TrigonometryFactor.setExponent(int)0111
expression.factors.TrigonometryFactor.substituteFunction(HashMap<Function, FunctionDefinition>)0111
expression.factors.TrigonometryFactor.substituteVariable(HashMap<Variable, Factor>)0111
expression.factors.TrigonometryFactor.toString()1112
expression.parsers.ExpressionParser.ExpressionParser(String)0111
expression.parsers.ExpressionParser.parseConstant()0111
expression.parsers.ExpressionParser.parseDerivative()0111
expression.parsers.ExpressionParser.parseExponent()0111
expression.parsers.ExpressionParser.parseExpression()3133
expression.parsers.ExpressionParser.parseFunction()2133
expression.parsers.ExpressionParser.parseTerm()3133
expression.parsers.ExpressionParser.parseTrigonometry()1122
expression.parsers.ExpressionParser.parseVariableFactor()3133
expression.parsers.FunctionDefinitionParser.FunctionDefinitionParser(String)0111
expression.parsers.FunctionDefinitionParser.parseFunctionDefinition()2133
expression.parsers.Parser.Parser(String)0111
expression.parsers.Parser.getAddOrSubtractOperator()2112
expression.parsers.Parser.getDerivative()3123
expression.parsers.Parser.getFunctionName()1113
expression.parsers.Parser.getSubstring()0111
expression.parsers.Parser.getTrigonometryType()2112
expression.parsers.Parser.getVariable()1113
expression.parsers.Parser.isAddOrSubtractOperator()0111
expression.parsers.Parser.isDerivative()0111
expression.parsers.Parser.isEndAt(int)0111
expression.parsers.Parser.isTrigonometry()0111
expression.parsers.Parser.isVariableFactor()0111
expression.parsers.Parser.skipBlankTerm()1112
expression.parsers.Parser.skipComma()0111
expression.parsers.Parser.skipEqualMark()0111
expression.parsers.Parser.skipLeftBracket()0111
expression.parsers.Parser.skipMultiplyOperator()0111
expression.parsers.Parser.skipPowerOperator()0111
expression.parsers.Parser.skipRightBracket()0111

为了满足代码风格的要求和控制调试时确定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 的数据来重构代码是有益的,但也不必过分追求数据的完美。

类复杂度分析

ClassOCavgOCmaxWMC
expression.Term2.55874
expression.parsers.Parser1.96453
expression.factors.ExpressionFactor1.84635
expression.Expression1.72431
expression.parsers.ExpressionParser2.18524
expression.factors.TrigonometryFactor1.75421
expression.factors.PowerFactor1.54420
expression.ancillaries.FunctionDefinition1.14316
expression.factors.ConstantFactor1.14316
expression.factors.FunctionFactor1.36315
expression.factors.DeriveFactor1.18313
expression.ancillaries.TermHash1.6735
expression.parsers.FunctionDefinitionParser1.5023
Main2.0022
expression.enumerations.Derivative1.0012
expression.enumerations.Function1.0012
expression.enumerations.Operator1.0012
expression.enumerations.Trigonometry1.0012
expression.enumerations.Variable1.0012
expression.ancillaries.ToStringLengthComparator1.0011

Term 类从各个指标来看都有着显著的最高的复杂度,大概是因为 Term 里可能含有各种各样的因子,对各种因子进行合并化简非常复杂导致的。另外对单个复杂方法抽出多个简单的小方法以降低 CogC 的做法,可能对类的复杂度统计造成负面影响。

三、项目迭代体验

关于重构

第一次作业我并没有按时完成,一方面是那一周遇上两门上学期课程补考,挤占了本不富裕的课下时间,另一方面是面向对象设计的能力和经验都很欠缺,以及对完成非玩具型的千行级中小型项目所需的时间预判有误,这些方面共同导致的。第一周的作业在提交截止前,我实现了使用递归下降法解析表达式,没能完成对表达式的展开,不过解析器的实现具有充足的可靠性和扩展性,这部分的代码在第二次和第三次作业中都可以可靠的运行,能够通过少量的增改实现新的需求。

第二次作业我在截止时间之前勉强完成提交,通过了所有的弱测与中测样例,不过代码的实现较为丑陋,容易出现 bug,没有使用多态降低代码复杂性、提高可扩展性,对于表达式因子的展开方法也很不简洁,没有利用递归的思想。

第三次作业在第二次作业上重构了许多。对于各种因子的类统一实现Factor接口,减少了大量的 instanceOf 判断,然后更多地利用 List 库中已有的方法实现功能而不是用若干个基础方法的组合来实现,降低了 NullPointerException 和 OutOfIndexException 异常的出现概率。利用递归的思想重写了表达式因子展开的方法,代码更加简洁可靠。在语法糖方面也大量使用了 Lambda 表达式来简化代码。

关于 Debug

第二次作业在强测和互测中被发现很多的 bug,根本原因在于缺少有效的测试,对各种边界条件的判断各种不足导致了许多本不该出现的bug,例如一个表达式的各项化简完为0,而表达式合并项后删除了表达式的所有项,导致表达式为空。还有对于嵌套函数调用,忘记了应在代入函数定义表达式时深拷贝,造成化简出错。

做第三次作业时,群里有同学写了评测机,评测机会随机生成符合要求的输入,对程序的输出用外部库验证是否与输入等价。我用同学写的评测机发现了很多bug,但是我疏忽了会造成 TLE 的样例,以为互测时的 cost 限制不会让我 TLE,结果并非如此。为了解决程序运行的性能问题,我又对代码做出了大量的修改。

感觉三次作业做完后,测试程序的 bug 仍然是我最为欠缺的能力,我希望在下个单元的作业中自己试着写评测机,以及手动全面构造测试样例,对每个类的方法进行测试。

...全文
59 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

442

社区成员

发帖
与我相关
我的任务
社区描述
2023年北京航空航天大学《面向对象设计与构造》课程博客
java 高校 北京·海淀区
社区管理员
  • 被Taylor淹没的一条鱼
  • 0逝者如斯夫0
  • Mr.Lin30
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧