题目背景介绍
- 本次作业中需要完成的任务为:读入一系列自定义函数的定义以及一个包含幂函数、指数函数、自定义函数调用、求导算子的表达式,输出恒等变形展开所有括号后的表达式。
在本次作业中,展开所有括号的定义是:对原输入表达式 E做恒等变形,得到新表达式 E′。其中,E′中不再含有自定义函数,不再含有求导算子,且只包含必要的括号
设定的形式化表述
- 表达式 → 空白项 [加减 空白项] 项 空白项 | 表达式 加减 空白项 项 空白项
- 项 → [加减 空白项] 因子 | 项 空白项 * 空白项 因子
- 因子 → 变量因子 | 常数因子 | 表达式因子|求导因子
- 变量因子 → 幂函数 | 指数函数 | 自定义函数调用
- 常数因子 → 带符号的整数
- 表达式因子 → ‘(’ 表达式 ‘)’ [空白项 指数]
- 幂函数 → 自变量 [空白项 指数]
- 自变量 → ‘x’
- 指数函数 → ‘exp’ 空白项 ‘(’ 空白项 因子 空白项 ‘)’ [空白项 指数]
- 指数 → ‘^’ 空白项 [‘+’] 允许前导零的整数 (注:指数一定不是负数)
- 带符号的整数 → [加减] 允许前导零的整数
- 允许前导零的整数 →→ (‘0’|‘1’|‘2’|…|‘9’){‘0’|‘1’|‘2’|…|‘9’}
- 空白项 → {空白字符}
- 空白字符 → (空格) |
\t
- 加减 → ‘+’ | ‘-’
迭代
本单元共经历三次迭代
- 处理基本表达式,只包含单层括号展开和加减乘除以及指数
- 处理多层嵌套括号,引入自定义函数和指数函数
- 处理自定义函数调用,引入求导因子
个人架构
最终架构如下图所示,相关类以及函数的功能已经在图上做出阐释
下面对各个类的功能以及运行流程做出具体说明
类名 | 解释说明 |
---|---|
MainClass | 程序入口,负责输入输出和调用 |
Initial | 负责初始化,删除空格/合并正负号/调用Diyfunc |
Diyfunc | 处理自定义函数,添加定义/传参 |
Lexer | 输入流,分析字符 |
Parser | 解析器,负责解析表达式,转为Expr |
Unit | 储存基本单元,内容为x的指数和exp的指数 |
Basic | 储存化简后表达式,多项式功能 |
Simplification | 用于计算和化简,调用计算器Calculation |
Calculation | 计算器,用于具体实现各种运算 |
Var Num ExpFactor Power Term Expr | 储存表达式各个层次,实现递归下降,均实现Factor 接口,处理求导因子 |
复杂度分析
最后发现有这几个方法的复杂度爆红,其中Initial.begin
、DiyFunc.passParameter
、Calculation.mul
最为严重
Initial.begin
方法复杂度高的原因是处理各种传入的字符时分支过多,在每个字符下又有各自的分支,导致认知复杂度过高,应该将其分化处理Diyfunc.passParameter
由于我将参数传递放在初始化部分,只能对字符串进行处理,因而出现了许多特判,另外还有递归调用的情况,提高了复杂度Calculation.mul
的复杂度高主要原因是在Unit
和Basic
中出现了相互包含关系,从而使得代码的可读性不高,而且存在多重循环用于处理各个项的乘法
这些类也是由于上述方法的复杂度过高而导致爆红
个人迭代历程
我想着重说一下第二次到第三次迭代,由于第一次迭代我在计算时将exp
的指数存为String
类型,假如计算出的exp
指数分别为x+x^2
和x^2+x
,这很显然是相等的两个项,但是在字符串层面他们并不相等,故此在第三次作业中做出了以下改变
- 新增
Unit
基本单元,不再采用HashMap<>
存储,包含powForX
和Basic
分别用于存储自变量x的指数和exp函数的表达式 - 重写
equals
方法,用于判断不同unit
是否相等,方便合并同类项 - 重写
HashCode
方法,用于多项式检索相应的Unit
基本单元 - 调整相应计算方法,适应新的迭代
bug分析
在第三次作业时出现了一个很抽象的bug,由于出现函数可以相互调用的功能,故我在解析函数定义时会检测并调用传参方法消除定义中的其他函数,如下代码
public void addFunc(String str) {
...
if (matcher1.find()) {
...
for (int i = 0; i < s.length();i++) {
if (definations.containsKey(String.valueOf(s.charAt(i)))) {//判断是否有其他自定义函数
def = passParameter(s); //调用传参函数
}
}
...
}
definations.put(funcName,varAndDef);
}
但是我忽略了自定义函数中会出现三个形参xyz
,若将传参放在这一步则会出现重复替换的问题
2
f(x,y,z) = x*exp(y)+z
g(x,y,z) = f(y,z,x) + x^2
若出现以上情况则会得到
g(x,y,z) = x*exp(x)+x //wrong
我的处理方法是将xyz
替换为abc
,以此来消除歧义
小tips:有的同学对exp中的x进行了特判,但我无意中发现replaceAll
中的替换是正则形式,完全可以用 replaceAll( "x(?!p)" , string)
替换,不需要再写判断
继续迭代
若有继续迭代,例如加入三角函数等,目前架构也可以很好的兼容。
- 需要新增三角函数因子
- 在
Unit
中添加三角函数的参数basicForTri
以及powForTri
即可 - 需要重写
equals
方法,用于判断基本项是否相等 - 还需要增加新的化简方法,例如倍角公式、平方和为1等等
心得体会
- 作为正课的第一个单元,难度比pre提升很多,刚开始也是无从下手,好在有学长提供的博客以及课程组的推送才得以完成
- 在迭代中不断进步,良好的架构应该是可扩展的、可复用的,我在这一点做的远远不够,甚至有很多时候为了省时省力而精简许多要求,这对持续迭代十分不利
- 另外我的代码层次性很差,还是存在好多高耦合的类,不太符合我们的基本原则,还需要持续改进
- 日后在开发时应该注意分段式开发,及时测试,而不是想要一次性完成再进行测试,这样会导致功能堆积过多,出现问题不能够随时锁定相关环节
- 感谢老师、助教、同学的各种分享和帮助,希望日后可以取得更多进步!