实验目标概述
本次实验训练抽象数据类型(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 并据此设计测试用例
实验过程
任务一 Poetic Walks
要求
请阅读 http://web.mit.edu/6.031/www/sp17/psets/ps2/,遵循该页面内的要求完
成编程任务。
⚫ 在 Get the code 步骤中,你无法连接 MIT 的 Athena 服务器,请从以下地
址获取初始代码:
https://github.com/rainywang/Spring2022_HITCS_SC_Lab2/tree/master/P1
⚫ 在作业描述中若遇到“commit and push”的要求, 请将你的代码 push 到你
的 GitHub Lab2 仓库中。
⚫ MIT 作业页面提及的文件路径,请按照下表的目录结构进行调整。例如
“test/poet”应为“test/P1/poet”,“src/poet”应为“src/P1/poet”。
⚫ 其他步骤请遵循 MIT 作业页面的要求。
实现
第一部分 Test Graph
- 要求
设计、记录和实施Graph.
现在,我们将只测试(然后实现)带有String顶点标签的图。稍后,我们将扩展到其他类型的标签。
为了适应在接口的多个实现Graph上运行我们的测试,这里是设置:
静态 Graph.empty()方法的测试策略和测试在GraphStaticTest.java. 由于该方法是静态的,因此只有一个实现,我们只需要运行这些测试一次。我们提供了这些测试。您可以自由更改或添加它们,但对于此问题,可以保持原样。
为所有实例方法编写测试策略和测试GraphInstanceTest.java。在这些测试中,您必须使用该emptyInstance()方法来获取新鲜的空图,而不是Graph.empty()! 请参阅提供testInitialVerticesEmpty()的示例。
GraphInstanceTest是一个抽象类。抽象类和子类化有其用途,但通常应避免使用。
与GraphStaticTest我们编写的任何其他 JUnit 测试类的工作方式不同,它GraphInstanceTest是不同的,因为您不能直接运行它。它有一个空白等待填充:emptyInstance()将提供空Graph对象的方法。在下一个问题中,我们将看到它的两个子类通过GraphInstanceTest返回不同类型的空图来填补空白。测试GraphInstanceTest必须是Graph规范的合法客户。任何特定于您的实现的测试都将放在下一个问题的子类中。 - 实现
@Override
public Graph<String> emptyInstance() {
return new ConcreteEdgesGraph<String>();
}
@Override
public Graph<String> emptyInstance() {
return new ConcreteVerticesGraph<String>();
}
第二部分 Implement Graph and Implement generic Graph
- Graph要求
需要实现两次
对于所有类的要求
1,Document the abstraction function and representation invariant.
2,Along with the rep invariant, document how the type prevents rep exposure.
3,Implement checkRep to check the rep invariant.
4,Implement toString with a useful human-readable representation of the abstract value.
以后需要重写equals和hashcode,但这个问题不做要求
所有类要有清晰的规约,除非使用@Override
在实现接口中已有规约的方法时,除非要写更强的规约,否则不需要再写
两个实现中不能存在代码依赖 - 实现泛型
a,将具体类的声明更改为:
public class ConcreteEdgesGraph implements Graph { … }
class Edge { … }
和:
public class ConcreteVerticesGraph implements Graph { … }
class Vertex { … }
更新您的两个实现以支持任何类型的顶点标签,使用占位符L而不是String. 粗略地说,您应该能够用!查找和替换String代码中的所有实例。L此重构将生成ConcreteEdgesGraph、ConcreteVerticesGraph、Edge和Vertex泛型类型。
以前,您可能已经声明了类型为Edgeor的变量List。那些将需要成为Edgeand List<Edge>。
类似地,您可能已经调用了类似new ConcreteEdgesGraph()or的构造函数new Edge()。这些将需要成为,例如,new ConcreteEdgesGraph()和new Edge()。根据上下文,您可以使用菱形符号 <>来避免两次写入类型参数。
完成转换后,所有实例方法测试都应该通过。
Implement ConcreteEdgesGraph and Implement generic Graph
- 要求
必须使用
private final Set<String> vertices = new HashSet<>();
private final List<Edge> edges = new ArrayList<>();
不能向不变量增加参数或者不使用其中的参数
对于Edge类,需要定义规约与表示,Edge类不可变
通常,实现不变类型意味着要重写equals和hashcode,在这个问题集中不要求;
不实现eauqls意味着不能使用equals去比较对象,规范并实现自己需要的操作
需要进行test
确保@Override保证重写toString()
应该加强规范ConcreteEdgesGraph.toString()并在ConcreteEdgesGraphTest.java. 不要将这些测试添加到GraphInstanceTest,它必须测试Graph规范
所有 Java 类都继承Object.toString()了其非常不确定的规范。 Graph没有指定更强大的规范,但您的具体类可以。选择不加强接口中的规范toString是Graph经过深思熟虑的:因为toString它是供人类使用的,所以保持其规范弱有助于防止其他代码依赖于其特定格式
完成后运行测试并提交
- 实现
public class ConcreteEdgesGraph<L> implements Graph<L> {
private final Set<L> vertices = new HashSet<>();
private final List<Edge<L>> edges = new ArrayList<>();
// Abstraction function:
//use a universe L to represent vertices; use edge class to represent edges
// Representation invariant:
// the vertex must be not null; the weight must be positive
// Safety from rep exposure:
// make the rep be private and final ,don't provide public function to modify
// the value of these fields
// never return mutable rep, only return the clone data
ConcreteEdgesGraph() {
}
// checkRep
private void checkRep() {
for (L vertex : vertices)
assert (vertex != null);
for (Edge<L> edge : edges)
assert (edge != null);
}
@Override
public boolean add(L vertex) {
if (vertices.contains(vertex))
return false;
vertices.add(vertex);
return true;
}
@Override
public int set(L source, L target, int weight) {
if (weight < 0)
throw new RuntimeException("Weight must be positive");
if (!vertices.contains(source) || !vertices.contains(target)) {
if (!vertices.contains(source))
this.add(source);
if (!vertices.contains(target))
this.add(target);
}
if (source.equals(target)) // source is the same with target, REFUSE to set the Edge.
return 0;
// Find the same edge
Iterator<Edge<L>> it = edges.iterator();
while (it.hasNext()) {
Edge<L> edge = it.next();
if (edge.sameEdge(source, target)) {
int lastEdgeWeight = edge.weight();
it.remove();
if (weight > 0) {
Edge<L> newEdge = new Edge<>(source, target, weight);
edges.add(newEdge);
}
checkRep();
return lastEdgeWeight;
}
}
// weight=0 means delete an edge, so it can't be before FINDING
if (weight == 0)
return 0;
// new positive edge
Edge<L> newEdge = new Edge<>(source, target, weight);
edges.add(newEdge);
checkRep();
return 0;
}
@Override
public boolean remove(L vertex) {
if (!vertices.contains(vertex))
return false;
edges.removeIf(edge -> edge.source().equals(vertex) || edge.target().equals(vertex));
vertices.remove(vertex);
checkRep();
return true;
}
@Override
public Set<L> vertices() {
return new HashSet<>(vertices);
}
@Override
public Map<L, Integer> sources(L target) {
Map<L, Integer> sources = new HashMap<>();
for (Edge<L> edge : edges) {
if (target.equals(edge.target())) {
sources.put(edge.source(), edge.weight());
}
}
checkRep();
return sources;
}
@Override
public Map<L, Integer> targets(L source) {
Map<L, Integer> targets = new HashMap<>();
for (Edge<L> edge : edges) {
if (source.equals(edge.source())) {
targets.put(edge.target(), edge.weight());
}
}
checkRep();
return targets;
}
// TODO toString()
@Override
public String toString() {
return "the number of vertices is " + vertices.size() + " ,the number of edges is " + edges.size();
}
}
/**
* The edge class must be immutable
* This class is internal to the rep of ConcreteEdgesGraph.
*
* <p>
* PS2's instructions: the specification and implementation of this class is up to you.
*/
final class Edge<L>{
// fields
private final L source, target;
private final int weight;
// Abstraction function:
// source: the start vertex of the edge
//target: the end vertex of the edge
//weight: the value of the edge
// Representation invariant:
//the weight must be positive the source and target must be different
// Safety from rep exposure:
//make source, target and weight unchangeable
Edge(L source, L target, int weight) {
this.source = source;
this.target = target;
this.weight = weight;
checkRep();
}
// checkRep
private void checkRep() {
assert (weight > 0 && !source.equals(target));
}
// methods
public L source() {
return this.source;
}
public L target() {
return this.target;
}
public int weight() {
return this.weight;
}
public boolean sameEdge(L source, L target) {
return source.equals(this.source) && target.equals(this.target);
}
// toString()
@Override public int hashCode() {
return 31*(31*(31+source.hashCode())+target.hashCode())+weight;
}
@Override
public String toString() {
return "Source: " + this.source + "; Target: " + this.target + "; Weight: " + this.weight;
}
@Override public boolean equals(Object edge) {
if (edge instanceof Edge<?> ) {
return Objects.equals(this.source, ((Edge<?>) edge).source) && this.target.equals(((Edge<?>) edge).target);
}
return false;
}
}
Implement ConcreteVerticesGraph nd Implement generic Graph
- 要求
必须使用
private final List<Vertex> vertices = new ArrayList<>();
对于 class Vertex,您定义规范和表示;但是,Vertex必须是可变的。
在确定其规范后,设计、记录和实施针对Vertex测试。然后继续执行Vertex和ConcreteVerticesGraph。
应该加强规范ConcreteVerticesGraph.toString()并在ConcreteVerticesGraphTest.java. 不要将这些测试添加到GraphInstanceTest,它必须测试Graph规范。
测试并提交
- 实现
public class ConcreteVerticesGraph<L> implements Graph<L> {
private final List<Vertex<L>> vertices = new ArrayList<>();
// Abstraction function:
// the List vertices represents the nodes in the graph
// the class Vertex contains edges to or from ThisVertex
// Representation invariant:
// the weight must be non-negative
// the total number of TO edges must be the same with the FROM edges
// ThisVertex of the vertex can't be null
// Safety from rep exposure:
// the vertex class can't be gotten by outside
// make the vertices be private and final and immutable
ConcreteVerticesGraph() {
}
// checkRep
private void checkRep() {
for (Vertex<L> vertex : vertices) {
assert (vertex.ThisVertex() != null);
Map<L, Integer> sources = vertex.sources();
for (Map.Entry<L, Integer> entry : sources.entrySet()) {
assert (entry.getKey() != null);
assert (entry.getValue() > 0);
}
Map<L, Integer> targets = vertex.targets();
for (Map.Entry<L, Integer> entry : targets.entrySet()) {
assert (entry.getKey() != null);
assert (entry.getValue() > 0);
}
}
}
@Override
public boolean add(L vertex) {
for (Vertex<L> V : vertices) {
if (vertex.equals(V.ThisVertex()))
return false;
}
Vertex<L> newVertex = new Vertex<>(vertex);
vertices.add(newVertex);
checkRep();
return true;
}
@Override
public int set(L source, L target, int weight) {
if (weight < 0) throw new RuntimeException("Weight must be positive");
if (!vertices.contains(source) || !vertices.contains(target)) {
if (!vertices.contains(source))
this.add(source);
if (!vertices.contains(target))
this.add(target);
}
if (source.equals(target))
return 0;
Vertex<L> from = null, to = null;
for (Vertex<L> vertex : vertices) {
if (vertex.ThisVertex().equals(source))
from = vertex;
if (vertex.ThisVertex().equals(target))
to = vertex;
}
if (from == null || to == null)
throw new NullPointerException("Inexistent vertex");
int lastEdgeWeight;
if (weight > 0) {
lastEdgeWeight = from.setOutEdge(target, weight);
lastEdgeWeight = to.setInEdge(source, weight);
} else {
lastEdgeWeight = from.removeOutEdge(target);
lastEdgeWeight = to.removeInEdge(source);
}
checkRep();
return lastEdgeWeight;
}
@Override
public boolean remove(L vertex) {
for (Vertex<L> THIS : vertices) {
if (THIS.ThisVertex().equals(vertex)) {
for (Vertex<L> v : vertices) {
if (THIS.sources().containsKey(v)) {
v.removeOutEdge(THIS.ThisVertex());
}
if (THIS.targets().containsKey(v)) {
v.removeInEdge(THIS.ThisVertex());
}
}
vertices.remove(THIS);
checkRep();
return true;
}
}
checkRep();
return false;
}
@Override
public Set<L> vertices() {
Set<L> VERTICES = new HashSet<>();
for (Vertex<L> vertex : vertices)
VERTICES.add(vertex.ThisVertex());
checkRep();
return VERTICES;
}
@Override
public Map<L, Integer> sources(L target) {
Map<L, Integer> sources = new HashMap<>();
for (Vertex<L> vertex : vertices) {
if (vertex.ThisVertex().equals(target)) {
sources.putAll(vertex.sources());
break;
}
}
checkRep();
return sources;
}
@Override
public Map<L, Integer> targets(L source) {
Map<L, Integer> targets = new HashMap<>();
for (Vertex<L> vertex : vertices) {
if (vertex.ThisVertex().equals(source)) {
targets.putAll(vertex.targets());
break;
}
}
checkRep();
return targets;
}
// toString()
public String t
第三部分 Poetic walks
- 要求
GrapgPoet使用语料库初始化诗人,然后在给定的输入下,让诗人转换输入在. GraphPoet_GraphPoet.java
您必须Graph在GraphPoet. 您GraphPoet应该只依赖 的规范Graph,而不是特定Graph实现的细节。否则,实施GraphPoet完全取决于您。
为了读取语料库文件,至少需要考虑三个 Java API:
FileReader是从文件中读取的标准类。FileReader将a包裹起来BufferedReader可以让您使用方便的readLine()方法一次读取整行。
实用程序函数Files.readAllLines(…)将读取 a 中的所有行Path。我们的诗人需要 a File,但File提供了toPath()获取 a 的方法Path。
类Scanner旨在将输入文本分解成片段。它提供了许多功能,您需要仔细阅读其规格。 - 实现
public class GraphPoet {
private final Graph<String> graph = Graph.empty();
// Abstraction function:
// the graph stores the corpus of the text
// the vertices are the words in the text, the edges are the ajacencies whose
// weight is the time "w1" is followed by "w2"
// Representation invariant:
// the vertices can't be empty , ' ' or the '\n'
// words must be lowercase
// Safety from rep exposure:
// set the graph private, do not provide public methods to modify it
/**
* Create a new poet with the graph from corpus (as described above).
*
* @param corpus text file from which to derive the poet's affinity graph
* @throws IOException if the corpus file cannot be found or read
*/
public GraphPoet(File corpus) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(corpus));
String line = "";
String[] words;
while ((line = reader.readLine()) != null) {
line = line.replace(".", " ");
words = line.split(" ");
for (int i = 0; i < words.length; i++) {
graph.add(words[i].toLowerCase());
if (i > 0) {
int lastEdgeWeight = graph.set(words[i - 1].toLowerCase(), words[i].toLowerCase(), 1);
if (lastEdgeWeight != 0)
graph.set(words[i - 1].toLowerCase(), words[i].toLowerCase(), lastEdgeWeight + 1);
}
}
}
reader.close();
checkRep();
}
// checkRep
private void checkRep() {
Set<String> vertices = graph.vertices();
for (String vertex : vertices)
assert (vertex != null);
}
/**
* Generate a poem.
*
* @param input string from which to create the poem
* @return poem (as described above)
*/
public String poem(String input) {
String laststring = input.contains(".") ? "." : "";
String newInput = input.replace(".", " ");
String[] words = newInput.split(" ");
String answer = words[0];
Set<String> vertices = graph.vertices();
Map<String, Integer> sources, targets;
Set<String> intersection;
for (int i = 1; i < words.length; i++) {
if (!vertices.contains(words[i - 1].toLowerCase()) || !vertices.contains(words[i].toLowerCase())) {
answer += " " + words[i];
continue;
}
targets = graph.targets(words[i - 1].toLowerCase());
sources = graph.sources(words[i].toLowerCase());
intersection = sources.keySet();
intersection.retainAll(targets.keySet());
if (intersection.isEmpty()) {
answer += " " + words[i];
continue;
}
int maxBridge = Integer.MIN_VALUE;
String bridge = "";
for (String key : intersection) {
if (sources.get(key) + targets.get(key) > maxBridge) {
maxBridge = sources.get(key) + targets.get(key);
bridge = key;
}
}
answer += " " + bridge + " " + words[i];
}
return answer + laststring;
}
// toString()
@Override
public String toString() {
return "This is an instance of GraphPoet, hashcode: " + this.hashCode();
}
}
任务二 Re-implement the Social Network in Lab1
要求
回顾 Lab1 实验手册中的 3.2 节 Social Network,你针对所提供的客户端代码
实现了 FriendshipGraph 类和 Person 类。
在本次实验中,请基于你在 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 类仍然表现正常。
实现
friendship
public class FriendshipGraph {
private final Graph<Person> graph = Graph.empty();
/**
* @param newPerson adding person
* @return true if add it successfully
*/
public boolean addVertex(Person newPerson) {
return graph.add(newPerson);
}
/**
* addEdge add edges of double directions
*
* @param a A Person
* @param b A Person
*
*/
public int addEdge(Person a, Person b) {
int lastEdgeWeight;
lastEdgeWeight = graph.set(a, b, 1);
lastEdgeWeight = graph.set(b, a, 1);
return lastEdgeWeight;
}
/**
* get Distance of two of them
*
* @param sta path starting person
* @param end path ending person
* @return distance between 2 persons or -1 when unlinked
*/
public int getDistance(Person sta, Person end) {
if (sta.equals(end))
return 0;
Map<Person, Integer> dis = new HashMap<>();
Map<Person, Boolean> vis = new HashMap<>();
Queue<Person> qu = new LinkedList<>();
Set<Person> persons = graph.vertices();
for (Person person : persons) {
dis.put(person, 0);
vis.put(person, false);
}
vis.remove(sta);
vis.put(sta, true);
for (qu.offer(sta); !qu.isEmpty();) {
Person person = qu.poll();
for (Map.Entry<Person, Integer> edge : graph.targets(person).entrySet()) {
Person target = edge.getKey();
if (!vis.get(target)) {
qu.offer(target);
vis.remove(target);
vis.put(target, true);
dis.remove(target);
dis.put(target, dis.get(person) + 1);
if (target.equals(end))
return dis.get(target);
}
}
}
return -1;
}
}
person
public class Person {
private final String Name;
public Person(String PersonName) {
this.Name = PersonName;
}
public String getName() {
return this.Name;
}
}
main
public class Main {
public static void main(String[] args){
FriendshipGraph graph = new FriendshipGraph();
Person rachel = new Person("Rachel");
Person ross = new Person("Ross");
Person ben = new Person("Ben");
Person kramer = new Person("Kramer");
graph.addVertex(rachel);
graph.addVertex(ross);
graph.addVertex(ben);
graph.addVertex(kramer);
graph.addEdge(rachel, ross);
graph.addEdge(ross, rachel);
graph.addEdge(ross, ben);
graph.addEdge(ben, ross);
System.out.println(graph.getDistance(rachel, ross));
//should print 1
System.out.println(graph.getDistance(rachel, ben));
//should print 2
System.out.println(graph.getDistance(rachel, rachel));
//should print 0
System.out.println(graph.getDistance(rachel, kramer));
//should print ‐1
}
}