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

目录

1 实验目标概述··· 1

2 实验环境配置··· 1

3 实验过程··· 2

3.1 Poetic Walks· 2

3.1.1 Get the code and prepare Git repository· 2

3.1.2 Problem 1: Test Graph <String>· 2

3.1.3 Problem 2: Implement Graph <String>· 3

3.1.3.1 Implement ConcreteEdgesGraph· 3

3.1.3.2 Implement ConcreteVerticesGraph· 5

3.1.4 Problem 3: Implement generic Graph<L>· 8

3.1.4.1 Make the implementations generic· 8

3.1.4.2 Implement Graph.empty()· 8

3.1.5 Problem 4: Poetic walks· 8

3.1.5.1 Test GraphPoet· 8

3.1.5.2 Implement GraphPoet· 9

3.1.5.3 Graph poetry slam·· 12

3.1.6 Before you’re done· 12

3.2 Re-implement the Social Network in Lab1· 12

3.2.1 FriendshipGraph类··· 12

3.2.2 Person类··· 14

3.2.3 客户端main()· 15

3.2.4 测试用例··· 17

3.2.5 提交至Git仓库··· 18

4 实验进度记录··· 18

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

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

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

6.2 针对以下方面的感受··· 19

  1. 实验目标概述

本次实验训练抽象数据类型(ADT)的设计、规约、测试,并使用面向对象

(编程OOP)技术实现 ADT。具体来说:

-针对给定的应用问题,从问题描述中识别所需的 ADT;

- 设计ADT(规约pre-conditionpost-condition)并评估规约的质量;

- 根据ADT的规约设计测试用例;

-ADT的泛型化;

- 根据规约设计ADT的多种不同的实现;针对每种实现,设计其表示

(representation)、表示不变性(rep invariant)、抽象过程(abstraction

function)

- 使用 OOP实现 ADT,并判定表示不变性是否违反、各实现是否存在表

(示泄露rep exposure);

- 测试 ADT的实现并评估测试的覆盖度;

- 使用 ADT及其实现,为应用问题开发程序;

- 在测试代码中,能够写出testing strategy

  1. 实验环境配置

本次实验的总体配置和上次实验相同,这里不再赘述。唯一需要增添的插件就是代码覆盖工具。

在这里给出你的GitHub Lab2仓库的URL地址(Lab2-学号)。

https://github.com/ComputerScienceHIT/HIT-Lab2-L20L021201.git

  1. 实验过程

请仔细对照实验手册,针对两个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。

    1. Poetic Walks

在这里简要概述你对该任务的理解。

      1. Get the code and prepare Git repository

如何从GitHub获取该任务的代码、在本地创建git仓库、使用git管理本地开发。

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

在本地获取该任务代码只需输入上述代码即可。

      1. Problem 1: TestGraph <String>

以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。

这里我们只需要测试Graph<String>中的empty()方法即可。补全Graph中的empty()。

public static Graph<String> empty() {

//throw new RuntimeException("not implemented");

return new ConcreteEdgesGraph();

}

此外由于测试中使用了Graph.empty().vertices()方法,我们将其补全,返回HashSet()。

      1. Problem 2: ImplementGraph <String>

以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。

        1. ImplementConcreteEdgesGraph

为了实现graph接口我们总体的想法就是测试优先,首先写出测试,然后根据规约进行设计。本实现方式是基于边的。

关于AF,RI和避免表示泄露,这里采用

// Abstraction function:

//用边的方式来表示图,映射到一个图结构上。

// Representation invariant:

//图的边是有向正权的,图中没有复边(两条方向和连接点均相同的边

//也没有重复的顶点

// Safety from rep exposure:

// vertices和edges是private final的,信息对外界是隐藏的

//final保证外界无法改变他的引用

//并且使用了防御式拷贝

//防止外界通过对内部引用进行操作导致表示泄露

而关于我们具体的设计,本部分涉及到的方法和变量如下:

其中右侧为用边实现一个有向正权图的所有方法和变量,左侧为实现一个完整的图所使用的方法和变量。其中比较重要的方法如:

public int set(L source, L target, int weight) {

int currentweight = 0;

if (weight != 0) {

            for (Edge<L> e : edges) {

if (e.getSource().equals(source) && e.getTarget().equals(target)) {

currentweight = e.getWeight();

edges.remove(e);

break;

}

}

vertices.add(source);

vertices.add(target);

edges.add(new Edge<L>(source, target, weight));

} else {

            for (Edge<L> e : edges) {

if (e.getSource().equals(source) && e.getTarget().equals(target)) {

edges.remove(e);

currentweight = 0;

break;

}

}

}

checkRep();

return currentweight;

}

这个地方实际上实现的是设置一条有起点、有终点、带权值的边的方法。我们通过输入的权值来更新目前储存的权值,便于返回操作得到的状态(成功更改,未找到目标顶点),实现方式是迭代器的查找。

实现结果:

测试覆盖率:

这里达到了94.6%,至于没能完全测试的原因是涉及到异常的测试,这一部分还没学习,因此就未进行测试

        1. ImplementConcreteVerticesGraph

关于AF,RI和避免表示泄露,这里采用

// Abstraction function:

//用顶点的方式来表示图,映射到一个图结构上。

// Representation invariant:

//图的边是有向正权的,图中没有复边(两条方向和连接点均相同的边

//也没有重复的顶点

// Safety from rep exposure:

// vertices是private final的,信息对外界是隐藏的

//final保证外界无法改变他的引用

//并且使用了防御式拷贝

//防止外界通过对内部引用进行操作导致表示泄露

而关于我们具体的设计,本部分涉及到的方法和变量如下:

其中右侧为用顶点角度实现一个有向正权图的所有方法和变量,左侧为实现一个完整的图所使用的方法和变量。其中比较重要的方法如:

public int setSource(L source, int weight) {

Integer now_Weight = 0;

if(weight<0)

{

throw new RuntimeException("边权不为负");

}

else if (weight == 0) {

now_Weight = this.removeSource(source);

} else {

now_Weight = this.source_vertex.put(source, weight);

if(now_Weight==null) now_Weight=0;

else now_Weight=weight;

}

checkRep();

return now_Weight;

}

设计思路是 weight<0 抛出异常"边权为负"。weight=0,删去传入的target,返回其weight,若没找到则返回0; weight>0,加入新的target和weight,若已经有一个存在则返回weight,否则返回0。最后还要进行不变量的检查,调用checkRep()。

最后的运行结果为:

测试覆盖率:

这里达到了93.0%,至于没能完全测试的原因是涉及到异常的测试,这一部分还没学习,因此就未进行测试.

      1. Problem 3: Implement genericGraph<L>
        1. Make the implementations generic

将上面涉及到具体对象类型的地方改为泛型实现,可以采用eclipse中的重构功能。

        1. ImplementGraph.empty()
      1. Problem 4: Poetic walks

这里我们实现了对前面已经构建好的数据结构的复用。具体的复用方式是利用了前面的有向无权图的格式,在给定输入句子时,会参考已有的语料库,检测单词和单词之间是否存在bridge,即对语料库不一样的单词看作一个顶点,相邻的单词对应图中的一条有向边。相邻单词对出现的次数,作为这条有向边的权值。如果输入的句子在图中出现并且之间有通路的话,就会进行信息补全,加入间隔的单词。

        1. TestGraphPoet

我们进行测试的等价类划分原则:

// 对GraphPoet的文件输入划分等价类:

// 文件只有一行

// 文件有多行

// 文件为空

   

// 对poem的选择划分等价类:

// 所有权都是1

//有不为1的权

   

// 对toString的输入划分等价类:

// 文件为空

//文件不为空

测试用的语料库:

        1. ImplementGraphPoet

关于AF,RI和避免表示泄露,这里采用

// Abstraction function:

// 将一个加权有向图映射到一个写诗的任务上

//诗歌之间的词语就是加权有向图通路上的点

// Representation invariant:

// 该图是加权有向图 weight>0

//图是非空的

// Safety from rep exposure:

//graph是private final的,信息对外界是隐藏的

//final保证外界无法改变他的引用

//并且使用了防御式拷贝

//防止外界通过对内部引用进行操作导致表示泄露

我们实现过程中涉及到的变量和方法如下

