302
社区成员
发帖
与我相关
我的任务
分享本单元的三次作业,让我从零开始构建了一个支持变量、函数、求导的表达式解析并展开括号化简的小程序。从上学期末尾学习的递归下降,到后来为了支持函数调用和指数函数进行AST重构,再到最后扩展到双变量和求导,每一步都充满了挑战和收获。
| 类名 | 属性数 | 方法数 | 代码行数 | 主要职责 |
|---|---|---|---|---|
Lexer | 3 | 3 | 80 | 词法分析,将输入转为Token流 |
Parser | 3 | 8 | 200 | 语法分析,递归下降构建AST |
Symbol (interface) | - | 3 | 10 | 符号接口 |
VarSymbol | 1 | 4 | 40 | 变量符号(x或y) |
ExpSymbol | 1 | 5 | 60 | 指数函数符号 |
Monomial | 1 | 8 | 120 | 单项式(因子的乘积) |
NormalizedExpr | 1 | 15 | 350 | 多项式(单项式之和) |
Recurrence | 4 | 2 | 30 | 递推公式存储 |
Main | 0 | 1 | 30 | 主程序入口 |
Tokentype | - | - | 5 | 保存Token |
第一次作业要求解析单变量表达式,支持加减乘、乘方和括号。设计很直观:
Lexer 将输入转为Token流。Parser 按照文法递归下降,每个非终结符返回一个多项式(HashMap<Integer, BigInteger>,指数→系数)。这个阶段的核心是把文法直接映射为代码,简单可靠。
第二次作业新增了自定义函数 f(x)、指数函数 exp(...) 和选择式因子。原有的多项式表示法(只包含x的幂)无法处理 exp(x) 这种非幂函数,因为 exp(x) 和 exp((x^2)) 本质是不同的原子。于是我进行了一次重构:
Symbol 接口,将变量 x 和 exp(参数) 都视为原子符号。Monomial 表示多个符号的乘积(如 x * exp(y))。NormalizedExpr 表示多项式。这个重构让表达式树变成了符号的多项式,将不同种类的因子统一为符号,后续的代入、求导都变得统一。
函数调用通过 substitute 实现:将定义中的 x 替换为实参表达式。选择式因子则先计算 A-B 是否为零,再选择分支。
第三次作业将变量扩展为 x 和 y,新增了求导算子 dx、dy、grad 和递推函数 f{n}(x)。
双变量的支持很简单:将 VarSymbol 改为携带名称(x 或 y),多项式运算完全通用。
求导的实现是关键:
NormalizedExpr 中添加 derivative(String var)(即求导) 方法。exp(f) 求导得 exp(f) * f',对乘积求导使用乘积法则。grad 直接展开为 dx + dy。递推函数则单独存储 f{0}、f{1} 和递推公式,在调用时递归展开(序号≤5,可缓存)。
Monomial 的 equals 和 compareTo 没有正确实现,导致相同内容但顺序不同的单项式被认为不等。改用 TreeMap 后解决。exp(f) 只算了自身,忘了乘 f'。修正为 exp(f) * f'。f{n}(g) 时,对 f{n-1}(arg) 中的 arg 没有替换外部实参。修正为递归前先对 arg 做代入。derivative、substitute)编写小样例。TreeMap 自动排序,每次运算后清理零系数项。pow 用快速幂减少乘法次数。f{k}(arg) 结果缓存,避免重复递归。exp 参数必要时加括号。本单元的三次作业让我在压力中快速成长。感谢课程组和互测小伙伴们,这段经历将成为我学习面向对象编程路上的宝贵财富。