301
社区成员
发帖
与我相关
我的任务
分享
Main:
接受输入,实例化Function,FunctionReplace(r)与Parser,存储函数输入并利用后二者解析输入表达式字符串。
Function:
自定义函数类。存储自定义函数供FunctionReplace(r)使用。
FunctionReplace:
工具类。根据提供的Function对表达式进行第一步的解析替换。起名失误。
Parser:
工具类。解析字符串得到表达式。
Operator:
表达式类。存储了所有项。存储有关指数函数中指数部分的字符串与表达式的对应关系以避免重复调用。实现了打印、取反、求导三个方法。同时由于不区分表达式、项与因子,三者共用这一个类。
Add:
工具类,继承自Operator。实现表达式的加法。
Mul:
工具类,继承自Operator。实现表达式的乘法。
Pow:
工具类,继承自Operator。实现表达式与数的乘方。
Factor:
工具类,继承自Operator。实现对不同因子的解析。
基于对表达式、项、因子的统一化表述设计了类,设计了尽可能兼容了对于题中任意对象的工具类,实现了除了Pow以外对表达式的的完全兼容,简化了相当大一部分思路,但也对性能优化造成了麻烦,同时提高了工具类方法的内部复杂度。

实用代码仅600+行,其中臃肿部分主要在于Operator类中的打印方法。因hw1设计中并未兼容exp输出,hw2时也并未进行重构而是针对exp输出进行了整套的重写,大幅提高了代码的臃肿程度,降低了可复用性。其余部分集成度较高。
| method | CogC | ev(G) | iv(G) | v(G) |
|---|---|---|---|---|
| src.Mul.Mul(Operator, Operator) | 27.0 | 1.0 | 13.0 | 13.0 |
| src.Parser.parse(String) | 19.0 | 6.0 | 7.0 | 7.0 |
| src.Operator.printExpFirst(boolean, String, PrintStream) | 16.0 | 3.0 | 7.0 | 8.0 |
| src.Operator.printFirst(boolean, PrintStream) | 16.0 | 3.0 | 7.0 | 8.0 |
| src.FunctionReplace.functionReplace(String, ArrayList) | 13.0 | 1.0 | 7.0 | 7.0 |
| src.Operator.printExpSecond(boolean, String, PrintStream) | 13.0 | 3.0 | 6.0 | 7.0 |
| src.Operator.printSecond(boolean, PrintStream) | 13.0 | 3.0 | 6.0 | 7.0 |
| src.Operator.printExp(boolean, String, PrintStream) | 12.0 | 1.0 | 8.0 | 8.0 |
| src.Parser.findFirst(String) | 12.0 | 1.0 | 5.0 | 9.0 |
| src.FunctionReplace.findFactor(String) | 11.0 | 4.0 | 5.0 | 8.0 |
| src.Operator.print(boolean, PrintStream) | 10.0 | 1.0 | 8.0 | 8.0 |
| src.Add.Add(Operator, Operator) | 9.0 | 1.0 | 6.0 | 6.0 |
| src.FunctionReplace.searchEnd(String) | 8.0 | 4.0 | 2.0 | 5.0 |
| src.Parser.findForth(String) | 8.0 | 1.0 | 3.0 | 6.0 |
| src.Parser.findSecond(String) | 8.0 | 1.0 | 3.0 | 6.0 |
| src.Operator.derive() | 7.0 | 1.0 | 5.0 | 5.0 |
| src.Pow.Pow(Operator, Operator) | 7.0 | 1.0 | 3.0 | 4.0 |
| src.Factor.Factor(String) | 6.0 | 6.0 | 7.0 | 7.0 |
| src.Parser.isNum(char) | 6.0 | 4.0 | 1.0 | 7.0 |
| src.Operator.turn() | 2.0 | 1.0 | 3.0 | 3.0 |
| src.Parser.findThird(String) | 2.0 | 2.0 | 2.0 | 3.0 |
| src.MainClass.main(String[]) | 1.0 | 1.0 | 2.0 | 2.0 |
| src.Operator.printThird(boolean, PrintStream) | 1.0 | 1.0 | 2.0 | 2.0 |
| src.Function.Function(String, String, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
| src.Function.getFactors() | 0.0 | 1.0 | 1.0 | 1.0 |
| src.Function.getFrom() | 0.0 | 1.0 | 1.0 | 1.0 |
| src.Function.getTo() | 0.0 | 1.0 | 1.0 | 1.0 |
| src.Operator.Operator() | 0.0 | 1.0 | 1.0 | 1.0 |
| src.Operator.Operator(TreeMap<BigInteger, BigInteger>) | 0.0 | 1.0 | 1.0 | 1.0 |
| src.Operator.Operator(TreeMap<BigInteger, BigInteger>, HashMap<String, Operator>) | 0.0 | 1.0 | 1.0 | 1.0 |
| src.Operator.Operator(TreeMap<BigInteger, BigInteger>, HashMap<String, Operator>, HashMap<String, Operator>) | 0.0 | 1.0 | 1.0 | 1.0 |
| src.Operator.copy(Operator) | 0.0 | 1.0 | 1.0 | 1.0 |
| src.Operator.getArray() | 0.0 | 1.0 | 1.0 | 1.0 |
| src.Operator.getHash() | 0.0 | 1.0 | 1.0 | 1.0 |
| src.Operator.getMap() | 0.0 | 1.0 | 1.0 | 1.0 |
| src.Operator.toString() | 0.0 | 1.0 | 1.0 | 1.0 |
| Total | 227.0 | 64.0 | 131.0 | 159.0 |
| Average | 6.305555555555555 | 1.7777777777777777 | 3.638888888888889 | 4.416666666666667 |
表1
可见代码复杂度较高。维护存在明显难度。其中复杂度峰值集中在解析方法parse()、打印方法print()及其附属方法以及乘法构造方法Mul()。可以看出虽然复杂度高但符合预期,单个方法理解与测试成本偏高,但类提供接口整体足够复用。
首先从hw1的形式化表述来看,表达式这一单位可以包含任一较小单元 (包括指数)。从数学逻辑上分析,任意运算符都可以以表达式作为操作数。因此可以忽视项与因子的层次,以表达式与算符为基础单位进行解析。但根据之后的架构发展来看,数学逻辑与形式化表述并不完全一致,这也导致了性能的降低。
对于具体的解析步骤而言,采用递归下降的方法进行解析。由于各运算符之间有着严格的逻辑顺序,从前往后,(优先级) 从高到低,而递归树会先解析两子树再计算当前节点,所以只需按照倒序依次查找并解析各运算符即可完成基本的解析。可以分析出叶子节点一定是单个变量x或常数,将该层级单独抽出作为递归终点即可。同时因为变量和常数均为因子层级,为了提高可扩展性和逻辑连贯性,将表达式因子与前二者的解析同时分离出parse,组成新的构造类Factor进行进一步解析,这为之后exp与dx算符的兼容提供便利。
hw2加入了自定义函数与exp算符。自定义函数的实现相对简单,只需进行简单的字符串替换即可。其中需要注意的是如何保护特殊字段中的字符,我选择了通过正则表达式匹配时,仅匹配运算符之后的函数变量名 (字母) 来避免匹配到算符中的字母。整体上并没有增加太多代码量和思考量。而exp的扩展则是出了个大麻烦。exp的操作数被固定位一个因子,而我的架构中并没有保留因子这一层级,导致只能通过加括号将表达式强行改为一个表达式因子的方式实现,这大大降低了性能。而为了合并同类项维护HashMap的过程中,写toString()方法时甚至弄出了非法操作。而hw1中写的print()方法也未能预留兼容exp的接口,导致为exp专门重写了一套输出方法。但除此之外,hw1的架构几乎保留了下来。
hw3加入了dx算符,这与原先架构的兼容性良好。由于本身就只有表达式这一层级,只需在Operator类中新增求导方法即可解决所有问题。然而因为交错版本导致没有通过强侧并不在博主的意料之内。除此之外,hw3几乎没有对架构本身产生任何影响,hw1的架构也被证实为使用上基本合理,但和题意有些许相悖的架构。
在面向对象程序的编写中,对于对象层次、关联等的架构如何的确非常重要。其实在这一单元的作业中,我也经常了感觉到先前的程序架构对新增的功能不够友好——感觉没有到要重构的进步,但不重构又觉得束手束脚。因此在最初架构时,还是最好提前预想之后可能会有的需求,提前预留好相应的接口以避免大规模的重构。
个人认为,表达式化简作为面向对象的第一单元的选题是相当合适的,包含的内容和限制也恰到好处。只是我从解题的角度来说会更希望exp的操作数是一个表达式而不是被限定为因子。不过考虑到这可能是提供优化角度有意为之,又觉得很合理。因此也想不到能够优化的建议了。