BUAA-OO-表达式解析与求导
BUAA-OO-表达式解析与求导
解析
按照常规,解析这一部分我们分为词法分析与语法分析。当然由于待解析的字符串较简单,词法分析器和语法分析器不必单独实现。
词法分析器
按照常规,我们先手写一个词法分析器,而不使用正则表达式。
词法分析器:读取字符流,产生标记流。它聚合字符形成单词,并应用一组规则来判断每个单词在源语言中是否合法,如果合法则为其分配一个语法范畴,产生一个标记。
我们的词法分析器行为如下:
- 如果 当前输入有定义,则 为其产生一个标记(token, token value)。譬如:当前输入是"+114514",则为其产生标记(Num, +114514);当前输入是"*",则为其产生标记(Op,*),诸如此类。
- 如果 当前的输入无定义,则 抛出错误。譬如:当前输入是"y",则抛出错误。
可能你已经发现,这个词法分析器并不能很好地满足我们的需求:当输入为"+114514"时,如何判断是"+",或是"+114514"呢?
这里我们需要额外介绍一个概念:lookahead,即提前看多个字符。由于有一个或多个标记是以相同的字符开头的,仅凭当前的字符无法确定具体应该解释为哪个标记,所以只能再向前查看字符。以"+114514"为例,在解析到"+"之后,还需要向前查看一个字符"1",此时就能够确定当前输入对应的标记种类应为 Num。
具体实现
完成词法分析器的行为定义之后,我们开始进行更为详尽的设计。
首先,由定义,我们可以得到我们所需的标记种类:
/**
* Num : 数字
* Op : 运算符
* Sin : Sin函数
* Cos : Cos函数
* X : 幂函数
* LP : 左括号
* RP : 右括号
* NULL: 字符流末尾
*/
enum TokenType {
Num, Op, Sin, Cos, X, LP, RP, NULL
}
接下来就是枯燥的枚举:
void getTok() {
token="";
tokenType = TokenType.NULL;
// consume blank char
{ /* some code */ }
// reach the end
{ /* some code */ }
switch (currentCharacter) {
case 'x' :
/* some code */
case 's' :
/* some code */
default :
throw new someKindOfException();
}
词法分析器部分告一段落。
语法分析器
文法
首先根据定义给出文法
<expr> ::= <expr> + <term>
| <expr> - <term>
| <term>
<term> ::= <term> * <factor>
| <factor>
<factor> ::= (<expr>)
| Num
| sin(<factor>)
...
在文法中出现了两种符号,一种是被<>包围的非终结符,如<expr>,可以用 ::= 右侧的式子替代;另一种是没有出现在 ::= 左侧的终结符,如 Num,一般对应于词法分析器输出的标记。
解析过程
然后是递归下降的解析过程,关于什么是递归下降,稍后会进行解释。以 1*(2+3) 为例
<expr> => <expr>
=> <term> * <factor>
=> <factor> |
=> Num (3) |
=> (<expr>)
=> <expr> + <term>
=> <term> |
=> <factor> |
=> Num (2) |
=> <factor>
=> Num (3)
整个解析的过程是在不断对非终结符进行替换(向下),直到遇到了终结符(底)。在解析的过程中,有的非终结符,如<expr>被递归地使用了。
递归下降:从起始非终结符开始,不断地对非终结符进行分解,直到匹配输入的终结符。
可以看出,整个解析的过程和我们的文法是十分相近的,我们可以很容易地将文法直接转换成实际的代码,只需为每个非终结符定义一个对应的函数。不过,很显然我们的文法是没有办法直接翻译成实际代码的,这是编译原理的内容了,此处不再赘述。
除了递归下降以外,还可以选择使用自底向上的方法进行语法分析,由于是手写语法分析器,我们不考虑采用自底向上。
语法树
根据解析过程,我们很自然地会想到树这种数据结构。一个简略的语法树如下
expr
/ | \
term term ...
/ | \
num sin cos ...
具体实现
以下是根据文法直接翻译的一个不可能的实现。
Expr parseExpr() {
Expr result = new Expr();
loop {
// create a node
Term term = parseTerm();
// attach
result.addNode(term);
}
return result;
}
Term parseTerm() {
Term result = new Term();
loop {
// create a node
Factor factor = parseFactor();
// attach
result.addNode(factor);
}
return result;
}
Factor parseFactor() {
Factor result = new Factor();
loop {
{/* some code */}
}
return result;
}
至此,已完成对输入字符串的解析。
求导
根据字符串解析的方法,我们求导的方式也是自顶向下的。
以下是根据语法树得到的一个不可能的实现
Expr exprDiff() {
Expr result = new Expr();
for (term : termContainer) {
result.addNode(termDiff(term));
}
return result;
}
Term termDiff() {
Term result = new Term();
for (factor : factorContainer) {
result.addNode()
}
return result;
}
Factor factorDiff() {
return diff();
}
个人实现分析
度量分析
UML类图:

Method Metrics:

Class Metrics:

以上为第三次表达式解析与求导作业的UML图和代码指标度量。可以看得出来其中不乏有许多设计和算法问题,譬如
- 没有另外设置一个Factor的抽象类或者接口,而是将各种Functions直接继承自Expr
- 模块间的耦合度高,没有合理地设计每个类暴露的接口形状
- 没有在创建树结点时存储其相应的HASH值,而是在每次进行相等性判断时都进行一次递归运算,大大提高了时间复杂度
- 在判断相等时用的是简单的遍历比较,而不是设计一个可以避免碰撞的HASH函数
设计模式
简单地运用了工厂模式,将各种Functions的创建托管至Expr。但是这并不是一个好的设计,应该如上述,Expr和各种Functions都继承自Factor抽象类,然后创建一律托管至FactorFactory。否则Functions实现的改变可能会影响Expr实现的改变,这将增大迭代开发和后期维护的复杂度。
BUAA-OO-表达式解析与求导的更多相关文章
- OO第一单元总结——求导
一.基于度量分析程序结构 (一)第一次作业 (1)设计思路 本次作业只涉及到简单幂函数通过加减运算而复合而成的函数,因此笔者自然的把函数分成了函数本体以及单个的项两个部分,在笔者的设计中两个类的功能如 ...
- oo第一次博客-三次表达式求导的总结与反思
一.问题回顾与基本设计思路 三次作业依次是多项式表达式求导,多项式.三角函数混合求导,基于三角函数和多项式的嵌套表达式求导. 第一次作业想法很简单,根据指导书,我们可以发现表达式是由各个项与项之间的运 ...
- OO_Unit1_表达式求导总结
OO_Unit1_表达式求导总结 OO的第一单元主要是围绕表达式求导这一问题布置了3个子任务,并在程序的鲁棒性与模型的复杂度上逐渐升级,从而帮助我们更好地提升面向对象的编程能力.事实也证明,通过这 ...
- 2019年北航OO第1单元(表达式求导)总结
2019年北航OO第1单元(表达式求导)总结 1 基于度量的程序结构分析 量化指标及分析 以下是三次作业的量化指标统计: 关于图中指标在这里简要介绍一下: ev(G):基本复杂度,用来衡量程序非结构化 ...
- 2020 OO 第一单元总结 表达式求导
title: BUAA-OO 第一单元总结 date: 2020-03-19 20:53:41 tags: OO categories: 学习 OO第一单元通过三次递进式的作业让我们实现表达式求导,在 ...
- OO第一单元作业总结——表达式求导
OO第一单元作业总结 第一次作业 基于度量分析代码结构 基本算法 第一次作业是简单多项式导函数求解,不需要对输入数据的合法性进行判定, 基本思想是用 (coeff, expo)表示二元组 coeff* ...
- OO第一单元总结——表达式求导
第一次作业 (1) UML结构图 (2)结构分析 Polynomial 类是对输入的字符串进行预处理,其中包括判断格式是否合法,运算符简化,分割成项等方法. Polynomial处理后得到的每一个项的 ...
- OO Unit 1 表达式求导
OO Unit 1 表达式求导 面向对象学习小结 前言 本博主要内容目录: 基于度量来分析⾃己的程序结构 缺点反思 重构想法 关于BUG 自己程序出现过的BUG 分析⾃己发现别人程序bug所采⽤的策略 ...
- 2019年北航OO第一单元(表达式求导任务)总结
2019面向对象课设第一单元总结 一.三次作业总结 1. 第一次作业 1.1 需求分析 第一次作业的需求是完成简单多项式导函数的求解,表达式中每一项均为简单的常数乘以幂函数形式,优化目标为最短输出.为 ...
随机推荐
- @EnableFeignClients 注解
feignClents在spring容器里找不到的原因 当使用的feignClents 来自引用别的工程时,需要指定包名,如果不指定就算使用ComponentScan 扫描也不行 import org ...
- json和java对象相互转换
json和java对象相互转换 springboot中json转换默认使用的是jackson包,通过spring-boot-starter-web依赖的 1 在属性上添加注解@JsonFormat(p ...
- 深入研究 Mini ASP.NET Core(迷你 ASP.NET Core),看看 ASP.NET Core 内部到底是如何运行的
前言 几年前,Artech 老师写过一个 Mini MVC,用简单的代码告诉读者 ASP.NET MVC 内部到底是如何运行的.当时我研究完以后,受益匪浅,内心充满了对 Artech 老师的感激,然后 ...
- 随心测试_软测基础_006<测试人职业发展>
接上篇:熟悉了_测试人员的工作职责范围与具体的工作内容 ,如何规划:测试人员的职业路线呢? 贴心小提示:以下内容,仅供参考,不挖坑 Q1:如何规划测试工程师的职业发展路线? A1:SX的观点:预定目标 ...
- 01-Redhat/Centos7网卡命名介绍及修改方式
1. Redhat/Centos7网卡命名介绍 1.1 网络设备命名 Redhat/Centos7提供在网络接口中使用新的网络设备命名方法.这些功能会更改系统中的网络接口名称,以便定位和区分这些接口. ...
- Γ(a) 的两种方差与均值
所以 这里是满足 Be(x+1,n-x+1),如果是要服从Be(a,b) 相应的后验概率
- SSM项目使用GoEasy 获取客户端上下线实时状态变化及在线客户列表
一.背景 上篇SSM项目使用GoEasy 实现web消息推送服务是GoEasy的一个用途,今天我们来看GoEasy的第二个用途:订阅客户端上下线实时状态变化.获取当前在线客户数量和在线客户列表.截止我 ...
- CentOS安装python3.6
下载Python安装包 cd /usr/local/src 编译时要提前装好gcc编译器和zlib zlib-devel 1.下载文件 wget https://www.python.org/ftp/ ...
- 集合源码分析[3]-ArrayList 源码分析
历史文章: Collection 源码分析 AbstractList 源码分析 介绍 ArrayList是一个数组队列,相当于动态数组,与Java的数组对比,他的容量可以动态改变. 继承关系 Arra ...
- 洛谷P2179 骑行川藏
什么毒瘤... 解:n = 1的,发现就是一个二次函数,解出来一个v的取值范围,选最大的即可. n = 2的,猜测可以三分.于是先二分给第一段路多少能量,然后用上面的方法求第二段路的最短时间.注意剩余 ...