3.1.1 Get the code and prepare Git repository
3.1.2 Problem 1: Test Graph <String>
3.1.3 Problem 2: Implement Graph <String>
3.1.3.1 Implement ConcreteEdgesGraph
3.1.3.2 Implement ConcreteVerticesGraph
3.1.4 Problem 3: Implement generic Graph<L>
3.1.4.1 Make the implementations generic
3.1.4.2 Implement Graph.empty()
本次实验训练抽象数据类型(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并据此设计测试用例。
安装了EclEmma。
https://github.com/ComputerScienceHIT/HIT-Lab2-wangzy-dbug(Lab2-1190200401)。
请仔细对照实验手册,针对三个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
分别建立两个类ConcreteEdgesGraph,ConcreteVerticesGraph 实现Graph接口。Graph接口要求实现add(添加新节点),set(添加新边),remove(移除节点),vertices(获得所有的节点集合),sources(target)获得以target为目标节点的边的起始节点,targes(source)获得以source为起始节点的边的目标节点。并且将ADT泛型化。而且编写测试文件的时候,需要从实现的基本功能出发,有良好的测试覆盖率,是基于测试的编程。
Poet:给定一组字符串(文件输入),对于两个相邻的单词a和b,认为存在一条由a到b的有向边,权值为这条边出现的次数。通过Graph接口构造有向图。再给定一由字符串组成的句子,如果句子中两个相邻单词之间在Graph图中有一个中间单词则将中间单词插入到两单词之间(如果有多个则插入权重最大的那个)即由input和图中的映射关系,得出最后的poem。
从此处获得代码。
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
修改Graph.java中empty函数为:
随后编写测试函数。
// Testing strategy for add
// 测试先添加两个点,然后检查这两个点是否被添加成功。然后再添加上两个其中之一的相同点,检查返回值是否为false。
// Testing strategy for set
// 测试首先几条添加新边,查看返回值是否为0,随后更新其中某一条边,看返回值是否为旧边的值,最后再添加权值为负的边,看返回值是否为-1。
// Testing strategy for remove
// 测试分别对已有点和不存在的点删除并改变已存在的边的权值和删除,检查返回值、其前驱节点的后继节点、点集
// Testing strategy for Vertices
// 测试返回的点集的size和是否包含已添加的点
// Testing strategy for Sources
// 测试一个单独点的sources
// 有source的点的返回的map的键值对是否与设置的一致
// Testing strategy for Targets
// 测试一个单独点的targets
// 有target的点的返回的map的键值对是否与设置的一致
本内容分为两部分,Edge类和ConcreteEdgesGraph类。
- Edge类
// TODO fields
private L source, target;
private int weight;
// Abstraction function:
// AF(name[0]) = 边的起点
// AF(name[1]) = 边的终点
// AF(weight) = 边的权值
// Representation invariant:
// weight >=0 source和target不为空
// Safety from rep exposure:
// 将name[],weight设置为private
首先是构造函数,也是边类的初始化。
之后是一些get private值,或者set private值的基础函数。
- ConcreteEdgesGraph类
private final Set<L> vertices = new HashSet<>();
private final List<Edge<L>> edges = new ArrayList<>();
// Abstraction function:
// AF(vertices)=图中的点
// AF(edges)=图中的边
// Representation invariant:
// edges长度是大于0的实数,有起始的节点
// vertex点必须包含在vertices中
// 两个点之间最多只有一条边
// Safety from rep exposure:
// 将vertices和edges设置为private
// 由于vertices和edges是mutable,所以在返回他们时进行了defensive copies
1. Add(L vertex):首先检查vertices(Set<L>)中是否包含次vertex,包含则返回false,否则vertices添加此vertex。
2. set(L source, L target, int weight):首先对weight判断。若小于0,则直接返回-1。否则遍历edges(List)检查这边是否存在,若存在则更新,并返回旧值,若不存在,则增添此边,顺便添加source与target至vertex。若weight为0,则寻找这条边并删除。
3. remove(L vertex):检查这条边是否存在,存在则删除,返回true,否则返回false。
4. Set<L> vertices():进行防御式拷贝vertices,随后返回。
5. Map<L, Integer> sources(L target):返回target的所有来源点,与所属边的权值。
6. Map<L, Integer> targets(L source):返回source的所有目标点,与所属边的权值。
-
-
-
- Implement ConcreteVerticesGraph
-
-
本内容分为两部分,Vertex类和ConcreteVerticesGraph类。
- Vertex类
// TODO fields
private L name;
private Map<L, Integer> map;// 所有这个点能到达的点的哈希表。
private Map<L, Integer> allsource;// 所有能到达这个点的点的哈希表。
// Abstraction function:
// AF(name)=点的名字
// AF(allsource)=指向这个点的所有点和边
// AF(map)=这个点指向的所有点和边
// Representation invariant:
// 每个边的权值应该大于0
// Safety from rep exposure:
// 将name,allsource,map设置为private
构造函数:
一系列返回数据的函数
以及重要的setSource函数和setTarget函数,用于配合ConcreteVerticesGraph类中的set函数。若i大于0,则把name加入哈希图allsource,并判断是否加入成功。若i为0,那么从allsource中调用removeSource函数移除点temp。i小于0,直接返回-1并报错。setTarget函数与其相似,不赘述。
最后是removeSource(L newsource) 和removetarget(L newtarget),主要作用是得到哈希表的键值。
- ConcreteVerticesGraph类
private final List<Vertex<L>> vertices = new ArrayList<>();
// Abstraction function:
// AF(vertices)={图中所有的vertices[i]|0<=i<vertices.sizes()}
// Representation invariant:
// vertices中不能有重复点
// Safety from rep exposure:
// 设置vertices为private
// 由于vertices是mutable,所以在返回他们时进行了defensive copies
函数 | 作用与内容 |
add(L vertex) | 添加顶点vertex,并且遍历集合vertices,存在则返回false,否则返回true。 |
set(L source, L target, int weight) | 添加or更新or删除边。遍历集合vertices,一旦与source或者target匹配,则调用Vertex类的setTarget函数或者setSource函数。 |
remove(L vertex) | 遍历集合vertices,把vertex本身从集合vertices中删除,并且也从所有点的source或者target中删除。 |
Set<L> vertices() | 先进行防御式拷贝,再返回全新的Set<L>。 |
Map<L, Integer> sources(L target) | 返回target的所有来源点以及边的权值。 |
Map<L, Integer> targets(L source) | 返回source的所有目的点以及边的权值。 |
-
-
- Problem 3: Implement generic Graph<L>
- Make the implementations generic
- Problem 3: Implement generic Graph<L>
-
1.将具体类的声明更改为:
public class ConcreteEdgesGraph implements Graph { … }
class Edge { … }
public class ConcreteVerticesGraph implements Graph { … }
class Vertex { … }
2..使用占位符L代替,更新两个实现以支持任何类型的顶点标签String。这种重构将Concrete¬EdgesGraph,Concrete¬VerticesGraph,Edge,和Vertex适应泛型类型。
-
-
-
- Implement Graph.empty()
-
-
采用ConcreteEdgesGraph来实例化Graph。
-
-
-
- Implement GraphPoet
-
-
private final Graph<String> graph = Graph.empty();
// Abstraction function:
// AF(graph)=文章中所有字符串构成的图
// Representation invariant:
// 必须保存从语料库文件生成的图
// Safety from rep exposure:
// 设置graph为private
- GraphPoet(File corpus) throws IOException
这一函数创建一个哈希表和一个集合。主要分为两步分,文件的读取,以及graph的生成。
1.1文件读取
用一行一行读取的思想,即BufferReader。把每一行的内容读取进数组后,在用Array.asList函数转化为list,再用addall。一个一个的读取list的元素并加入mylist。若文件读入流创建失败,则抛出异常。
1.2graph的生成
将mylist中的元素两两组合,比较。首先全部小写化,这是为了迎合要求。随后把两个字符串拼接为一个,并且存入哈希表中,这是为了,如果之后再次检测到这个组合,就会把这个组合的键值+1,也就是权值+1,最后调用set函数,更新graph中的边,点信息。自此,graph完成。
- String poem(String input)
首先做一些准备工作。先建立一个StringBuilder build,用来添加字符串。然后把输入的字符串按空格分开。最后再把graph的vertices中的点全部依次放入name字符串数组之中。
接着在一个循环中,每次把第一个元素存入build,也就是build.append(Input[i])。随后把inpupr[i]与input[i+1]设置为source和target,并且遍历name数组,一旦找到某个点的sources集合和targets集合都分别恰好包含source与target,那么这个点就被作为待选点。接下来计算权值,并采用选择比较法找到权值最大的点,一旦发现比sum大,记录此点的下标与内容,并且加入Build。整个循环结束后,再把最后一个未考虑的点加入build。
ConcreteEdgesGraph覆盖度为:87.9%,99.1%,100%
GraphPoetTest覆盖度为:96.5%和97.5%。
请按照http://web.mit.edu/6.031/www/sp17/psets/ps2/#before_youre_done的说明,检查你的程序。
如何通过Git提交当前版本到GitHub上你的Lab2仓库。
在这里给出你的项目的目录结构树状示意图。
这个实验是基于在Poetic Walks中定义的Graph及其两种实现,重新实现Lab1中的 FriendshipGraph类。我们需要复用ConcreteEdgesGraph或 ConcreteVerticesGraph中已经实现的add()和set()方法,而不是从零开始。另外基于所选定的 ConcreteEdgesGraph 或 ConcreteVerticesGraph的rep来实现,而不能修改父类的rep。
private final Graph<Person> graph;
// Abstraction function:
// AF(graph)=实现的图
// Representation invariant
// 图中的Person不重复
// Safety from rep exposure
// 将graph设置为private
// 由于graph是inmutable,所以在返回时不需要进行defensive copies
给出你的设计和实现思路/过程/结果。
函数名称与变量 | 作用与内容 |
FriendshipGraph() | 对graph进行初始化,创建一个空图。 |
addVertex(Person npeople) | 调用graph的add函数,并通过返回值判断是否添加成功。 |
addEdge(Person name1, Person name2) | 调用graph的set函数,graph.set(name1, name2, 1)。并且把调用Person类的自创的social函数,name1.social(name2)。 |
getDistance(Person people1, Person people2) | 首先创建一个队列queue和一个哈希表map。然后先判断people1和peolple2是否相等,一旦相等,那么直接返回0。否则将people1入队,再把键值设为0,加入哈希表。设置一个while循环,循环终止条件是队列为空。进入循环后,首先把队首第一个元素出队,随后调用graph.targets(Person)函数,和把哈希表变为集合Set的keySet函数,得到队首元素的targets Person的集合,再把这个集合转为列表。随后我们遍历这个列表,一旦发现首先创建的哈希表map不包含这个Person,那么就入队,并且放入哈希表,键值为此时刚刚出队的队首Person的键值+1,然后再判断入队的这个Person和目标People2是否相同,相同则返回键值,否则继续循环。最后如果没有找到可行路径,则返回-1。 |
Set<Person> vertices() | 用于返回graph的vertices。主要作用在于方便后续的测试。 |
private String name;
private static ArrayList list = new ArrayList<String>();// 这个人的朋友的名字。
private List<Person> myfriend = new ArrayList<Person>();// 这个人的朋友
// Abstraction function:
// AF(name)=人的名字
// Representation invariant:
// 没有重复名字
// Safety from rep exposure:
// 将name设置为private
// 由于name是inmutable,所以在返回时不需要进行defensive copies
给出你的设计和实现思路/过程/结果。
构造方法,用啥判断是否重复,不重复则添加。
Social(Person one)用于添加朋友。getHisFriend()用于返回本人的朋友(还做了防御式拷贝)。
addVertextest:添加四个点,并且检查是否添加成功
addEdgetest:创建四个点,并且添加边,查看结果是否与预期相同。
添加点与边,最后检查距离是否符合预期即可。