2022年春季学期
计算学部《软件构造》课程
Lab 2实验报告
姓名 | 艾浩林 |
学号 | 120L021917 |
班号 | 2003006 |
电子邮件 | 2017869860@qq.com |
手机号码 | 18891201338 |
3.1.1 Get the code and prepare Git repository· 1
3.1.2 Problem 1: Test Graph <String>· 1
3.1.3 Problem 2: Implement Graph <String>· 1
3.1.3.1 Implement ConcreteEdgesGraph· 2
3.1.3.2 Implement ConcreteVerticesGraph· 2
3.1.4 Problem 3: Implement generic Graph<L>· 2
3.1.4.1 Make the implementations generic· 2
3.1.4.2 Implement Graph.empty()· 2
3.1.5 Problem 4: Poetic walks· 2
3.1.5.2 Implement GraphPoet· 2
本次实验训练抽象数据类型(ADT)的设计、规约、测试,并使用面向对象
编程(OOP)技术实现 ADT。 具体来说:
针对给定的应用问题,从问题描述中识别所需的 ADT;
设计 ADT 规约(pre-condition、 post-condition)并评估规约的质量;
根据 ADT 的规约设计测试用例;
ADT 的泛型化;
根据规约设计 ADT 的多种不同的实现; 针对每种实现,设计其表示
( representation)、表示不变性( rep invariant)、抽象过程( abstraction
function)
使用 OOP 实现 ADT,并判定表示不变性是否违反、 各实现是否存在表
示泄露(rep exposure);
测试 ADT 的实现并评估测试的覆盖度;
使用 ADT 及其实现,为应用问题开发程序;
在测试代码中,能够写出 testing strategy 并据此设计测试用例。
打开IDEA,创建新项目Lab2—120L021917
将目标代码在URL地址下载好并导入Lab2项目的src里面
在这里给出你的GitHub Lab2仓库的URL地址(Lab2-学号)。
https://github.com/ComputerScienceHIT/HIT-Lab2-120L021917
请仔细对照实验手册,针对两个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
本次实验给出了一个图接口,要求我们建立一个边图类、一个点图类分别继承自这个图接口,并且在里面实现一系列方法,并且实现抽象数据型,并用这个图的抽象数据型完成poet的工作。将任意一个文本读入,并根据该文本的单词前后连接情况,创建一个语料库,这个语料库的点就是各个单词,边就是单词的前后连接情况,边的权重就是这个词组出现的次数,最后输入一段话,要求程序能根据这段话中的关键字,基于语料库的边给这句话扩充
如何从GitHub获取该任务的代码、在本地创建git仓库、使用git管理本地开发。
在 Get the code 步骤中,你无法连接 MIT 的 Athena 服务器,请从以下地
址获取初始代码:
https://github.com/rainywang/Spring2022_HITCS_SC_Lab2/tree/master/P1
将代码下载到我的文件中,修改文件路径并开始编写
-
-
- Problem 1: Test Graph <String>
-
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
测试策略:
- 只测试顶点没有边的情况
- 只测试途中没有邻接的边的情况
- 测试add方法:加入若干个点以后在重复加入,分别判断输出是否为true和false
- 测试set方法:一直修改其中的某一条边权值,并输出之前的权值。最后将这条边的权重变为0,并判断这条边是否被删掉
- 测试remove方法:先将几个点加入图中,看删掉v1后是不是返回true,再删一边v1看是不是返回false 删掉v1后分别看与v1相关联的点的source和target是不是都把之前的v1删掉了
- 测试source方法:创建几个点,并将所有的点都与其中的一个点邻接,在输出这个点的source,判断这个sourceMap是否输出合法
- 测试target方法:测试几个点,将所有点都作为这个点的target,最后输出target判断targetMap是否合法
-
-
- Problem 2: Implement Graph <String>
-
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
-
-
-
- Implement ConcreteEdgesGraph
-
-
- 实现Edge类
Edge是concreteEdgegraph的基础,是图中一条确定有向边的对象。这条边包括三条信息:source,target,weight,为保证数据安全,应把三个数据设置成private类型,这样不会被外部类随意调用以及更改。然后应该创建一个构造方法,就是创建一个实例化对象方法。Edge的三个信息都应该设置独立的方法去返回他
- 实现ConcreteEdgesGraph方法,该方法调用接口graph,其中graph一共包含以下几个函数:
函数名 | 作用 | 输入参数 | 返回值 |
add | 向图中加入端点 | Vertex | Boolean,如果原图中没有这个点就加入成功并返回true,否则返回false |
set | 在图中设置有向边 | source 、target、weight | Int, 在图中设置一条从source到target的有向边,如果之前已经设置过这条边则需要修改这条边权值并返回 |
remove | 删掉图中的一个点 | Vertex | Boolean,在途中删掉这个点的所有信息,包括有向边的集合 |
Vertices | 得到图中的所有点 | Null | Set,得到这个图中的所有点集合 |
Source | 得到某个点在所有有向边的源点 | Target | Map,遍历所有有向边,并得到有向边中指向target的所有点以及权值 |
Target | 得到某个点所在所有有向边的终点 | Source | Map。遍历所有有向边,并得到有向边中由source指向的所有点以及边的权值 |
Tosting | 将图中的边打印输出 | Null | String |
-
-
-
- Implement ConcreteVerticesGraph
-
-
- 实现Vertex类
Vertex是 ConcreteVerticesGraph的基础,这个图相当于基于点的实现,是图中一个确定点的对象,Vertex对象有两个信息:vername即该点的名字,Map target记录了这个点所指向的目标点的信息以及这条有向边的权重,由于点的构造式有多种,所以要设置多个构造方法来初始化实例对象。由于Vertex类的信息是private类型,不能被外部调用,所以也要构造多种get方法来返回这个点的信息
- 实现ConcreteEdgesGraph方法,该方法调用接口graph,其中graph一共包含以下几个函数:
函数名 | 作用 | 输入参数 | 返回值 |
add | 向图中加入端点 | Vertex | Boolean,如果原图中没有这个点就加入成功并返回true,否则返回false |
set | 在图中设置有向边 | source 、target、weight | Int, 在图中设置一条从source到target的有向边,如果之前已经设置过这条边则需要修改这条边权值并返回 |
remove | 删掉图中的一个点 | Vertex | Boolean,在途中删掉这个点的所有信息,包括有向边的集合 |
Vertices | 得到图中的所有点 | Null | Set,得到这个图中的所有点集合 |
Source | 得到某个点在所有有向边的源点 | Target | Map,遍历所有有向边,并得到有向边中指向target的所有点以及权值 |
Target | 得到某个点所在所有有向边的终点 | Source | Map。遍历所有有向边,并得到有向边中由source指向的所有点以及边的权值 |
Tosting | 将图中的边打印输出 | Null | String |
通过引入Graph<L>接口,实现对数据类型泛型化,具体来说就是将原本默认的String数据类型转化成泛型L,这样在以后调用的时候可以对多种数据类型进行处理,具体操作就是将所有原本String类型的值改成L即可,他的意思是将string类型抽象化,以后调用的时候就不会仅限于string
-
-
-
- Implement Graph.empty()
-
-
创建一个Graph对象,创建方式选择Vertex或者Edge均可,创建的时候要修改接口empty函数的返回值
- public static <L> Graph<L> empty() {
- return new ConcreteEdgesGraph<>();
- }
读入一个文件,根据文件中dan次地出现频率以及单词前后连接关系实现一个语料库,然后要求写一段话,并将这段话按照语料库的内容将它扩充
-
-
-
- Test GraphPoet
-
-
按照文件读入的方式划分,测试语料库
- 读入空文件
- 只读入一句话,这句话只有一行
- 读入多行话
按照边的权重划分,测试语料库
- 所有单词权值只有1
- 单词权值>=1
测试覆盖度如下:
-
-
-
- Implement GraphPoet
-
-
- GraphPoet的字段为String构成的Graph,构造方式为Graph.empty
- AF(graph) = 读入文本生成的语料库,是图数据类型,根据读入的文本构造图
- Graph.empty构造一个graph数据类型,empty方法返回ConcreteVerticesGraph方法调用,
构建一个邻接表 - final类型,保证数据不被泄露,不能被外部类所修改。防御式拷贝,每次返回前创造一个新的对象保证返回的对象在被引用时不会对原来的值作出修改
GraphPoet | 输入文件的路径,一行一行读入,储存在List中,然后每次取相邻的元素,在图中添加新的边 |
checkRep | 检查不变性,必须保存从语料库文件生成的图 |
poem | 输入需要进行扩充的字符串,声明声明一个StringBuilder保存,每次读取一个词,当前词作为source,下一个词作为target,然后在garph中寻找source的终点表中是否有与target的源点表中相同的元素,并且找到权值最大的和的点加入source和target之间,返回扩充后的字符串 |
toString | 调用ConcreteEdgesGraph中的toString方法,将整个图中所有点的指向转化为一条字符串输出 |
请按照Problem Set 2: Poetic Walks的说明,检查你的程序。
如何通过Git提交当前版本到GitHub上你的Lab2仓库。
在这里给出你的项目的目录结构树状示意图。
调用P1写好的Concrete类,创建一个新图。在这个图中重新实现Lab1的方法:addVertex,addEdge,getDistance以及构造新的对象Person
在图中新增Person对象 | |
addEdge | 在两个Person对象之间建立关系 |
getDistance | 得到两个Person对象在Friendship中的距离,具体实现方法为:采用队列结构:利用广度优先遍历,从第一个点开始入队,出队后将第一个点邻接的所有未标记过的点都加入队列中,直到找到目标点,并输出距离,距离的求取和树的层序优先遍历类似,每计算一轮,两个点距离在其出队的点上加一,由于图的addVertex和addEdge方法都已经提前设置好,并且Concrete方法内数据都是private类型,所以不能修改。在这次的宽度优先算法中,Person内部不再设Set来保存他的friends,直接在getDistance中创建一个Map类型数据,保存每次遍历结果并且修改Person内部的flag变量 |
-
-
- Person类
-
-
-
- 客户端main()
-
按要求先创建person对象,按要求在邻接表中加点,加边并且输出要求的两个人之间路径长度
-
-
- 测试用例
-
分别创立几个点,构造Person对象,将这几个对象加入到图中,主要测试getDistance:两个对象没有联系,两个对象直接联系,两个对象通过其他人简介联系,同一个对象的distance应该输出为0
测试代码覆盖度的时候要把main函数注释掉
给出你的设计和实现思路/过程/结果,覆盖度报告。
-
-
- 提交至Git仓库
-
如何通过Git提交当前版本到GitHub上你的Lab2仓库。
在这里给出你的项目的目录结构树状示意图。
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 | 时间段 | 计划任务 | 实际完成情况 |
2022.4.12 | 8:00-11:00 | 上课,看懂实验报告以及要求,把代码从仓库中下载好以后导入到自己的项目中并尝试理解各个项目结构 | 完成 |
2022.4.20 | 20:00-23:00 | 开始着手编写代码,初次完成P1中的ConcreteEdgesGraph以及 ConcreteVerticesGraph部分 | 完成 |
2022.4.21 | 10:00-23:00 | 完成GraphPoem的编写以及P1部分的test以及调试,完成P2基本算法 | 完成 |
2022.4.26 | 8:00-12:00 | 完成P2以及调试 | 完成 |
2022.5.29 | 16:00-20:00 | 完成最后调试以及报告撰写 | 完成 |
遇到的难点 | 解决途径 |
ConcreteVerticesGraph中target方法总是读入不对 | Target方法再进行比较的时候比较对象错误,应该是将Vertex中的vername比较而不是比较对象本身 |
Set方法中加入的点少一个 | 使用迭代器遍历对象,在iterator删除内容的时候把iterator给remove了,实际上只要remove iterator的getSource即可 |
- 在写代码前要做好充足的工作,一定要充分理解题目要求,不然改的时候很困难
- 牢记规约重要性,不然最后得出的结果与自己预想的不一样
- 一定要显式设置迭代器删除元素,否则会出现
java.util.ConcurrentModificationException 异常
- 面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?
面向ADT编程,对我来说和面向场景编程最大的区别是对场景的抽象程度,面向ADT要求将所有事物抽象化成对象,这样每次编译的时候只需要对对象进行直接操作,而面向场景则是对这个过程进行复盘推理
- 使用泛型和不使用泛型的编程,对你来说有何差异?
编写差异其实不大,但就实用性来说,泛型的作用远大于不使用泛型,只要设计好一个程序,不对输入类型做限制,这样做的好处就是增加了代码的复用性,方便以后使用
- 在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?
设计规约优势是让程序员提前知道程序应有的输入以及输出,不会对设计有歧义,对接的时候更方便,我目前来说勉强适应这种模式,但是还不习惯
- P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?
代码复用减少了出错的概率,大大增加了代码编写的效率,以后要更新这段代码就不用考虑实现泛型的类都需要做出调整
- 为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?
这样做可以及时判断输入输出是否符合规约,要是编写大型项目,则一定要这样做,因为这样会在过程开始的时候免去了很多不必要的错误
- 关于本实验的工作量、难度、deadline。
工作量,难度均适中,Poem较为新颖,花了一段时间,学习进度和实验有冲突导致我第一时间没有开始写实验,deadline时间充裕,对学业压力不是很大。
- 《软件构造》课程进展到目前,你对该课程有何体会和建议?
希望老师多讲一点java的高级知识。。