主要的一些方法设计思路:

poem方法

参数:输入的原始句子。

利用spilt分割输入字符串,用StringBuilder保存返回结果。每次读取一个词,然后以当前词为source,下一个词为target,在graph中寻找符合此条件的边,记录权值,结束后选择权值最大的,调用StringBuilder. Append方法,将节点名字加入字符串。

public String poem(String input) {

StringBuilder SB = new StringBuilder();

List<String> list = new ArrayList<String>(Arrays.asList(input.split(" ")));

Map<String, Integer> sourceMap = new HashMap<>();

Map<String, Integer> targetMap = new HashMap<>();

        for (int i = 0; i < list.size() - 1; i++) {

SB.append(list.get(i)).append(" ");

String sourceString = list.get(i).toLowerCase();

String targetString = list.get(i + 1).toLowerCase();

targetMap = graph.targets(sourceString);

sourceMap = graph.sources(targetString);

int maxWeight = 0;

String bridgeWord = "";

            for (String string : targetMap.keySet()) {

if (sourceMap.containsKey(string) && sourceMap.get(string) + targetMap.get(string) > maxWeight) {

maxWeight = sourceMap.get(string) + targetMap.get(string);

bridgeWord = string;

}

}

if (maxWeight > 0) {

SB.append(bridgeWord + " ");

}

}

SB.append(list.get(list.size() - 1));

return SB.toString();

}

GraphPoet方法

参数:本地的语料库。

打开文件,读取文件输入,识别单个的单词,构建图结构。利用BufferedReader.readLine方法读取全部输入后用string.split以空格划分,保存在数组中,随后每次取相邻元素,在图中新增边。

        1. Graph poetry slam

我们用吉檀迦利诗歌节选作为语料库。

运行效果如上图,下面是补全之后的诗歌。

:覆盖度

我们的方法覆盖度为97%,这也进一步印证了我们代码的正确性。

      1. Before you’re done

请按照Problem Set 2: Poetic Walks的说明,检查你的程序。

如何通过Git提交当前版本到GitHub上你的Lab2仓库。

在这里给出你的项目的目录结构树状示意图。

    1. Re-implement the Social Network in Lab1

在这里简要概述你对该任务的理解。

我们在lab1里面实际上已经构建过一个图,虽然我们实验1要求里面是采用无向图进行测试,但是实际上也是用有向图来实现的,那么我们进行了哪些扩充呢?实际上是进行了扩充到泛型领域的操作,我们的顶点可以是自己设计的对象people,也可以是其他对象。当然我们也加入了顶点之间的权值。

泛型的加入使我们程序的可扩展性大大增加了。

      1. FriendshipGraph

给出你的设计和实现思路/过程/结果。

我们将原来用邻接表表现社交关系换为了用有向图来表现,有向图是以边和顶点构成的。并且有向图的边是正权的,而不是局限于lab1的只有一种权。

这里涉及到的主要方法为:

addVertex:增加新的人

addEdge:增加新朋友

getDistance:获取二人距离。

public int getDistance(Person Person1, Person Person2) {

if (Person1 == Person2)

return 0;

Queue<Person> queue = new LinkedList<>();

Map<Person, Integer> distantMap = new HashMap<>();

queue.offer(Person1);

distantMap.put(Person1, 0);

while (!queue.isEmpty()) {

Person topPerson = queue.poll();

int nowDis = distantMap.get(topPerson);

Set<Person> friendList = people.targets(topPerson).keySet();

for (Person ps : friendList)

if (!distantMap.containsKey(ps)) {

distantMap.put(ps, nowDis + 1);

queue.offer(ps);

if (ps == Person2) {

return distantMap.get(Person2);

}

}

}

return -1;

}

这里使用了队列和深度优先搜索来实现计算两人之间的距离。具体思路就是:用diatantmap保存其他人到自己的距离,然后入队自己,将朋友加入队列,不是person2就加入队列,是就返回最终距离。

      1. Person

给出你的设计和实现思路/过程/结果。

public Person(String nameString) {

if (personlist.contains(nameString)) {

System.out.println("出现了重复的名字");

} else {

this.name = nameString;

personlist.add(nameString);

}

}

