BUAA_OO UNIT1总结
BUAA_OO UNIT1总结
总体框架
UML图

更简洁的UML图

框架分析
在我的设计中,项目被分成了三个部分:
第一部分为解析部分,采用了递归下降的策略,功能是对输入的表达式进行语法解析。这一部分很大程度上参考了hw_1的training,并在之后的迭代中进行了小规模重构以支持自定义函数解析。
第二部分为数据处理部分,主要是将已解析的token转化为可计算的数据,并实现了运算的方法。由于浅拷贝可能造成错误,因此这一部分的对象被设计为不可修改的,这样就在一定程度上解决了拷贝方式可能带来的问题。
第三部分主要由MainClass组成,负责输入输出和预处理。如果未来的迭代需要特殊的输入输出方式,可以为输入输出新建类。但由于三次作业中的输入输出相对简单,因此没有改变在main函数中实现输入输出的方式。
复杂度分析
方法复杂度表
| Method | CogC | ev(G) | iv(G) | v(G) |
|---|---|---|---|---|
| Function.Function() | 0 | 1 | 1 | 1 |
| Function.Function(String) | 6 | 3 | 6 | 6 |
| Function.getAns(Poly, Poly, Poly, Parser) | 3 | 1 | 4 | 4 |
| Function.getParaNum() | 0 | 1 | 1 | 1 |
| Lexer.Lexer() | 0 | 1 | 1 | 1 |
| Lexer.Lexer(Lexer) | 0 | 1 | 1 | 1 |
| Lexer.Lexer(String) | 24 | 1 | 17 | 21 |
| Lexer.getToken() | 2 | 2 | 2 | 2 |
| Lexer.isEmpty() | 0 | 1 | 1 | 1 |
| Lexer.next() | 0 | 1 | 1 | 1 |
| Lexer.print() | 1 | 1 | 2 | 2 |
| Lexer.processAdd(String, int) | 6 | 1 | 4 | 5 |
| Lexer.processSub(String, int) | 6 | 1 | 4 | 5 |
| MainClass.main(String[]) | 5 | 1 | 5 | 5 |
| Mono.Mono() | 0 | 1 | 1 | 1 |
| Mono.Mono(BigInteger, BigInteger, Poly) | 0 | 1 | 1 | 1 |
| Mono.add(Mono) | 0 | 1 | 1 | 1 |
| Mono.detailedPrint() | 2 | 2 | 3 | 3 |
| Mono.differentiate() | 1 | 2 | 1 | 2 |
| Mono.getCoefficient() | 0 | 1 | 1 | 1 |
| Mono.getExp() | 0 | 1 | 1 | 1 |
| Mono.getIndex() | 0 | 1 | 1 | 1 |
| Mono.isCompatible(Mono) | 2 | 3 | 1 | 3 |
| Mono.isEqual(Mono) | 2 | 3 | 1 | 3 |
| Mono.mul(Mono) | 0 | 1 | 1 | 1 |
| Mono.negate() | 0 | 1 | 1 | 1 |
| Mono.print(boolean) | 15 | 2 | 5 | 12 |
| Mono.printExp(boolean) | 29 | 1 | 6 | 9 |
| Parser.Parser() | 0 | 1 | 1 | 1 |
| Parser.ParserExpression(Lexer) | 12 | 11 | 12 | 12 |
| Parser.ParserFactor(Lexer) | 23 | 8 | 12 | 14 |
| Parser.ParserFunction(Lexer, Function) | 3 | 1 | 4 | 4 |
| Parser.ParserTerm(Lexer) | 13 | 12 | 13 | 13 |
| Parser.getFuncF() | 0 | 1 | 1 | 1 |
| Parser.getFuncG() | 0 | 1 | 1 | 1 |
| Parser.getFuncH() | 0 | 1 | 1 | 1 |
| Parser.getParaMap() | 0 | 1 | 1 | 1 |
| Parser.initF(String) | 0 | 1 | 1 | 1 |
| Parser.initG(String) | 0 | 1 | 1 | 1 |
| Parser.initH(String) | 0 | 1 | 1 | 1 |
| Parser.parserFactorExp(Lexer) | 6 | 1 | 5 | 5 |
| Parser.parserFactorVariable(Lexer) | 5 | 1 | 4 | 4 |
| Parser.setFuncF(Function) | 0 | 1 | 1 | 1 |
| Parser.setFuncG(Function) | 0 | 1 | 1 | 1 |
| Parser.setFuncH(Function) | 0 | 1 | 1 | 1 |
| Parser.setParserFunction(boolean) | 0 | 1 | 1 | 1 |
| Poly.Poly() | 0 | 1 | 1 | 1 |
| Poly.Poly(Mono) | 0 | 1 | 1 | 1 |
| Poly.add(Poly) | 0 | 1 | 1 | 1 |
| Poly.detailedPrint() | 1 | 1 | 2 | 2 |
| Poly.differentiate() | 1 | 1 | 2 | 2 |
| Poly.getGcd() | 4 | 4 | 2 | 4 |
| Poly.isEmpty() | 3 | 3 | 2 | 4 |
| Poly.isEqual(Poly) | 9 | 6 | 4 | 6 |
| Poly.isFactor() | 7 | 5 | 4 | 8 |
| Poly.mul(Poly) | 3 | 1 | 3 | 3 |
| Poly.negate() | 1 | 1 | 2 | 2 |
| Poly.power(BigInteger) | 2 | 2 | 2 | 3 |
| Poly.print() | 6 | 4 | 5 | 6 |
| Poly.print(BigInteger) | 6 | 4 | 5 | 6 |
| Poly.simplify() | 14 | 5 | 6 | 7 |
| Poly.sub(Poly) | 0 | 1 | 1 | 1 |
| Pre.Process(String) | 10 | 1 | 9 | 9 |
| Token.Token(TokenType, String) | 0 | 1 | 1 | 1 |
| Token.getContent() | 0 | 1 | 1 | 1 |
| Token.getType() | 0 | 1 | 1 | 1 |
| Token.getValue() | 3 | 3 | 3 | 3 |
| Token.print() | 0 | 1 | 1 | 1 |
类复杂度表
| Class | OCavg | OCmax | WMC | |
|---|---|---|---|---|
| Function | 3 | 6 | 12 | |
| Lexer | 3.56 | 18 | 32 | |
| MainClass | 5 | 5 | 5 | |
| Mono | 2.79 | 11 | 39 | |
| Parser | 3.56 | 14 | 64 | |
| Poly | 3.31 | 7 | 53 | |
| Pre | 6 | 6 | 6 | |
| Token | 1.4 | 3 | 7 | |
| TokenType | n/a | n/a | 0 |
分析
一些方法的复杂度明显偏高。以Lexer.Lexer(String)方法为例,这个方法的结构大概是这样:
Lexer(String str) {
// ...
while (pos < str.length()) {
char charNow = str.charAt(pos);
if (charNow == '+') {
// ...
} else if (charNow == '-') {
// ...
} else if (charNow == '*') {
// ...
} else if (charNow == '(') {
// ...
} else if (charNow == ')') {
// ...
}
// ...
else if (charNow == ')') {
// ...
}
}
}
实际上,其他的复杂度较高的方法,比如Parser.ParserExpression(Lexer)、Parser.ParserFactor(Lexer)、Parser.ParserFunction(Lexer, Function)、Parser.ParserTerm(Lexer),大都也有大面积的if判断。事实上,可以通过多态等更OO的方式解决这个问题。因此,在以后的作业中,我应该用更OO的方式设计程序。
迭代情况
hw_1
第一次作业的要求相对简单,但一个好的架构可以显著地降低之后迭代的工作量。因此,在第一次作业中,我一共进行了两次重构(因此有三个版本)。
第一版:采用了递归下降的思想进行解析,并使用正则表达式进行判断。但是,这一版的递归下降相对简陋,比如没有实现Lexer而是直接对字符串解析。由于解析过程写的比较随意,结果第一版的程序在解析+++1之类的数据时遇到困难。我认识到了按照构造规则进行解析的重要性(方便迭代时新规则的加入),因此有了第二版。
第二版:使用搜索的策略,严格按照构造规则解析。指导书中有规定"带符号整数内不允许包含空白字符,注意符号与整数之间也不允许包含空白字符",这让我意识到空白符也许并不是完全没有意义的(比如,+1的'+'可以解释为表达式前的符号、项前的符号、带符号的整数的符号,但是+ 1的'+'就不能解释为带符号的整数的符号)。因此,我写了搜索去寻找输入表达式的一种可能的解释。但是后来发现搜索算法的时间复杂度过高,在浏览讨论区的删空白符和消符号的优化后后,我认识到任何一个合理的解析都是正确的,并且直接解析总是可以得到一个合理的解析,于是有了第三版。
第三版:采用了较为完整的递归下降。在认真学习了training的代码后,我认识到递归下降的方法本身就有良好的可扩展性,于是在重构时,我较为完整的实现了递归下降。
hw_2
这一次作业要求加入自定义函数与exp函数。得益于递归下降,自定义函数的加入并不复杂。在自定义函数的实现方法上,由于函数表达式与最终表达式的构造规则较为接近,因而我没有采用字符串替换,而是对原有的词法解析进行扩展使其能支持表达式解析。这样可以从一定程度上避免引入新符号所带来的问题。但是由于在hw_1中我使用HashMap存储数据,为了避免HashMap嵌套带来的复杂度,我将数据部分重构为采用ArrayList表示数据。括号嵌套已经在上一次作业中实现,基本没有修改。
hw_3
这一次作业要求加入求导。其实求导可以看做一种的运算,因此直接在原有的方法上进行了扩展。函数嵌套已经在上一次作业中实现,基本没有修改。
优化分析
第一次作业中实现了基本的的输出优化(先输出正系数项,系数指数为一时省略等)。
第二次作业在第一次作业的基础上加入了简单的gcd优化(在输出exp时提取exp表达式的gcd,并选取提取gcd输出与原样输出中更短的作为输出结果)。
bug分析
在三次作业中,我都搭建了简易的评测机,但还是出现了一些bug:
- 在hw_2中,由于输出优化导致出现了exp(-x)^2之类的错误格式。
- 在hw_2中,由于输出优化时的疏漏导致没有特判exp(0)。
- 在hw_3中,由于迭代时的疏忽导致在解析dx(x)*x等数据时会卡死。
这些bug反映出我测试方式的缺陷。实际上,这些bug正中我评测机的盲区。
- 在hw_2时评测机只进行了值的比较而没有检查格式,并且没有测试极限数据。
- 在hw_3中,由于测试样例生成具有随机性,因此有时可能会出现很长的测试样例,导致程序运行很长的时间。而在实现批量评测时,我只保留了评测结果和很少的提示信息,在遇到评测机卡死时直接重启(认为是遇到了较大的数据),结果忽略了bug导致评测机卡死这一情况。
事实上,在互测时,基于随机数据生成器发现bug的可能性比较小(毕竟同房的代码已经经过了强测的检验)。反而通过枚举一些极端的数据可能发现bug。
心得体会
良好的架构非常重要。在设计架构时应该有一定的前瞻性。比如我在hw_1的时候就实现了括号嵌套,在hw_2的时候就实现了函数嵌套,这让之后的迭代过程变得不那么复杂。相应的,由于我在hw_1的时候使用HashMap存储数据,扩展较为困难,导致在hw_2时进行了较大规模的重构。
由于上学期没有参加OOP,我在hw_1设计架构的时候采取的很多方式并不是十分OO。在hw_2与hw_3迭代时由于之前的设计具有一定的扩展性,因此对许多内容并没有做大的改变。结果是在hw_3完成后,我的程序不OO的地方有很多(比如大量的if判断,没有使用继承等OO特性)。这导致我程序的复杂度较高,也为bug的出现埋下了隐患(hw_3的bug出现在了一个复杂度较高的方法中)。在之后的作业中,我应该更多的采用OO的方式解决问题。
此外,在做测试的时候应该更加谨慎,不能迷信评测机。应该让评测机支持更全面的测试,并且采取多种不同的测试手段(比如阅读代码)。
相关推荐
这条线索还管不管?研判有效期机制避免过期线索浪费精力.docx
科易网基于40亿+科创知识图谱数据库,深度探索AI技术在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,研究科技创新领域的AI+数智化解决方案,推动科技创新与产业创新智能化发展。
产业园区运营负责人如何利用平台提升园区企业技术对接效率?.docx
科易网基于40亿+科创知识图谱数据库,深度探索AI技术在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,研究科技创新领域的AI+数智化解决方案,推动科技创新与产业创新智能化发展。
电子商务概论(第七版)课程标准+课件+教案+习题答案
电子商务概论(第七版)课程标准+课件+教案+习题答案
基于格子 Boltzmann 方法的二维通道多孔介质流动模拟:理论、实现与验证(Matlab代码实现)
内容概要:本文系统阐述了基于格子Boltzmann方法(LBM)的二维通道多孔介质流动模拟的理论基础、Matlab代码实现过程及结果验证方法。详细介绍了LBM在处理复杂多孔结构流动问题中的独特优势,如对微观几何形态的高度适应性与天然并行性。采用D2Q9离散速度模型构建数值算法,通过设定合理的边界条件与碰撞算子,实现了多孔介质内流体速度场、压力场等关键物理量的动态演化模拟,并对模拟结果进行了可视化分析与物理机制解读。最终通过与理论解或已有数值结果对比,验证了所实现代码的准确性与稳定性,为相关领域的研究提供了可复现的技术路径。; 适合人群:具备流体力学基础知识和Matlab编程能力的研究生、科研人员及工程技术人员,特别适用于从事多孔介质渗流、微尺度流动建模、计算流体力学(CFD)方法研究及相关交叉学科的学者;; 使用场景及目标:①深入理解格子Boltzmann方法的核心原理及其在复杂边界流动问题中的应用技巧;②掌握利用Matlab实现LBM算法的具体流程,包括网格划分、分布函数演化、边界处理与数据后处理;③为开展多孔介质传热传质、地质渗流、生物组织血流等课题研究提供可靠的数值仿真工具与代码参考; 阅读建议:建议读者结合文中详尽的算法步骤与Matlab代码逐行研读,亲自动手运行、调试并修改程序,尝试改变多孔介质的几何构型、雷诺数等参数以观察流场变化,从而深化对LBM数值特性与物理内涵的理解,并在此基础上拓展至更复杂的实际应用场景。
输电线异物数据集2739张YOLO+VOC格式.zip
数据集格式:VOC格式+YOLO格式 压缩包内含:3个文件夹,分别存储图片、xml、txt文件 JPEGImages文件夹中jpg图片总计:2739 Annotations文件夹中xml文件总计:2739 labels文件夹中txt文件总计:2739 标签种类数:4 标签名称:["balloon","kite","nest","trash"] 每个标签的框数(注意yolo格式类别顺序不和这个对应,而以labels文件夹classes.txt为准): balloon 框数 = 766 kite 框数 = 935 nest 框数 = 1480 trash 框数 = 6036 总框数:9217 图片清晰度(分辨率:像素):清晰 图片是否增强:是 标签形状:矩形框,用于目标检测识别 重要说明:暂无 特别声明:本数据集不对训练的模型或者权重文件精度作任何保证,数据集只提供准确且合理标注


