目录

 

1 实验目标概述    1

2 实验环境配置    1

3 实验过程    1

3.1 Poetic Walks    1

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.1 Test GraphPoet    2

3.1.5.2 Implement GraphPoet    2

3.1.5.3 Graph poetry slam    2

3.1.6 Before you're done    2

3.2 Re-implement the Social Network in Lab1    2

3.2.1 FriendshipGraph    2

3.2.2 Person    3

3.2.3 客户端main()    3

3.2.4 测试用例    3

3.2.5 提交至Git仓库    3

3.3 Playing Chess    3

3.3.1 ADT设计/实现方案    3

3.3.2 主程序MyChessAndGoGame设计/实现方案    3

3.3.3 ADT和主程序的测试方案    3

4 实验进度记录    4

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

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

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

6.2 针对以下方面的感受    4

 

 

 

  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. 实验环境配置

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

一开始我在网站上搜索EckEmma的配置过程,但是发现在官网上下载似乎需要FQ,这令我而苦恼,而后我又在的eclipse商店查找相关信息,结果发现也无法下载。 稍微思考了一下我又去搜了一下,原来现在的eclipse直接装的就有这个工具,因此实验环境就不用再配置了。

 

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

https://github.com/ComputerScienceHIT/Lab2-1180500313.git

  1. 实验过程

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

  1. Poetic Walks

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

  1. Get the code and prepare Git repository

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

git clone https://github.com/rainywang/Spring2020_HITCS_SC_Lab2.git

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

 

git init

git remote add origin git@github.com:ComputerScienceHIT/Lab2-1180500313.git

git pull origin master

git add .

git commit -m "init"

git push origin master

用上面的语句初始化本地仓库并做第一次提交的测试,之后用git管理本地开发时进行类似的操作即可。

 

Problem 1: Test Graph <String>

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

public static Graph<String> empty() {

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

    return new ConcreteEdgesGraph();

}

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

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

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

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

// 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%,至于没能完全测试的原因是涉及到异常的测试,这一部分还没学习,因此就未进行测试

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

 

// 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,加入新的targetweight,若已经有一个存在则返回weight,否则返回0。最后还要进行不变量的检查,调用checkRep()。

最后的运行结果为:

测试覆盖率:

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

 

 

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

 

 

能返回一个空的实例即可。

    public static <L> Graph<L> empty() {

    Graph<L> graph = new ConcreteEdgesGraph<L>();

        return graph;

}

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

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

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

    // 文件只有一行

    // 文件有多行

    // 文件为空

    

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

    // 所有权都是1

    //有不为1的权

    

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

    // 文件为空

    //文件不为空

测试用的语料库:

 

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

 

// 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以空格划分,保存在数组中,随后每次取相邻元素,在图中新增边。

 

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

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

覆盖度:

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

请按照http://web.mit.edu/6.031/www/sp17/psets/ps2/#before_youre_done的说明,检查你的程序。

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

 

首先进入我们要提交的文件夹中,然后依次输入以下指令

git add .

git commit -m "P1"

git push origin master

 

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

 

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

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

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

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

 

我们将原来用邻接表表现社交关系换为了用有向图来表现,有向图是以边和顶点构成的。并且有向图的边是正权的,而不是局限于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就加入队列,是就返回最终距离。

 

这里的变量和lab1一样,唯一的一个方法是:

public Person(String nameString) {

        if (personlist.contains(nameString)) {

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

        } else {

            this.name = nameString;

            personlist.add(nameString);

        }

    }

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

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));

    }

 

}

我们进行等价类划分的大致策略如下:

    // Testing strategy

    // addVertex输入划分等价类:

    //只有一个人名

    //有重复人名

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

    // 起始点存在

    //起始点不存在

    // getDistance输入划分等价类:

    // 距离存在

    //距离不存在

测试结果如下:

测试样例覆盖率如下:

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

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

首先进入我们要提交的文件夹中,然后依次输入以下指令

git add .

git commit -m "P1"

git push origin master

 

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

 

 

我定义的主要类:

Action模拟下棋的动作

public class Action {

    private final ArrayList<String> historys = new ArrayList<>();

    private int historycounter = 0;

    // Abstraction function:

    //映射为现实中下棋的场景

 

    // Representation invariant:

    // 每轮必须有棋手下棋

    // 棋手必须轮流下棋

    //棋手只能下在棋盘内

 

     // Safety from rep exposure:

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

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

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

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

//historycounter是基本数据类型,是不可变的

Board模拟下棋的动作

public class Board {

    private int boardSize;

    private final Set<Piece> boardSet = new HashSet<>();

    public String playerA;

    public String playerB;

        // Abstraction function:

        // 从一个boardSet映射到真实的棋盘

 

        // Representation invariant:

        //进行操作的坐标必须在棋盘内,一个位置不能有两个棋子

 

        // Safety from rep exposure:

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

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

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

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

        //String ,int都是不可变的数据类型,防止了表示的泄露

Game进行对弈的初始化

// Abstraction function:

    // 映射为真实世界的对弈游戏,进行初始化

 

    // Representation invariant:

    //进行操作的坐标必须在棋盘内,一个位置不能有两个棋子

    //两人必须轮流下棋

 

    // Safety from rep exposure:

    // Board是不可变的

//gameAction,PlayerA,PlayerB都是private,final的

所有类的UML

 

辅之以执行过程的截图,介绍主程序的设计和实现方案,特别是如何将用户在命令行输入的指令映射到各ADT行。

 

方法:

goGameMenu

输出围棋的菜单

cheseGameMenu

输出国际象棋菜单

gameMain

游戏尚未明确类型时的客户端函数

goGame

执行围棋操作

cheseGame

执行国际象棋的函数

main

声明一个MyChessAndGoGame实例,调用gameMain方法,启动游戏。

游戏流程:

首先是main函数,新建实例,开始gameMain方法。

gameMain方法读取玩家输入选择游戏的种类,

初始化玩家名字,初始化棋盘,

之后按游戏种类调用两个游戏方法:

在游戏函数中,针对落子,提子,吃子一类操作,都由客户端读取输入,分解用户输入,检查输入是否合法:(这里以落子为例,其他类似)

之后传递参数让game类执行操作,game类再由声明的action实例调用board类执行一系列落子,提子,吃子操作,并返回成功与否的布尔值。如果执行不成功输出错误提示信息,如果成功,由action类记录历史,返回真值,game类返回真值给客户端,客户端收到真值后,令TURN = (TURN + 1) % 2;,达到令TURN01之间反复的效果。以此判断下一个选手是谁。

如果输入是end,修改exitflag之后,退出。

之后选择是否输出游戏历史:

游戏流程截图:

 

介绍针对各ADT的各方法的测试方案和testing strategy

介绍你如何对该应用进行测试用例的设计,以及具体的测试过程。

 

主程序主要使用手动测试的方法,针对参数越界,控制权不符合要求,输入参数不足等情况手动测试。

测试board类:

主要测试putPieceremovePiecemoveeat几个重点操作。

public void testputPiece() {

        Board B = new Board("b");

        Piece piece1 = new Piece("AAA", 1);

        Piece piece2 = new Piece("AAA", 1);

        piece1.setPosition(1, 1);

        piece2.setPosition(101, 101);

        assertTrue(B.putPiece(piece1));

        assertFalse(B.putPiece(piece1));

        assertFalse(B.putPiece(piece2));

    }

 

    @Test

    public void testremovePiece() {

        Board B = new Board("b");

        Piece piece1 = new Piece("AAA", 1);

        Position pa = new Position(1, 1);

        Position pb = new Position(2, 2);

        piece1.setPosition(1, 1);

        B.putPiece(piece1);

        assertTrue(B.removePiece(pa));

        assertFalse(B.removePiece(pb));

    }

 

    @Test

    public void testmove() {

        Game game = new Game("a");

        Board B = game.getGameBoard();

        Player paPlayer = new Player();

        paPlayer.setPlayerName("a");

        paPlayer.setPlayerTurn(0);

        Position pa = new Position(0, 0);

        Position pb = new Position(2, 2);

        Position pc = new Position(3, 3);

        Position pd = new Position(999, 999);

        assertTrue(B.move(paPlayer, pa, pb));

        assertTrue(B.move(paPlayer, pb, pc));

        assertFalse(B.move(paPlayer, pa, pb));

        assertFalse(B.move(paPlayer, pd, pb));

 

    }

 

    @Test

    public void testeat() {

        Game game = new Game("a");

        Board B = game.getGameBoard();

        Player paPlayer = new Player();

        paPlayer.setPlayerName("a");

        paPlayer.setPlayerTurn(0);

        Position pa = new Position(0, 0);

        Position pb = new Position(2, 2);

        Position pc = new Position(3, 3);

        Position pd = new Position(999, 999);

        assertTrue(B.move(paPlayer, pa, pb));

        assertTrue(B.move(paPlayer, pb, pc));

        assertFalse(B.move(paPlayer, pa, pb));

        assertFalse(B.move(paPlayer, pd, pb));

    }

测试Game:按照操作流程测试

public void testAssertionsEnabled() {

        assert false;

    }

    public Game gametest =new Game("b");

    

    @Test

    public void testgettersetter() {

        gametest.setNames("pl1", "pl2");

        assertEquals("pl1", gametest.getPlayerA().getPlayerName());

        assertEquals("pl2", gametest.getPlayerB().getPlayerName());

    }

    @Test

    public void testaddnew() {

        Piece testPiece1=new Piece("black",0);

        Piece testPiece2=new Piece("white",1);

        Piece testPiece3=new Piece("white",1);

        Position P1 =new Position(1,1);

        Position P2 =new Position(2,1);

        Position P3 =new Position(3,1);

        gametest.addnewPiece(gametest.getPlayerA(), testPiece1, P1);

        gametest.addnewPiece(gametest.getPlayerB(), testPiece2, P2);

        gametest.addnewPiece(gametest.getPlayerB(), testPiece3, P3);

        assertTrue(gametest.getGameBoard().getBoardSet().contains(testPiece1));

        assertTrue(gametest.getGameBoard().getBoardSet().contains(testPiece2));

        assertTrue(gametest.getGameBoard().getBoardSet().contains(testPiece3));

    }

测试结果:

 

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

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

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

日期

时间段

计划任务

实际完成情况

2020-03-12

18:30-21:50

完成P1问题的边实现

未完成

2020-03-16

19:00-24:00

完成P1问题的边实现

完成

2020-03-18

18:30-22:00

完成P1问题的顶点实现

完成

2020-03-18

22:00-23:50

完成P1

未完成

2020-03-19

18:30-24:00

完成P1

完成

2020-03-24

17:30-21:00

完成P2

完成

2020-03-28

09:00-22:00

完成P3

未完成

2020-04-04

13:00-21:00

完成P3

完成

2020-04-05

13:00-24:00

更新代码,完成实验报告

完成

遇到的难点

解决途径

忘记怎么书写规约,AFRI

查看PPT

Eclipse不会安装UML生成工具

 

查看博客

 

 

 

在类的总体设计能力上仍有欠缺。协调这些类的能力也有欠缺。我得到的经验是:首先要明白自己要设计什么,写出测试,然后根据目标为导向进行程序设计会快得多。

 

posted on 2020-04-13 00:13  何莫道  阅读(416)  评论(0编辑  收藏  举报