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
3.1.6 使用Eclemma检查测试的代码覆盖度··· 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 并据此设计测试用例。
我的JDK是在年初的时候安装的,所以版本比较新是JDK15,eclipse也是那个时候安装的,但是实验要求就用了eclipse内置的JDK8
请仔细对照实验手册,针对三个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
根据要求完成接口Graph<L> 的两个具体实现:ConcreteEdgesGraph和ConcreteVerticesGraph。Graph<L> 接口要求实现如下几个方法:
add:添加新节点;
set:添加新边;
remove:移除节点;
vertices:获得所有的节点集合;
sources(target):获得以target为目标节点的边的起始节点;
targes(source):获得以source为起始节点的边的目标节点。
Poet:给定一组单词作为语料库,对于两个相邻的单词a和b通过Graph接口构造有向图。再给定一条由单词组成的句子,如果其中两个相邻单词之间在Graph图中有一个桥接词则将桥接词插入到两单词之间。
-
-
- Get the code and prepare Git repository
-
如何从GitHub获取该任务的代码、在本地创建git仓库、使用git管理本地开发。
从https://github.com/rainywang/Spring2021_HITCS_SC_Lab2/tree/master/P1中下载所需实验包即可。
然后在桌面上使用git clone 创建本地仓库。
将所需代码拖入,git add . 全部添加之后git commit -m “v0.1.0”
最后git push -u origin master即可完成上传。
-
-
- Problem 1: Test Graph <String>
-
根据Graph的spec,利用等价类划分的思想确定测试策略并编写测试用例
Graph.add测试:
点在图中,点不在图中
Graph.set测试:
起点:在图中,不在图中
终点:在图中,不在图中
权重:不等于0,等于0
Graph.remove测试:
点在图中,点不在图中
Graph.vertices测试:
图中没有点,图中有多个点
Graph.sources、Graph.targets方法测试:
给定点不在图中
给定点在图中:图中没有相应的点,图中有相应的点
-
-
- Problem 2: Implement Graph <String>
- Implement ConcreteEdgesGraph
- Edge类的实现
- Implement ConcreteEdgesGraph
- Problem 2: Implement Graph <String>
-
- Field
Edge类中设置边的起点、终点和权重并使用final做了限定:
private final int weight;
private final String source, target;
- 方法
方法 | 方法功能 |
Edge(String source, String target, int weight) | 构造器 |
private void checkRep() | 检查类是否出错 |
public String getSource() | 获得边的起点 |
public String getTarget() | 获得边的终点 |
public int getWeight() | 获得权重 |
public String toString() | Override后创建的转字符串输出方法 |
-
-
-
-
- ConcreteEdgesGraph类的实现
-
-
-
- Field
Vertices集用来保存所有顶点, 列表edges变量用来保存所有的边。
private final Set<String> vertices = new HashSet<>();
private final List<Edge> edges = new ArrayList<>();
- 方法
checkRep():判断所有的点,边都不为空且边数 <= 点数 * (点数 - 1)。
add(String vertex):如果vertex不空且vertices域中不包含vertex,把vertex加入到vertices中,返回true,否则返回false。
set(String source, String target, int weight):先检查是否在edges中有以source为起点,target为终点的边。
如果有,把原来的边的权重记录下来并移除这条边,再判断weight是否为0,如果不为0,新建一条以source为起点,target为终点,weight为权重的边,将其加入到edges中,否则不做任何操作,此时相当于移除这条边,最后返回记录下来的原来的边的权重。而如果没有,那么先判断这两个顶点是否在vertices中,如果某个顶点不在vertices中,那么先把它加入到vertices中,然后再判断weight是否为0,如果不为0,新建一条以source为起点,target为终点,weight为权重的边,将其加入到edges中,否则不做任何操作。然后检查不变量,返回表示原来不存在边的0。
remove(String vertex):先判断vertex是否在vertices中,如果不在直接返回false,如果在其中则从vertices中删除vertex,然后遍历edges中的所有边,把与vertex有关(以其为起点或终点)的所有的边都删除。检查不变量然后返回true。
vertices():使用Collections.unmodifiableSet(vertices)方法把vertices封装成一个不可修改的Set类型返回给外部。
sources(String target):新建一个Map<String, Integer>类型的变量sources用来保存所有以target为终点的边的起点及权重,然后遍历edges,如果某个边的终点是target,那么把这条边的起点和权重加入到sources中,最后返回使用Collections.unmodifiableMap(sources)封装过的不可修改的Map类型的sources。
targets(String source):新建一个Map<String, Integer>类型的变量targets用来保存所有以source为起点的边的终点及权重,然后遍历edges,如果某个边的起点是source,那么把这条边的终点和权重加入到targets中,最后返回使用Collections.unmodifiableMap(targets)封装过的不可修改的Map类型的targets。
toString():如果vertices是空的,返回“”,也就是什么也没有。否则就把所有的顶点都加入到要返回的字符串中。然后再判断edges是不是空的,如果不是就把所有的边都加入到要返回的字符串中。最后把字符串返回。
-
-
-
-
- ConcreteEdgeGraphTest实现
-
-
-
由于ConcreteEdgeGraphTest是由GraphInstanceTest继承而得的,所以只需要测试添加的ConcreteEdgeGraph.toString方法和Edge类中的方法即可。其中ConcreteEdgeGraph.toString方法需要判定空图,图内有点但没有边,图内既有点又有边等情况。而Edge类需要考虑对于一条边是否能够正常获得它的各个属性,以及对应的转字符串的方法。
-
-
-
- Implement ConcreteVerticesGraph
- Vertex类的实现
- Implement ConcreteVerticesGraph
-
-
- Field
vertex变量用来保存顶点, targets用来保存以vertex为起点的所有的边的终点和权重。
private final String vertex;
private final Map<String, Integer> targets;
在一个Vertex变量中,vertex变量不能为空,targets中所有的终点不能为空,所有的权重必须是正整数。
- 方法
方法 | 方法功能 |
public Vertex(String vertex) | 构造方法 |
private void checkRep() | 检查不变量 |
public String getVertex() | 获得顶点的名称vertex |
public Map<String, Integer> getTargets() | 以vertex为起点的边集targets |
public int add(String target, int weight) | 添加以vertex作为起点的边,其终点为target,权重为weight |
public int remove (String target) | 删除到target这个终点的边,即从targets中移除target |
public String toString() | 把边按照期望的形式输出 |
-
-
-
-
- ConcreteVerticesGraph类的实现
-
-
-
- Field
保存所有顶点的一个List变量vertices。声明如下:
private final List<Vertex> vertices = new ArrayList<>();
- 方法
checkRep():判断所有的点都不为空,且不包含重复点。
add(String vertex):如果vertex不空且vertices域中不包含vertex,把vertex加入到vertices中,返回true,否则返回false。
set(String source, String target, int weight):先检查是否在vertices中有source和target这两个点,如果没有target则先把这个点加入到vertices中。
如果没有source这个点,则把source也加入到vertices中,再判断权重是否为0,如果不为0,则把边加入到source的边集中,否则直接返回0。
如果存在source这个点,那么判断weight是否为0,如果不为0, 把边加入到source的边集中,返回原先边的权重;如果为0,则把以target为终点的这条边删除,返回原先边的权重。
remove(String vertex):先遍历vertices中所有的顶点,把以vertex为终点的边都删除,然后在vertices中找到名为vertex的点,将其删除并返回true,如果没有返回,说明不存在名为vertex的点,返回false即可。
vertices():遍历所有的顶点,取出顶点的名字加入到名字集合中,然后将这个集合用Collections.unmodifiableSet封装后返回。
sources(String target):新建一个Map<String, Integer>类型的变量sources用来保存所有以target为终点的边的起点及权重,然后遍历vertices,如果某个顶点vertex.getTargets包含到target的边,那么把这个点和这条边的权重加入到targets中。
最后返回使用Collections.unmodifiableMap(sources)封装过的不可修改的Map类型的sources。
targets(String source):遍历vertices找到source这个点,使用getTargets方法得到终点和对应边的集和,最后返回使用Collections.unmodifiableMap()封装过的不可修改的边集。
如果没找到,先创建new HashMap<L, Integer>(),然后返回它。
toString():遍历vertices中所有的顶点,调用Vextex中的toString方法输出以每个顶点为起点的所有边,把这个字符串加入到要返回的字符串中,最后把字符串返回。
由于ConcreteVerticesGraphTest是由GraphInstanceTest继承而得的,所以只需要测试添加的ConcreteVerticesGraph.toString方法和Vertex类中的方法即可。其中ConcreteVertexGraph.toString方法需要测试空图,图内有点但没有边,图内既有点又有边的情况,而Vertex类的测试包括:
getter方法:测试对于一个点是否能够正常获得它的点的名称和以它为起点的边集
add方法:按加入的边分:原先不存在这条边,原先存在这条边
remove方法:按被删除的边分:原先不存在这条边,原先存在这条边
toString方法:按边分:只有顶点没有以之为起点的边,有以之为起点的边
-
-
- Problem 3: Implement generic Graph<L>
-
只需将所有为String的地方改为L即可
需要注意的是一些数据结构和类也需要限定为L型
-
-
-
- Implement Graph.empty()
-
-
在GraphStaticTest中编写测试是否为空的label测试:
-
-
- Problem 4: Poetic walks
- Test GraphPoet
- Problem 4: Poetic walks
-
构造方法中需要测试空文件,单行输入,多行输入,然后poem方法的测试包括:按桥接点分:空输入,在语料库中没有桥接点的输入,在语料库中有桥接点的输入;按权重分:权重等于1,权重大于1;测试toString方法:空图,非空图;根据测试策略编写测试用例。
-
-
-
- Implement GraphPoet
-
-
Field:private final Graph<String> graph;
-
-
-
-
- 实现构造方法GraphPoet
-
-
-
从文件中按行读入所有的语料然后根据要求以空格分割出所有的单词保存在一个名为words的List列表中,然后从words中依次读出第i个和第i+1个单词,如果存在以第i个单词名为起点,以第i+1个单词名为终点的边,那么就在原来的权重上加1,否则直接创建一条新的权重为1边,将其放入图中。
-
-
-
-
- 实现poem
-
-
-
如果input为空,直接返回一个空字符串。否则,把输入的字符串以空格分割成单词存入List类型的words中,然后遍历words,判断第i个单词和第i+1个单词是否有桥接词的存在,即以第i个单词为起点,在创建好的graph中找到它的所有终点即为所有可能成为桥接词的点,再分别以所有的终点为起点找它们的终点,如果某个点的终点是我们的第i+1个单词,那么它就是我们找到的桥接词,再比较所有找到的桥接词的路径长度:第i个单词到桥接词的权重+桥接词到第i+1个单词的权重,取这个值最大时所经过的桥接词。
然后把找到的桥接词添加到待输出的字符串中,如果没有找到桥接词,则不需要这一步。
最后把得到的字符串返回。
-
-
-
-
- 实现toString
-
-
-
直接调用ConcreteEdgesGraph的toString方法把所创建的图输出。
-
-
- 使用Eclemma检查测试的代码覆盖度
-
覆盖度为90.6%
在桌面上使用git clone 创建本地仓库。
将所需代码拖入,git add . 全部添加之后git commit -m “v0.1.0”
最后git push -u origin master即可完成上传。
-
- Re-implement the Social Network in Lab1
在本节的FriendshipGraph 中,图中的节点仍需为 Person 类型,只需让新 FriendshipGraph 类要利用已经实现的 ConcreteEdgesGraph<L> 或 ConcreteVerticesGraph<L>
然后保证FriendshipGraph 中应提供addVertex()、addEdge()和 getDistance()三个方法即可。
给出你的设计和实现思路/过程/结果。
/**
* Lab1中的main函数
*/
public static void main(String[] args)
这个函数保持lab1中原样即可。
/**
* 求得两个人间的最短距离
* @param person1 第一个人
* @param person2 第二个人
* @return 返回值有以下情况:
* 0 同一个人
* -1 两个人间不存在路
* 正整数 两个人之间的最短距离
*/
public int getDistance(Person person1, Person person2)
最短距离函数首先创建包含人对象的队列和以人为key和路径长度为value的map,
接下来使用广搜算法即可,检查每次队首元素的target集中的person’对象,如果该对象不存在于map中则加入队列,同时加入map中,将其路径长度设为队首元素的路径长度加一,如此循环。直到target集取出的某个person等于person2为止。
返回的情况则如规约区分为三种。
/**
* 找寻一个节点对应的边的终点集
* @param person 想要找到target的人节点
*/
public Map<Person,Integer> targets(Person person)
{
return graph.targets(person);
}
/**
* 两个人间增设长为1的边。
* @param person1 第一个人
* @param person2 第二个人,就是边的target
* @return 返回值为set(person1, person2, 1),也就是graph中的设置边的函数
*/
public void addEdge(Person person1, Person person2) {
graph.set(person1, person2, 1);
}
/**
* 向图中添加新的顶点,新人
* @param person 新人
*/
public boolean addVertex(Person person) {
return graph.add(person);
}
这几个函数设计如规约所说。
* Immutable type representing a person
*/
public class Person{
private final String name;
/* 构造器
* @param name 人的名字
*/
public Person(String name){
this.name = name;
}
这个是人对象的构造器,初始化名字即可
/**
* @return 返回名字
*/
public String getName() {
return name;
}
返回名字的函数。
/**
* 判断两个人是否为同一人
* @param Person 与该对象进行比较的人
*/
@Override
public boolean equals(Object Person) {
if(this.name.equals(((Person)Person).getName()))
return true;
else
return false;
}
将对象转换为人后使用equals函数判断。
-
-
- 客户端main()
-
与lab1中相同
addVertex方法:首先验证已经被添加的点会返回false,未被添加的点返回true,然后验证同名的人也会返回false的情况。
addEdge方法:分别验证单向边与双向边;
getDistance方法:分别验证到达自身,不能到达,单向到达,多条路径到达
-
-
- 提交至Git仓库
-
在桌面上使用git clone 创建本地仓库。
将所需代码拖入,git add . 全部添加之后git commit -m “v0.1.0”
最后git push -u origin master即可完成上传。