309
社区成员
发帖
与我相关
我的任务
分享| 类名 (Class) | 属性数 (Fields) | 方法数 (Methods) | 总规模 (LOC) | 平均方法规模 (Avg LOC/M) | 最大分支数 (OCmax) |
|---|---|---|---|---|---|
| Parser | 1 | 14 | 140 | 10.00 | 10 |
| Lexer | 3 | 7 | 80 | 11.42 | 16 |
| Poly | 1 | 18 | 290 | 16.11 | 9 |
| Mono | 4 | 10 | 85 | 8.50 | 6 |
| Term | 2 | 5 | 40 | 8.00 | 3 |
| Expr | 2 | 4 | 35 | 8.75 | 3 |
| FuncCallFactor | 2 | 2 | 20 | 10.00 | 2 |
| MainClass | 0 | 2 | 75 | 37.50 | 9 |
数据分析:
核心的解析与计算逻辑集中于
Parser和Poly类。其中Poly类的总规模(LOC)最大,因为它承担了极复杂的公因式贪心提取和字符串极致化简任务。
Lexer和Parser的最大分支数(OCmax)较高,这是由递归下降算法的本质决定的。在没有使用自动化词法生成工具的情况下,必须使用密集的if-else分支来向前查看(Peek)并判定不同类型的 Token。AST 节点层(如
Term,Expr,FuncCallFactor等)属性数均控制在极低水平,方法规模极小,遵循了高内聚、低耦合的设计思路。
| class | OCavg (平均操作复杂度) | OCmax (最大操作复杂度) | WMC (加权方法复杂度) |
|---|---|---|---|
| Parser | 3.20 | 10 | 45 |
| Lexer | 4.10 | 16 | 29 |
| Poly | 3.50 | 9 | 63 |
| Mono | 1.80 | 6 | 18 |
| Term | 1.50 | 3 | 7 |
| FuncCallFactor | 1.50 | 2 | 3 |
| MainClass | 6.50 | 9 | 13 |
| Total | 178 |
复杂度分析:
整体代码平均复杂度较低。除了
Lexer和MainClass(存在预处理逻辑)的平均复杂度稍微偏高以外,真正核心的代数运算类Mono和 AST 节点类的OCavg均极低(低于 2.0)。证明我的架构将复杂的表达式操作成功下放并拆解到了单一职责的对象中。

整个架构分为两层:前端 AST 解析树 与 底层代数运算引擎。
解析层 (Parser & AST):遵循严格的递归下降文法,由 Lexer 提供词法单元,Parser 构建出由 Expr, Term 以及各类 Factor 组成的抽象语法树。
运算层 (Math Engine):由 Poly (多项式) 和 Mono (单项式) 构成。所有的 AST 节点最终都会调用 .toPoly() 方法,化为纯数学的 Poly 对象。
优点:实现了真正意义上的归一化。无论外层嵌套了多么离谱的函数调用或求导算子,进入底层后仅仅是两个 Poly 对象在做纯数学运算,彻底免疫了 AST 树节点的指数级膨胀。
HW1 到 HW2:从纯粹的多项式引入了嵌套和指数函数。得益于初版架构中剥离了 Poly 的纯数学计算属性,我在 HW2 仅仅增加了 ExpFactor 并在 Mono 中增加了处理 exp 内部表达式的属性,并没有经历重构。
HW3:加入了求导算子和的递推函数。还是“实参先求值,AST树上做代换”的原则。在处理 FuncCallFactor 时,我先把实参计算为一个 Poly,再带入到函数体中。
ln(x)情景描述:支持 ln 嵌套及其求导。
可扩展性分析:我的架构具备极强的扩展性。在解析层,只需在 Parser 的 parseFactor 分支中新增对 ln 的识别,并新建 LnFactor。在计算层,在 Mono 中新增针对 ln 内部表达式的引用。由于每个节点自带 toPoly(),求导只需在 LnFactor 中依据链式法则生成对应的分数多项式结构即可。
在第三次强测中,我遭遇了一次极其隐蔽且惨烈的TLE。
1. Bug :
在处理强测中类似 36 层指数嵌套 exp(exp(exp...)) 的极端数据时,我的程序发生了严重超时。
经过复盘,问题出在 Poly 类的 buildExpString 方法中。为了做公因式提取的贪心判断,当未发生提取时,程序会对内层 expPoly 错误地调用了两次深层 toString()(一次用于暂存,一次用于比对)。在 36 层嵌套下,这 2 次调用瞬间裂变,时间复杂度退化为 O(2^N),导致了数十亿次的无效遍历。
2. 修复与复杂度降低策略:
我立刻将该方法重构,引入了 origInner = expPoly.toString() 作为起始缓存变量,确保在同一个方法栈中绝对不重复计算深层字符串,瞬间将复杂度降回 O(N)。
编写自己代码时发现自己犯的错误会记录,用于互测时的hack。
此外,舍友hack到的比较好用的数据也会进行分享。
为了追求性能分,我的代码实现了以下优化:
底层设计了 isZero() 判断,遇到常数求导等情况,多项式会直接输出物理最短的 1 或 0。
遇到项合并时,自动将正数项前置(例如 -x+y 重排为 y-x。
在处理 exp 内部多项式时,遍历单项式提取全局最大公约数。在优化时,我坚持了“暴力比对”原则,即分别生成提取和不提取的字符串,让 Java 比较长度,谁短用谁。但是最终就是由于这个优化不够完善导致了强测TLE了一个点。
| 场景 | AI 使用率 | 评估与效果 |
|---|---|---|
| 架构设计讨论 | 20% | 核心底层架构、解析逻辑基本独立手写完成。 |
| 疑难 Bug 定位 | 20% | 在排查 O(2^N) 超时问题时,投喂给 AI,辅助指出了重复的递归树分支调用。 |
| 极端用例构造 | 30% | 辅助生成了包含多层递推、高阶偏导和条件分支嵌套的数据。 |
对于房间里的其他人是否大量使用ai生成代码,本人不敢妄自判断。
经过第一单元的学习和作业,我再次加深了对于面向对象编程的了解,此外这么大的代码编写也使得我的编程能力进一步提高。同样我也认识到了自己的诸多不足,对于java语言的不熟悉,使我还是吃了不少苦头。
整个第一单元的设计还是很完善的,就是任务量有点太大了,对于我这种对于java熟悉度不够高的更是挑战有点大。可以增加挑战性任务,完成加分,不完成不扣分,减少同学们的压力。