这个构造方法可以加入新的人,并判断是否重复。

      1. 客户端main()

给出你的设计和实现思路/过程/结果。

public class FriendshipGraphTest {

@Test(expected = AssertionError.class)

public void testAssertionsEnabled() {

assert false;

}

@Test

public void testsamePerson() {

FriendshipGraph graph = new FriendshipGraph();

Person a = new Person("a");

graph.addVertex(a);

Person f = new Person("a");

assertFalse(graph.getPeople().vertices().contains(f));

}

@Test

public void testsaddEdge() {

FriendshipGraph graph = new FriendshipGraph();

Person a = new Person("a");

Person b = new Person("b");

graph.addVertex(a);

graph.addVertex(b);

graph.addEdge(a, b);

assertEquals("expected distance", 1, graph.getDistance(a, b));

}

@Test

public void testsaddEdgenotexist() {

FriendshipGraph graph = new FriendshipGraph();

Person a = new Person("a");

Person b = new Person("b");

graph.addEdge(a, b);

assertEquals("expected distance", 1, graph.getDistance(a, b));

}

@Test

public void testFriendshipGraph() {

FriendshipGraph graph = new FriendshipGraph();

Person a = new Person("a");

Person b = new Person("b");

Person c = new Person("c");

Person d = new Person("d");

graph.addEdge(a, b);

graph.addEdge(b, c);

graph.addEdge(c, d);

assertEquals("expected distance", 1, graph.getDistance(a, b));

assertEquals("expected distance", 1, graph.getDistance(b, c));

assertEquals("expected distance", 1, graph.getDistance(c, d));

assertEquals("expected distance", 2, graph.getDistance(a, c));

assertEquals("expected distance", 2, graph.getDistance(b, d));

assertEquals("expected distance", 3, graph.getDistance(a, d));

assertEquals("expected distance", -1, graph.getDistance(b, a));

}

}

      1. 测试用例

给出你的设计和实现思路/过程/结果,覆盖度报告。

// Testing strategy

// addVertex输入划分等价类:

//只有一个人名

//有重复人名

// addEdge的输入划分等价类:

// 起始点存在

//起始点不存在

// getDistance输入划分等价类:

// 距离存在

//距离不存在

测试结果如下:

测试样例覆盖率如下:

可以看到,P2的测试覆盖率接近100%

      1. 提交至Git仓库

如何通过Git提交当前版本到GitHub上你的Lab2仓库。

在这里给出你的项目的目录结构树状示意图。

  1. 实验进度记录

请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。

每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。

不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。

日期

时间段

计划任务

实际完成情况

2022-05-19


18:30-21:50

完成P1问题的边实现


未完成

2022-05-20


19:00-24:00

完成P1问题的边实现


完成

2022-05-22

18:30-22:00


完成P1问题的顶点实现

未完成

2022-05-23

22:00-23:50

完成P1

完成

2022-05-25

18:30-24:00


完成P1


完成

2022-05-27


17:30-21:00

完成P2


完成

2022-05-28

13:00-21:00

完成P3

完成

2022-05-29


13:00-24:00


更新代码,完成实验报告


更新代码,完成实验报告

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

遇到的难点

解决途径

忘记怎么书写规约,AFRI


查看PPT

  1. 实验过程中收获的经验、教训、感想
    1. 实验过程中收获的经验和教训
    2. 针对以下方面的感受
  1. 面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?
    后者针对的是具体对象,在代码量和可复用性上都比面向过程要好得多。

  1. 使用泛型和不使用泛型的编程,对你来说有何差异?

泛型增加了程序的可扩展性。

  1. 在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?
    以目标为导向进行程序设计,更加具有针对性。

  1. P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?
    这种复用实际上是提炼出了实物的一些本质属性,这些属性可能是抽象的,我们就可以对这些抽象的属性进行复用,比如P3的国际象棋和围棋有一些共同的属性,我们对其加以复用就能减少很多工作量。

  1. 为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?
    避免内部变量被修改,很有必要,不然会出现一些很难察觉的bug

  1. 关于本实验的工作量、难度、deadline。
    难度适中

  1. 《软件构造》课程进展到目前,你对该课程有何体会和建议?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值