2021年春季学期计算学部《软件构造》课程Lab 2实验报告

1实验目标概述

本次实验训练抽象数据类型(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

2实验环境配置

在之前的基础上安装配置 EclEmma,https://www.eclemma.org/有教程,主要过程如下:

  1. From your Eclipse menu select Help → Eclipse Marketplace.
  2. Search for “EclEmma”.
  3. Hit Install for the entry “EclEmma Java Code Coverage”.
  4. Follow the steps in the installation wizard.
    安装完成
    在这里插入图片描述

在这里给出你的GitHub Lab2仓库的URL地址(Lab2-学号)。
https://github.com/ComputerScienceHIT/HIT-Lab2-1190202002

3实验过程

3.1Poetic Walks

一.先构建一个有向加权图的操作,包括创建图,以及对图中顶点和边的增,删,改,查。
二.复用之前完成的图操作,将一段文本的各个词作为点,各词之间的联系作为边的权。即:此b出现在a后几次,则a到b的边权为几。
三.根据输入语段和完成的文本图,查找文本图中同时出现在输入语段两词a b,a后b前的词。将此词加入a b之间,以此类推,完成对输入语段的扩句。

3.1.1Get the code and prepare Git repository

如何从GitHub获取该任务的代码、在本地创建git仓库、使用git管理本地开发。在 Get the code 步骤中,无法连接 MIT 的 Athena 服务器,将从以下地
址获取初始代码https://github.com/rainywang/Spring2021_HITCS_SC_Lab2/tree/master/P1

git clone https://github.com/rainywang/Spring2021_HITCS_SC_Lab2/tree/master/P1

git init 创建本地空仓库
git add README.md //文件添加到仓库
git add . //不但可以跟单一文件,还可以跟通配符,更可以跟目录。一个点就把当前目录下所有未追踪的文件全部add了
git commit -m “first commit” //把文件提交到仓库
git remote add origin git@github.com:wangjiax9/practice.git //关联远程仓库
git push -u origin master //把本地库的所有内容推送到远程库上

在作业描述中若遇到“commit and push”的要求,请将你的代码 push 到你
的 GitHub Lab2 仓库中。g

3.1.2Problem 1: Test Graph

设计:向GraphStaticTest.java和GraphInstanceTest添加测试。包括对add,set,remove,vertices,sources,targets的测试。
实现:静态 Graph.empty() 方法的测试策略是静态的,因此只有一种实现,我们只需要运行一次这些测试。MIT提供了这些测试。需要为 GraphInstanceTest.java 中的所有实例方法编写测试策略和测试。在这些测试中,必须使用 emptyInstance() 方法来获取新的空图,而不是 Graph.empty()。
过程:testAdd:添加点,测试图中是否包含这些点。
testSet:设置两点之间权值,测试当weight为正数,负数,0时,所得的之前weight的结果是否符合要求。
testRemove:删除点和相连的边,根据删除返回true,没有此点返回false,进行测试返回值与实际是否相同。并在删除后添加此点和相连的边,根据返回值判断是否真的删除。
testVertices:测试生成的点集是否将所有点包含在内。
testSources:测试根据source生成的Hashmap<targets,weights>与我们的判断应该生成的Map是否相同。
testTargets:测试根据target生成的Hashmap<sources,weights>与我们的判断应该生成的Map是否相同。
结果:全部完成后的测试结果,对graph代码覆盖率达到90.5%。
在这里插入图片描述
在这里插入图片描述

3.1.3Problem 2: Implement Graph

设计:实现分为两种,Implement ConcreteEdgesGraph和Implement ConcreteVerticesGraph。无论哪种,最终都需要实现图的add,set,remove,vertices,sources,targets函数。不同的是,对于两种实现,所用到的类和类中的操作不同,第一个用Edge实现,第二个用Vertices实现。导致图的函数实现略有不同。

3.1.3.1Implement ConcreteEdgesGraph

实现:由于是图的边实现,所以需要定义Edge类,在类中有边的出发点,终点,权值。类中应包含返回出发点,终点,权值的函数。然后用由所有Edge组成的列表和包含所有点的元组完成图的实现的各类函数add,set,remove,vertices,sources,targets。
过程:先完成Edge类的实现,边需要在类中定义私有变量出发点source,终点target,权值weight,根据这三个变量的输入构成Edge。
在Edge类中实现返回出发点source,终点target,权值weight的函数,直接返回对应的变量即可。完成toString函数,将边以字符串形式显示出来,直接将出发点source,终点target,权值weight,以source——>weight——>target形式返回。
完成ConcreteEdgesGraph。首先创建一个要放所有点的元组vertices和一个存放Edge的列表edges。
public boolean add(String vertex):如果vertices中不包含添加点,则将点添加到vertices中,返回true。否则返回false。
public int set(L source, L target, int weight):如果weight<0,直接返回,weight不能为负。否则进行边的增,删,改操作。删改都需要将原本的边删除,所以可以先搜索有无相同source和target的边,若有将weight记录下来,将边从列表中删除,此时删除操作已完成。而增和改此时都是加边,由于weight=0是删除操作,当weight>0直接将边加入列表中并用add操作将点加入即可。返回之前记录的weight值,若没有则之前没有此边,返回的即为0。
public boolean remove(L vertex):如果有此点则将它在vertices中删除,并将edges中所有包含此点的边删除,返回true。否则返回false。
public Set vertices():返回vertices的元组即可
public Map<L, Integer> sources(L target):根据参数target,找到edges中所有包含target的边,将边的source和weight加到map中,返回这个map。
public Map<L, Integer> targets(L source):根据参数source,找到edges中所有包含source的边,将边的target和weight加到map中,返回这个map。
public String toString():遍历所有边,将所有边的toString生成的字符串连在一起,输出。
结果:对ConcreteEdgesGraph进行测试,全部通过,覆盖率达92.4%
在这里插入图片描述在这里插入图片描述

3.1.3.2Implement ConcreteVerticesGraph

实现:由于是图的点实现,所以需要定义Vertex类,在类中有一个点以及从这个点出发连向的所有点和权值组成的map。类中应包含返回出发点,终点和权值map的函数。然后用由所有Vertex组成的列表完成图的实现的各类函数add,set,remove,vertices,sources,targets。
过程:先完成Vertex类的实现,点在类中定义为私有变量出发点source,target和weight组成map,根据这两个变量构成Vertex。
在Vertex类中实现返回出发点source,终点target和权值weight组成的map函数,直接返回对应的变量即可。完成toString函数,以字符串形式显示出来,直接将出发点source,map中的终点targets,权值weights,以source——>weight——>target形式返回。
完成 ConcreteVerticesGraph。首先创建一个要放所有点Vertex的列表vertices。
public boolean add(String vertex):如果vertices中的每个Vertex中的source都不是添加点,则构建一个Vertex,将vertex作为source,将Vertex添加到vertices中,返回true。否则返回false。
public int set(L source, L target, int weight):如果weight<0,直接返回,weight不能为负。否则进行边的增,删,改操作。删改都需要将原本的边删除,所以可以先搜索有无相同source的Vertex,Vertex中的map中有无target,若有将weight记录下来,在map中将key=target删除,此时删除操作已完成。而增和改此时都是加边,由于weight=0是删除操作,当weight>0用add操作将点加入vertices,搜索对应的source,在map中加入key=target,value=weight。返回之前记录的weight值,若没有则之前没有此边,返回的即为0。
public boolean remove(L vertex):搜索此点,如果此点为Vertex中source,则删掉此Vertex;若此点为map中的key,则将map中此key删除。返回true。否则返回false。
public Set vertices():将vertices中所有Vertex的source放入一个元组中,返回。
public Map<L, Integer> sources(L target):根据参数target,搜索vertices找到Vertex中对应的source和map中的value将source和value加到map中,返回这个map。
public Map<L, Integer> targets(L source):根据参数source,搜索vertices找到相同source的Vertex,返回targetmap即可。
public String toString():遍历所有Vertex,将所有的toString生成的字符串连在一起,输出。
结果:运行测试通过,覆盖率88.8%。
在这里插入图片描述
在这里插入图片描述

3.1.4Problem 3: Implement generic Graph

设计:进行修改扩充,使得点的类型可以为任意类型。由于基本实现已经完成,所以此处实现比较简单。

3.1.4.1Make the implementations generic

实现:按给出的例子,进行修改,将点类型String改为L。
在这里插入图片描述

过程:Edge 或 List 等类型的变量需要成为 Edge 和 List<Edge>。
类似地像 new ConcreteEdgesGraph() 或 new Edge() 这样的构造函数将需要变为 new ConcreteEdgesGraph() 和 new Edge()

结果:测试仍可通过,graph的覆盖率达到90.5%
在这里插入图片描述

3.1.4.2Implement Graph.empty()

实现:在Graph.empty中直接返回一个新的ConcreteEdgesGraph。
在这里插入图片描述

增加GraphStaticTest.java中对其他标签类型的测试,较为简单,不做过多描述
在这里插入图片描述

结果:测试仍可通过,graph的覆盖率达到90.5%
在这里插入图片描述

3.1.5Problem 4: Poetic walks

设计:复用之前完成的图操作,将一段文本的各个词作为点,各词之间的联系作为边的权。即:此b出现在a后几次,则a到b的边权为几。根据输入语段和完成的文本图,查找文本图中同时出现在输入语段两词a b,a后b前的词。将此词加入a b之间,以此类推,完成对输入语段的扩句

3.1.5.1Test GraphPoet

实现:测试应使用一个或多个示例语料库文件。将语料文件1.txt放在 test/P1/poet中。
过程:内容为From time to time,sadness fills my throat and pain.测试当输入语句为From time to sadness时,生成的语句是否为From time to time sadness。

3.1.5.2Implement GraphPoet

实现:定义一个之前实现的空图。将文本语料进行空格和符号的分词,分成一个个单词。从前到后读取两个相连的单词,通过pre_weight=graph.set(words[i].toLowerCase(), words[i+1].toLowerCase(), 0);会将图中已经存在的权值返回记录到pre_weight中,并将已经存在的删除。再通过graph.set(words[i].toLowerCase(), words[i+1].toLowerCase(), pre_weight+1);将新的权值记录下来。而若不存在,因pre_weight初始值赋为0,再加1刚好对应当前权值。
过程:
在这里插入图片描述

结果:生成了一个关于语料文件的可以反映词之间关系的图。

3.1.5.3Graph poetry slam

实现:将输入语句分为一个个单词,从前到后读取连续的两个单词,前面的作为source,后面的作为target,source通过graph.targets获取目标点map,target通过graph.sources获取出发点map。寻找两个map中的相同key的词,如果有将此key在两个map中的value的和相加,将结果最大的那个key加入source,target之间,扩充。如果没有,则不扩充,继续向后找。
过程:
在这里插入图片描述

结果:生成结果正确,测试通过
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.1.6Before you’re done

已进行检查,均已完成。
请按照http://web.mit.edu/6.031/www/sp17/psets/ps2/#before_youre_done的说明,检查你的程序。
如何通过Git提交当前版本到GitHub上你的Lab2仓库。
git commit -a -m “no1”
git remote add origin git@github.com/ComputerScienceHIT/HIT-Lab2-1190202002.git
git pull --rebase origin master
git push -u origin master

在这里给出你的项目的目录结构树状示意图。
在这里插入图片描述

3.2Re-implement the Social Network in Lab1

基于 3.1 节 Poetic Walks 中定义的 Graph及其两种 实现,重新实现 Lab1 中 3.3 节的 FriendshipGraph 类。
注 1:可以忽略你在 Lab1 中实现的代码,无需其基础上实现本次作业;
注 2:在本节 FriendshipGraph 中,图中的节点仍需为 Person 类型。故你的新 FriendshipGraph 类要利用 3.1 节已经实现的 ConcreteEdgesGraph 或 ConcreteVerticesGraph,L 替换为 Person。根据 Lab1 的要求, FriendshipGraph 中应提供 addVertex()、addEdge()和 getDistance()三个方法 : 针对 addVertex() 和 addEdge() ,你需要尽可能复用 ConcreteEdgesGraph或 ConcreteVerticesGraph中已经实现的 add() 和 set()方法,而不是从 0 开始写代码实现或者把你的 Lab1 相关代码直接复制过来;针对 getDistance()方法,请基于你所选定的 ConcreteEdgesGraph 或 ConcreteVerticesGraph的 rep 来实现,而不能修改其 rep。
注 3:不变动 Lab1 的 3.3 节给出的客户端代码(例如 main()中的代码),即
同样的客户端代码仍可运行。重新执行你在 Lab1 里所写的 JUnit 测试用例,测试你在本实验里新实现的 FriendshipGraph 类仍然表现正常。

3.2.1FriendshipGraph类

思路:FriendshipGraph类有三个功能:添加成员addVertex,增加有向边addEdge,计算两个人相连的最短距离getDistance(自己和自己距离为0,无法相连输出-1),以及返回生成图verticesGraph。
过程:addVertex:调用graph.add()直接添加。
addEdge:调用graph.set()进行设置边。
getDistance:采用先广搜索,构建一个队列,并构建一个HashMap<Person, Integer> map。将出发点的人添加到队列,添加到map的Key处,0加到Value处,读取队首,poll队首,读取他的每个朋友,若其朋友不在map中,则将其朋友全部添加到队列,并map.put(朋友,队首人的value+1),如果队列不空则一直循环,在循环中如果找到了终止点的人,则返回这个人在map中对应value值,即为距离。否则返回-1。
示意如下图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

结果:
添加成员addVertex。
增加有向边addEdge。
getDistance输出两个人相连的最短距离,自己和自己距离为0,无法相连输出-1。

3.2.2Person类

思路:建立Person类,由于各功能都能用图来实现,所以此时无需定义任何操作,只建立Person类即可。

3.2.3客户端main()

实验要求中已经给出,不做过多赘述。就进行了创建人员,添加人员,建立朋友关系,输出距离。
在这里插入图片描述

3.2.4测试用例

思路:测试用例可仿照3.3.3中给出的进行设计。创建人员,添加人员,建立朋友关系,输出距离。并进行断言。
过程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.2.5提交至Git仓库

如何通过Git提交当前版本到GitHub上你的Lab3仓库。
git commit -a -m “no2”
git remote add origin git@github.com/ComputerScienceHIT/HIT-Lab2-1190202002.git
git pull --rebase origin master
git push -u origin master

在这里给出你的项目的目录结构树状示意图。
在这里插入图片描述

4实验进度记录

日期 时间段 计划任务 实际完成情况
2021.6.6 20:00-23:00 将P1中graph完成 只完成了graph中的边实现
2021.6.10 12:00-22:00 将代码全部写完 完成
2021.6.11 14:00-20:00 将实验报告完成 完成

5实验过程中遇到的困难与解决途径

遇到的难点 解决途径
在点实现的过程中,对点类的构造错误,导致后续无法完成对应的图的实现 在点类中添加对应的相连的点与权值构成的map
在循环中如果将循环到的删除,会导致循环错误,无法继续进行

改用迭代器iterator进行遍历删除操作。

6实验过程中收获的经验、教训、感想

6.1实验过程中收获的经验和教训

要充分利用好已经完成的程序,做好复用能大大提升工作效率,并减少代码量。设计好实验测试,用Coverage进行测试,查看未覆盖代码的问题。

6.2针对以下方面的感受

(1)面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?
面向ADT编程,能很好地进行复用,更安全、结构性更好。面向场景编程,更加直观。
(2)使用泛型和不使用泛型的编程,对你来说有何差异?
泛型更加灵活,适用的情况更多。
(3)在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?
随时进行测试,保证正确性。感觉很好,但不太适应。
(4)P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?
减少工作量和代码量,而且复用能保证正确性和安全性。
(5)为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?
保证ADT的正确和安全,减少出现问题的可能,愿意。
(6)关于本实验的工作量、难度、deadline。
工作量很大,难度对于初学者很高,不过给出的时间也很长,还是能够完成。
(7)《软件构造》课程进展到目前,你对该课程有何体会和建议?
对于面向对象有了更深刻的认识,感觉这门课主要学习的是Java的思想。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值