309
社区成员
发帖
与我相关
我的任务
分享| Class | OCavg | OCmax | WMC |
|---|---|---|---|
| ChoiceFactor | 1.50 | 2 | 3 |
| DeriveFactor | 1.50 | 2 | 3 |
| ExpFactor | 1.50 | 2 | 3 |
| Expr | 1.50 | 2 | 3 |
| ExprFactor | 1.00 | 1 | 2 |
| Factor | 1.00 | 1 | 3 |
| FuncCallFactor | 1.00 | 1 | 2 |
| FuncDef | 1.00 | 1 | 3 |
| Lexer | 4.00 | 22 | 40 |
| MainClass | 3.00 | 5 | 9 |
| Mono | 1.82 | 5 | 20 |
| NumFactor | 1.00 | 1 | 2 |
| Parser | 5.18 | 15 | 57 |
| Poly | 4.25 | 10 | 85 |
| RecursiveFuncCallFactor | 1.00 | 1 | 2 |
| RecursiveFuncDef | 4.00 | 7 | 8 |
| RecursiveFuncDefParser | 3.33 | 8 | 20 |
| Term | 1.33 | 2 | 4 |
| Token | 1.00 | 1 | 3 |
| Token.Type | n/a | n/a | 0 |
| VarFactor | 1.50 | 2 | 3 |
| Method | CogC | ev(G) | iv(G) | v(G) |
|---|---|---|---|---|
| ChoiceFactor.ChoiceFactor(Factor, Factor, Factor, Factor) | 0 | 1 | 1 | 1 |
| ChoiceFactor.toPoly() | 2 | 2 | 2 | 2 |
| DeriveFactor.DeriveFactor(Expr, String) | 0 | 1 | 1 | 1 |
| DeriveFactor.toPoly() | 2 | 1 | 2 | 2 |
| ExpFactor.ExpFactor(Factor) | 0 | 1 | 1 | 1 |
| ExpFactor.toPoly() | 1 | 2 | 2 | 2 |
| Expr.addTerm(Term) | 0 | 1 | 1 | 1 |
| Expr.toPoly() | 1 | 1 | 2 | 2 |
| ExprFactor.ExprFactor(Expr) | 0 | 1 | 1 | 1 |
| ExprFactor.toPoly() | 0 | 1 | 1 | 1 |
| Factor.getIndex() | 0 | 1 | 1 | 1 |
| Factor.setIndex(int) | 0 | 1 | 1 | 1 |
| Factor.toPoly() | 0 | 1 | 1 | 1 |
| FuncCallFactor.FuncCallFactor(FuncDef, Factor) | 0 | 1 | 1 | 1 |
| FuncCallFactor.toPoly() | 0 | 1 | 1 | 1 |
| FuncDef.FuncDef(String, Expr) | 0 | 1 | 1 | 1 |
| FuncDef.getBody() | 0 | 1 | 1 | 1 |
| FuncDef.getParamName() | 0 | 1 | 1 | 1 |
| Lexer.Lexer(String) | 0 | 1 | 1 | 1 |
| Lexer.getCurrentToken() | 0 | 1 | 1 | 1 |
| Lexer.getIndex() | 0 | 1 | 1 | 1 |
| Lexer.handleSign(char) | 15 | 1 | 8 | 15 |
| Lexer.isEnd() | 0 | 1 | 1 | 1 |
| Lexer.nextToken() | 0 | 1 | 1 | 1 |
| Lexer.parseNumber(int) | 3 | 1 | 5 | 5 |
| Lexer.preProcess(String) | 1 | 1 | 2 | 2 |
| Lexer.setIndex(int) | 0 | 1 | 1 | 1 |
| Lexer.tokenize() | 30 | 1 | 35 | 37 |
| MainClass.findSingleEquals(String) | 6 | 3 | 6 | 7 |
| MainClass.main(String[]) | 4 | 1 | 5 | 5 |
| MainClass.parseFuncDef(String) | 0 | 1 | 1 | 1 |
| Mono.Mono(BigInteger, BigInteger) | 0 | 1 | 1 | 1 |
| Mono.Mono(BigInteger, BigInteger, BigInteger, Poly) | 3 | 1 | 2 | 3 |
| Mono.Mono(BigInteger, BigInteger, Poly) | 3 | 1 | 2 | 3 |
| Mono.getCoeff() | 0 | 1 | 1 | 1 |
| Mono.getExpInner() | 0 | 1 | 1 | 1 |
| Mono.getXExp() | 0 | 1 | 1 | 1 |
| Mono.getYExp() | 0 | 1 | 1 | 1 |
| Mono.isSameType(Mono) | 6 | 5 | 1 | 7 |
| Mono.multiply(Mono) | 5 | 1 | 4 | 5 |
| Mono.negate() | 0 | 1 | 1 | 1 |
| Mono.withCoeff(BigInteger) | 0 | 1 | 1 | 1 |
| NumFactor.NumFactor(String) | 0 | 1 | 1 | 1 |
| NumFactor.toPoly() | 0 | 1 | 1 | 1 |
| Parser.Parser(Lexer) | 0 | 1 | 1 | 1 |
| Parser.isConditionWrapped() | 14 | 2 | 7 | 12 |
| Parser.parseChoiceFactor() | 16 | 1 | 15 | 15 |
| Parser.parseDeriveFactor(String) | 4 | 1 | 5 | 5 |
| Parser.parseExpFactor() | 4 | 1 | 5 | 5 |
| Parser.parseExpr() | 9 | 1 | 6 | 7 |
| Parser.parseFactor() | 22 | 1 | 18 | 20 |
| Parser.parseFuncCall() | 18 | 2 | 15 | 15 |
| Parser.parseTerm(int) | 2 | 1 | 3 | 3 |
| Parser.setFuncDef(FuncDef) | 0 | 1 | 1 | 1 |
| Parser.setRecFuncDef(RecursiveFuncDef) | 0 | 1 | 1 | 1 |
| Poly.Poly() | 0 | 1 | 1 | 1 |
| Poly.Poly(int) | 0 | 1 | 1 | 1 |
| Poly.addMono(Mono) | 0 | 1 | 1 | 1 |
| Poly.addPoly(Poly) | 0 | 1 | 1 | 1 |
| Poly.derive(String) | 1 | 1 | 2 | 2 |
| Poly.deriveMono(Mono, String) | 22 | 5 | 8 | 10 |
| Poly.equals(Object) | 22 | 10 | 7 | 14 |
| Poly.expToString(Poly) | 12 | 6 | 5 | 11 |
| Poly.formatSingleGroup(ArrayList, BigInteger) | 18 | 1 | 6 | 14 |
| Poly.hashCode() | 0 | 1 | 1 | 1 |
| Poly.isZero() | 0 | 1 | 1 | 1 |
| Poly.monoToString(Mono) | 13 | 1 | 9 | 11 |
| Poly.multiPoly(Poly) | 3 | 1 | 3 | 3 |
| Poly.negate() | 1 | 1 | 2 | 2 |
| Poly.polyToString(Poly) | 12 | 5 | 6 | 10 |
| Poly.powerPoly(BigInteger) | 6 | 2 | 5 | 5 |
| Poly.print() | 1 | 2 | 2 | 2 |
| Poly.simplify() | 10 | 5 | 5 | 6 |
| Poly.substitute(Poly) | 6 | 1 | 4 | 4 |
| Poly.subtractPoly(Poly) | 0 | 1 | 1 | 1 |
| RecursiveFuncCallFactor.RecursiveFuncCallFactor(RecursiveFuncDef, int, Factor) | 0 | 1 | 1 | 1 |
| RecursiveFuncCallFactor.toPoly() | 0 | 1 | 1 | 1 |
| RecursiveFuncDef.RecursiveFuncDef(Expr, Expr, BigInteger, Factor, int, BigInteger, Factor, ...) | 0 | 1 | 1 | 1 |
| RecursiveFuncDef.evaluate(int) | 15 | 2 | 7 | 7 |
| RecursiveFuncDefParser.expect(Lexer, Type) | 2 | 1 | 3 | 3 |
| RecursiveFuncDefParser.findSingleEquals(String) | 6 | 3 | 6 | 7 |
| RecursiveFuncDefParser.parse(String, String, String) | 2 | 1 | 2 | 2 |
| RecursiveFuncDefParser.parseCoeff(Lexer) | 2 | 1 | 3 | 3 |
| RecursiveFuncDefParser.parseRecurrence(String, Expr, Expr) | 13 | 1 | 8 | 11 |
| RecursiveFuncDefParser.skipUntil(Lexer, Type) | 3 | 1 | 4 | 4 |
| Term.Term(int) | 0 | 1 | 1 | 1 |
| Term.addFactor(Factor) | 0 | 1 | 1 | 1 |
| Term.toPoly() | 1 | 1 | 2 | 2 |
| Token.Token(Type, String) | 0 | 1 | 1 | 1 |
| Token.getType() | 0 | 1 | 1 | 1 |
| Token.getValue() | 0 | 1 | 1 | 1 |
| VarFactor.VarFactor(String) | 0 | 1 | 1 | 1 |
| VarFactor.toPoly() | 2 | 1 | 2 | 2 |
我们可以发现整体项目代码的核心在于parser,lexer和poly这三个类,他们的主要作用分别是语法分析、词法分析和多项式处理。
总体来看,本次设计的代码架构属于高内聚情况。代码的高内聚比例达到了65%,其中主要的类都属于高内聚(Poly, Lexer, Parser, Mono, Token 等核心类);而中等内聚主要是一些拥有大量数据存储型属性的类,他的占比20% (RecursiveFuncDef, Expr, Term)。最后的低内聚占比约为15%,其中均为Factor的子类。
| 类 | 入度* | 出度** | 扇出 | 耦合强度 |
|---|---|---|---|---|
| Poly | 9 | 2 | 中 | 强(中心) |
| Parser | 2 | 8 | 高 | 强(枢纽) |
| Lexer | 2 | 2 | 低 | 中 |
| Factor | 11 | 0 | 低 | 中(父类) |
| Mono | 5 | 1 | 低 | 中 |
| Token | 2 | 0 | 低 | 弱 |
| Expr | 3 | 1 | 低 | 弱 |
| Term | 2 | 1 | 低 | 弱 |
本次设计的代码的总体耦合度评估为中等。
我们代码中的主要耦合链如下:
MainClass
→ Lexer(词法分析)
→ Parser(语法分析)
→ Expr → Term → Factor(表达式树)
→ Poly → Mono(计算核心)
而在Parser和各个Factor子类以及递归类之间的耦合则较弱。
总体来说,本次代码设计的主要耦合集中在了框架定义方面————词法-语法-语义。
┌─────────────────────────────────────────────┐
│ 应用层:MainClass │
│ (输入 → 解析 → 计算 → 输出) │
└────────────┬────────────────────────────────┘
│
┌────────────▼────────────────────────────────┐
│ 词法分析层(Lexer)& 语法分析层(Parser) │
│ 职责:String → Token序列 → 抽象语法树 │
└────────────┬────────────────────────────────┘
│
┌────────────▼────────────────────────────────┐
│ 语义处理层:Poly & Mono(数学计算核心) │
│ 职责:抽象语法树 → 多项式 → 结果输出 │
└─────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────┐
│ 应用层:MainClass │
│ 负责整个流程的协调和结果输出 │
└────────────────────────────┬─────────────────────────────────────────┘
│
┌────────────────────────────▼──────────────────────────────────────────┐
│ 第一层:词法分析(Lexer) │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ preProcess() → tokenize() → handleSign() → parseNumber() │ │
│ │ (预处理) (主要流程) (符号处理) (数字解析) │ │
│ └──────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ String → Token[] │
└────────────────────────────┬──────────────────────────────────────────┘
│
┌────────────────────────────▼──────────────────────────────────────────┐
│ 第二层:语法分析(Parser) │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ parseExpr() │ │
│ │ ↓ (处理 + 和 -) │ │
│ │ parseTerm() │ │
│ │ ↓ (处理 * 和 /) │ │
│ │ parseFactor() │ │
│ │ ↓ (处理幂运算、括号、函数调用、三目表达式) │ │
│ │ Factor 多态调度(13个子类) │ │
│ │ - NumFactor / VarFactor / ExpFactor │ │
│ │ - FuncCallFactor / DeriveFactor / ChoiceFactor │ │
│ │ - RecursiveFuncCallFactor / ... │ │
│ └──────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ Token[] → Expr → Poly │
└────────────────────────────┬──────────────────────────────────────────┘
│
┌────────────────────────────▼──────────────────────────────────────────┐
│ 第三层:表达式结构(Expr → Term → Factor) │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ Expr Term Factor │ │
│ │ ├─ List<Term> ├─ List<Factor> ├─ abstract │ │
│ │ ├─ addTerm() ├─ addFactor() ├─ toPoly() │ │
│ │ └─ toPoly() └─ toPoly() └─ setIndex() │ │
│ └──────────────────────────────────────────────────┬────────────────┘ │
│ │ │
│ Factor.toPoly() 递归调用 Term.toPoly(), │
│ Term.toPoly() 递归调用 Factor.toPoly() │
└────────────────────────────┬──────────────────────────────────────────┘
│
┌────────────────────────────▼──────────────────────────────────────────┐
│ 第四层:函数管理(Function & Recursion) │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ FuncDef RecursiveFuncDef │ │
│ │ ├─ name: String ├─ base0, base1: Expr │ │
│ │ ├─ body: Expr ├─ cache: HashMap<Int, Poly> │ │
│ │ ├─ call(Poly) ├─ evaluate(int n) │ │
│ │ └─ substitute() └─ 记忆化优化(O(2^n) → O(n)) │ │
│ │ │ │
│ │ FuncCallFactor ──→ FuncDef.call() │ │
│ │ RecursiveFuncCallFactor ──→ RecursiveFuncDef.evaluate() │ │
│ └──────────────────────────────────────────────────────────────────┘ │
└────────────────────────────┬──────────────────────────────────────────┘
│
┌────────────────────────────▼──────────────────────────────────────────┐
│ 第五层:数学计算核心(Mono & Poly)【关键层】 │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ Mono(单项式) Poly(多项式) │ │
│ │ ┌────────────────────────────┐ ┌─────────────────────────┐ │ │
│ │ │ • coeff: BigInteger │ │ • monos: List<Mono> │ │ │
│ │ │ • xexp: BigInteger │ │ • addPoly() │ │ │
│ │ │ • yexp: BigInteger │ │ • multiPoly() │ │ │
│ │ │ • expInner: Poly ★★★ │ │ • simplify() │ │ │
│ │ │ │ │ • derive() │ │ │
│ │ │ 方法: │ │ • powerPoly() │ │ │
│ │ │ • multiply() │ │ • substitute() │ │ │
│ │ │ • isSameType() │ │ • toPoly() / print() │ │ │
│ │ │ • withCoeff() │ │ │ │ │
│ │ └────────────────────────────┘ └──────┬──────────────────┘ │ │
│ │ │ │ │
│ │ ◆ 递归关系(关键创新): │ │
│ │ Mono 中的 expInner 指向 Poly │ │
│ │ Poly 的 monos 包含 Mono │ │
│ │ 这形成了自然的递归结构,支持 exp 无限嵌套 ◆ │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
│ 运算示例: │
│ • exp(x) → Mono(1, 1, Poly{x}) │
│ • exp(exp(x)) → Mono(1, 0, Poly{Mono(1, 1, Poly{x})}) │
│ • (x+1)^2 → Poly{Mono(1,2), Mono(2,1), Mono(1,0)} → simplify │
└────────────────────────────┬──────────────────────────────────────────┘
│
┌────────────────────────────▼──────────────────────────────────────────┐
│ 输出:格式化字符串(toString()) │
│ • 动态规划优化:公因式提取 │
│ • 缓存机制:避免重复计算 │
└──────────────────────────────────────────────────────────────────────┘
优点:架构清楚明了,支持多次的迭代和优化,将整个代码分成了三大板块,各自分工明确。
不足:Pasrser的语法分析过于冗长,Factor子类过多
第一次作业我采用了三层架构来处理多项式表达式。将字符串通过逐层解析转化为多项式对象,再进行数学运算。
数据结构: 简单的系数-幂次映射
HashMap<Integer, Integer>
key: x 的幂次
value: 该项的系数
解析器: 递归下降解析
Expr → Term → Factor
按优先级处理加减、乘法、幂运算
计算层: Poly 类统一处理所有运算
addPoly():多项式加法multiPoly():多项式乘法 powerPoly():多项式幂运算评价:架构清晰简洁,但 无法表示 exp(x) 这样的项,限制了功能扩展。
第二次作业需要支持 exp 嵌套、函数定义等复杂需求,原有的 HashMap 数据结构无法满足。我引入了 Mono 类 来表示更复杂的项,改用 ArrayList<Mono> 存储,并为 exp 运算添加了新的方法。
核心改进:Mono 类的递归设计
class Mono {
BigInteger coeff; // 系数
BigInteger xexp; // x 的幂次
Poly expInner; // exp 内部的多项式(递归!)
}
在这里我使用了递归结构:在Mono 中包含 Poly,而 Poly 是 List<Mono>,形成了自然的递归关系。这样我们就可以表示任意深度的 exp 嵌套了。
同时我也需要重新设计几个运算逻辑,例如:
isSameType():判断同类项时需要比较 exp 内容,不仅仅是幂次.multiply():两项相乘时,exp 相乘遵循规则 exp(u)·exp(v) = exp(u+v).simplify():合并同类项需要考虑 exp 信息.最后我新增了内容以便支持这次迭代:用于支持函数定义和调用的Function 类;用于处理函数调用因子的FuncCallFactor;以及处理三目表达式 的ChoiceFactor;最后是处理 exp() 函数的ExpFactor。
评价:保留了第一次的架构框架,通过新增 Mono 类和改变存储方式优雅地扩展了功能。但 ArrayList 的顺序存储导致合并同类项时需要 O(n²) 的复杂度,性能有所下降。不过这个设计足够稳定,为第三次作业的扩展奠定了坚实基础。
第三次作业不需要在底层进行重构,他只需要在第二次稳定的数据结构基础上,进一步扩展运算功能。我新增了 RecursiveFuncDef 类独立处理递推函数,在 Poly 类中添加 derive() 方法处理导数运算,同时支持了 y 变量。
核心设计:
RecursiveFuncDef 类用 HashMap 缓存计算结果,实现记忆化优化Poly 层面分解链式法则,最终拆分为基本的加减乘运算yexp 字段存储 y 的幂次评价:在不改动底层数据结构的前提下,通过设计新类和扩展已有方法优雅地实现了新功能,体现了第二次重构设计的成功。
三次作业的迭代过程比较流畅,主要问题在于用ArrayList去储存,这导致了代码性能方面的下降。
现象:第二次作业中,Lexer 的 handleSingle 方法对于前导符号和运算符号的解析存在混乱,导致某些符号组合无法正确处理。
根本原因:handleSingle 方法职责过重,试图在一个方法中处理多种不同的符号场景。
解决方案:引入 preProcess() 前置处理方法,负责预先对相邻符号进行合并和化简,然后再将规范化后的 token 流传入 handleSingle。这样通过分工明确的两个方法替代了一个臃肿的方法,提高了代码的可维护性。
现象:计算多层嵌套的指数时,指数的数值超出了int存储范围,同时计算速度太慢。
解决方案:我把将指数改用 BigInteger 存储,支持任意大小的指数,同时相应的修改了相关方法;同时在指数计算时采用了快速幂算法,将时间复杂度降至 O(log n)
虽然没有搭建完整的测评机,但通过分析同学代码,我发现了一些规律性的问题:
第一次作业:主要问题集中在符号组合处理上,特别是在连续符号的识别。我针对这类场景构建了测试数据。
第二、三次作业:主要问题出现在函数运算和大规模展开后的项合并上。在函数递推或乘法展开后,如何正确识别和合并同类项成为关键。
总体而言,我在互测过程之中发现的实质性 bug 并不多,这反映出大多数同学代码的基础实现是正确的。不过这也让我意识到搭建完整的自动化测评机确实是必要的,能够系统地发现边界情况和性能问题。
1. 三目表达式的解析
对于 [condition ? exprA : exprB],
根据 condition 的结果只解析需要的分支,
跳过不需要的另一分支的解析
→ 减少不必要的计算
2. 输出优化(toString 方法)
通过动态规划和公因式提取,优化输出字符串的长度。例如,3x + 6x + 9x 可以输出为 3(x + 2x + 3x) 或直接合并为 18x。
3. 递推函数的记忆化缓存
使用 HashMap 缓存已计算过的递推值,避免重复计算。时间复杂度从 O(2^n) 降至 O(n)。
| 序号 | 正确性 | 性能 |
|---|---|---|
| 第一次作业 | 5% | 10% |
| 第二次作业 | 10% | 30% |
| 第三次作业 | 10% | 0% |
大模型的主要使用场景:第一次作业中的输出序列优化;第二次作业中的动态dp算法以及三目运算中项的分解;第三次作业递归函数的计算。
对于代码的优化部分,大模型完成的很好,我的动态dp算法由大模型生成,同时,我的递归函数计算过程也由大模型指导,在这两个小模块代码之中,大模型的完成效果显著。
对于寻找bug环节,大模型实现的效果并不理想,很多时候会虚构出一些bug或者构造不符合输入原则的序列。
在本次作业中,我自己最大的体会就是底层代码的重要性和性能对于代码影响的重要性。由于在第二次作业的时候为了方便而使用了ArrayList去存储Mono,我的代码在性能方面一直处于劣势,而这一错误也一直在我的代码中贯穿,直到总结时才发现自己的问题。
同时,明确自己代码的架构对于程序的构建也至关重要。在多次的迭代之中,我们需要抓住本质的架构,在修改方法、增加变量、增改类的繁琐工作之中,只有掌握好自己代码的框架,在每次迭代之前明确自己的修改范围和方法,才能够保证自己的改进能够完整而正确。
可以在第一次作业时在指导书之中加入对于代码架构设计的指引,这样可以让同学们不用在之后的迭代中反复推倒重建自己的代码架构。