哈工大2020軟件構造Lab1實驗報告

HIT

2020春計算機學院《軟件構造》課程Lab1實驗報告

1 實驗目標概述

本次實驗通過求解三個問題,訓練基本 Java 編程技能,能夠利用 Java OO 開發基本的功能模塊,能夠閱讀理解已有代碼框架並根據功能需求補全代碼,能夠爲所開發的代碼編寫基本的測試程序並完成測試,初步保證所開發代碼的正確性。另一方面,利用 Git 作爲代碼配置管理的工具,學會 Git 的基本使用方法。
 基本的 Java OO 編程
 基於 Eclipse IDE 進行 Java 編程
 基於 JUnit 的測試
 基於 Git 的代碼配置管理

2 實驗環境配置

2.1 Java & Eclipse & Maven 使用配置方法

2.2 JUnit

3 實驗過程

3.1 Magic Squares

  • 幻方是一個有n*n個不同數字、且每行、每列和斜線上都有相同的和的方形結構。要求寫出程序判斷輸入一個矩陣是否是幻方,並且構造幻方。
  • Main函數有兩個部分,分別是:讀取5個矩陣並判斷、生成一個矩陣判斷後輸出到文件中。在讀取時,用for循環分別將字符1到5拼入地址中,輸出同理。其中在生成幻方之前,判斷n的合法性。

	static final int N = 200;
	public static int[][] square = new int[N][N];
	public static boolean[] vis = new boolean[N * N + 1];

	public static void main(String[] args) throws IOException {
		for (char i = '1'; i <= '5'; i++) {
			System.out.println(i + " " + String.valueOf(isLegalMagicSquare("src/P1/txt/" + i + ".txt")));
		}
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		while (n <= 0 || n % 2 == 0) {
			System.out.println("Input Wrong");
			n = sc.nextInt();
		}
		generateMagicSquare(n);
		System.out.println("6" + " " + String.valueOf(isLegalMagicSquare("src/P1/txt/" + "6" + ".txt")));
		return;
	}

3.1.1 isLegalMagicSquare()

該函數要實現判斷一個矩陣是否爲幻方。

  1. 讀入文件
    a) 創建FileReader、BufferReader、StringBuilder對象
    b) 初始化line
  2. 逐行將字符串轉換爲整型矩陣存儲
    a) 將非空readline的字符串分割
    b) 去除頭尾空格後轉換爲數字存儲到二維數組中
    c) 存儲時判斷該數字是否出現過
    i. 出現過則報錯
    ii. 否則用Boolean表標記
    d) 判斷行列長度是否相等
  3. 計算兩條斜線的和並比較
    a) 分別得出主對角線和次對角線的和
    b) 比較
    i. 若相等則記錄,作爲基準值
    ii. 若不相等則報錯
  4. 計算每條縱線和橫線和和並比較
    a) 分別計算第i條橫線和縱線的和
    b) 與基準值比較
    i. 不相等則報錯
  5. 確定是否爲幻方

	public static boolean isLegalMagicSquare(String fileName) throws IOException {
		File file = new File(fileName);
		FileReader reader = new FileReader(file);
		BufferedReader bReader = new BufferedReader(reader);
		StringBuilder sb = new StringBuilder();
		String line = "";

		int n = 0, m = 0;
		Arrays.fill(vis, false);
		while ((line = bReader.readLine()) != null) {
			String[] l = line.split("\t");
			m = l.length;
			for (int i = 0; i < m; i++) {
				square[n][i] = Integer.valueOf(l[i].trim());
				if (square[n][i] <= 0 || vis[square[n][i]])
					return false;
				else
					vis[square[n][i]] = true;
			}
			n++;
		}
		bReader.close();
		if (n != m)
			return false;
		int s1 = 0, s2 = 0, s = 0;
		for (int i = 0; i < n; i++) {
			s1 += square[i][i];
			s2 += square[n - i - 1][i];
		}
		if (s1 == s2)
			s = s1;
		else
			return false;
		for (int i = 0; i < n; i++) {
			s1 = s2 = 0;
			for (int j = 0; j < n; j++) {
				s1 += square[i][j];
				s2 += square[j][i];
			}
			if (s1 != s || s2 != s)
				return false;
		}
		return true;
	}

3.1.2 generateMagicSquare()

該函數要實現生成一個邊長爲奇數的幻方。

  • 初始化
    i. 生成空矩陣
    ii. Row=0,Col=n/2
  • 循環n*n次填充矩陣
    i. 將矩陣的[row, col]位置填充爲i
    ii. 在保證座標在矩陣範圍內的情況下使Row–,Col++
  • 打開文件,打印結果

	public static boolean generateMagicSquare(int n) throws IOException {
		int magic[][] = new int[n][n];
		int row = 0, col = n / 2, i, j, square = n * n;
		for (i = 1; i <= square; i++) {
			magic[row][col] = i;
			if (i % n == 0)
				row++;
			else {
				if (row == 0)
					row = n - 1;
				else
					row--;
				if (col == (n - 1))
					col = 0;
				else
					col++;
			}
		}
		File file = new File("src/P1/txt/6.txt");
		PrintWriter output = new PrintWriter(file);
		for (i = 0; i < n; i++) {
			for (j = 0; j < n; j++)
				output.print(magic[i][j] + "\t");
			output.println();
		}
		output.close();
		return true;
	}

3.2 Turtle Graphics

  • 該任務需要我們clone已有的程序後,利用turtle按照要求畫圖,其中需要利用幾何知識設計一些函數簡化編程,最後可以發揮想象力進行Personal Art。首先分析turtle的package組成,瞭解類成員。

3.2.1 Problem 1: Clone and import

  • 打開目標存儲文件夾 右鍵點擊Git Bash
  • 輸入git clone https://github.com/ComputerScienceHIT/Lab1-1183710109.git

3.2.2 Problem 3: Turtle graphics and drawSquare

  • 該函數需要實現:已知邊長,畫出邊長爲指定數值的正方形。參數是海龜對象turtle和編程sidelength。
  • 首先將海龜畫筆設置爲黑色。然後執行4次的前進sidelength長度、轉完90度,即可完成一個邊長爲sidelength的正方形。下圖是邊長爲200的正方形:

    public static void drawSquare(Turtle turtle, int sideLength) {
        turtle.color(PenColor.BLACK);
        for (int i = 0; i < 4; i++) {
            turtle.forward(sideLength);
            turtle.turn(90);
        }
    }

3.2.3 Problem 5: Drawing polygons

  • 該問題首先希望已知正多邊形邊數的情況下計算正多邊形的內角度。根據幾何知識可以推導得公式:
  • (double)180.0(double)360.0/sides(double) 180.0 - (double) 360.0 / sides
  • 使用該公式,實現calculateRegularPolygonAngle,通過運行TurtleSoupTest中的Junit測試得:

    public static double calculateRegularPolygonAngle(int sides) {
        return (double) 180.0 - (double) 360.0 / sides;
    }
  • 該問題還希望已知正多變型得邊數和邊長畫出一個正多邊形。參照畫正方形的方法,可以先前進sidelength,再使海龜旋轉一個角度,執行“邊數”次。其中,這個角度是正多邊形內角的補角,利用calculateRegularPolygonAngle的功能計算出多邊形內角,再用180°減去這個值即可。邊長爲100的正六邊形效果如下:

    public static void drawRegularPolygon(Turtle turtle, int sides, int sideLength) {
        turtle.color(PenColor.BLACK);
        for (int i = 0; i < sides; i++) {
            turtle.forward(sideLength);
            turtle.turn((double) 180.0 - calculateRegularPolygonAngle(sides));
        }
    }

3.2.4 Problem 6: Calculating Bearings

該問題首先希望解決,已知起點和當前朝向角度,想知道到終點需要轉動的角度。例如,如果海龜在(0,1)朝向 30 度,並且必須到達(0,0)它必須再轉動 150 度。

  1. 首先使用Math.atan2函數計算兩點之間的邊在座標系的角度,減去當前朝向的角度;
  2. 然後取相反數(海龜旋轉的方向是順時針,座標軸角度的旋轉角度的逆時針);
  3. 再減去90°(海龜的0°線是向上,座標軸的0°線是向右,向右到向上要逆時針旋轉90°);
  4. 最後調整爲0-360°之間(可能大於360°或小於0°)。

    public static double calculateBearingToPoint(double currentBearing, int currentX, int currentY, int targetX,
            int targetY) {
        double angle = Math.atan2(targetY - currentY, targetX - currentX) * 180.0 / Math.PI;
        if (angle < 0)
            angle += 360.0;
        double bearing = (360 - angle + 90 >= 360 ? 90 - angle : 360 - angle + 90) - currentBearing;
        return bearing < 0 ? 360.0 + bearing : bearing;
    }
  • 基於上一個問題,此時有若干個點,想知道從第一個點開始到第二個點,再從第二個點到第三個點……以此類推每次轉向的角度。
  1. 將“起點”選爲第一個點(座標爲(xCoords.get(0),yCoords.get(0)));
  2. 循環n-1次(n爲點的個數)
  3. 每次將第i+1號點設置爲“終點”,通過上一個函數計算旋轉角度並存儲到List中;
  4. 將下一次的“起點”用當前“終點”更新,繼續循環;
  5. 退出循環後返回List。

    public static List<Double> calculateBearings(List<Integer> xCoords, List<Integer> yCoords) {
        double currentBearing = 0.0;
        int currentX = xCoords.get(0), currentY = yCoords.get(0), targetX, targetY;
        int length = xCoords.size();
        List<Double> ans = new ArrayList<>();
        for (int i = 1; i < length; i++) {
            targetX = xCoords.get(i);
            targetY = yCoords.get(i);
            ans.add(calculateBearingToPoint(currentBearing, currentX, currentY, targetX, targetY));
            currentBearing = ans.get(i - 1);
            currentX = targetX;
            currentY = targetY;
        }
        return ans;
    }

3.2.5 Problem 7: Convex Hulls

3.2.5.1 凸包問題

3.2.5.2 算法描述

3.2.5.3 JUnit測試結果

3.2.6 Problem 8: Personal art

3.2.7 Submitting

3.3 Social Network

  • 該任務要求設計一張社交網絡圖,基於連接人與人,並且能計算任意兩人之間的聯繫情況。網絡圖基於兩個類,分別是FriendshipGraph類和Person類。

3.3.1 設計/實現FriendshipGraph類

  • 該類的實際意義是一張社交網絡圖,包括了代表每個Person的點、代表每兩個Person之間聯繫的邊、以及建立點和聯繫和計算距離的方法。

3.3.1.1 鄰接表存儲結構

  • 在存儲社交網絡時,我使用了鄰接表。所有的Node被連在一起,方便查找,並補充了一個head變量用來標記首個Node。假定一個社交網絡爲

  • 則該圖轉換爲鄰接表的示意圖爲:

3.3.1.2 Class Node

  • Node類要實現的是將一個Person轉換爲鄰接表裏的點,所以一個Node有鄰接表中點的重要成員變量:下一個Node爲next,對應的Person對象person,直接連接的邊lastEdge,以及實現鄰接表的相關方法。此外,爲了能實現方法getDistance,我另外增設了vis和dis兩個變量用來記錄是否訪問過以及與當前起點的最近距離。

  • Class Node.Edge
    該類是鄰接表中的邊,每個Edge對象存儲了鄰接表中的下一條邊,以及對應的邊的兩個Person所對應的Node。
  • Method Node.LoadData
    該方法將Person對象導入到Node中進行存儲,需要的時候可以直接調用。
  • Method Node.addNode
    該方法將相應的Node加入到Node的鏈表中(即鄰接表圖中的縱向鏈表)。
  • Method Node.addNodeEdge
    該方法將新的邊加入到對應的Node中,更新每個Node後的Edge鏈表。
	/**
	 * @Class Node : A Node is linked with a particular Person based on graph
	 *        theory. TODO Build nodes and Linking persons
	 * @param next     the person joins in the graph after him/her
	 * @param person   class Person matched with
	 * @param vis      visited mark for BFS
	 * @param dis      lowest distance from a chose person
	 * @param lastEdge first edge in First-Search
	 * @method LoadData record which person THIS belongs to
	 * @method addNode add a new Node for a person
	 * @method addNodeEdge add a edge between 2 Nodes
	 */

	public class Node {
		private Node next = null;
		Person person;
		private boolean vis;
		private int dis = Integer.MAX_VALUE;

		private Edge lastEdge = null;

		public void LoadData(Person person) {
			this.person = person;
			person.node = this;
		}

		public void addNode(Node nextVertex) {
			nextVertex.next = this.next;
			this.next = nextVertex;
		}

		/**
		 * @Class Edge A edge between 2 Nodes
		 * @param origin   one Node
		 * @param terminal another Node
		 * @param nextEdge the next edge which has the same ORIGIN with THIS
		 */

		public class Edge {
			public Node origin = null, terminal = null;
			public Edge nextEdge = null;
		}

		public void addNodeEdge(Node toVertex) {
			Edge newEdge = new Edge();
			newEdge.origin = this;
			newEdge.terminal = toVertex;
			newEdge.nextEdge = this.lastEdge;
			this.lastEdge = newEdge;
		}
	}

3.3.1.3 Method addVertex()

  • 該方法目標是在社交網絡圖中增加一個新的節點,參數是要加入的Person類。首先,方法要對Person的名字進行判重:用哈希集合HashSet記錄下已加入的所有Person的名字,每當新加入一個Person則進行判斷是否在集合中;然後則新建一個Node類,使每一個Person與一個Node對應起來。

	/**
	 * @method addVertex add new vertex if the person's name is unduplicated
	 * @param newPerson adding person
	 * @param head      head of Linked List of persons
	 * @param NameSet   set of persons' names, used for removing duplication
	 */

	private Node head = null;
	private HashSet<String> NameSet = new HashSet<>();

	public void addVertex(Person newPerson) {
		if (NameSet.contains(newPerson.Name)) {
			System.out.println("Person " + newPerson.Name + " already existed.");
			System.exit(0);
		}
		NameSet.add(newPerson.Name);
		Node NewVertex = new Node();
		newPerson.node = NewVertex;
		NewVertex.LoadData(newPerson);
		if (head == null)
			head = NewVertex;
		else
			head.addNode(NewVertex);
		return;
	}

3.3.1.4 Method addEdge()

  • 該方法目標是將兩個Person之間進行聯繫,在鄰接表中,用有向邊來代表“有社交關係”,由於題目設定是社交默認爲雙向,則需要在函數中兩次調用Node中的addNodeEdge方法加兩個方向的邊。考慮到可擴展性和可複用性,程序考慮到了“單向社交的情況”,僅需將雙向加邊中的“B->A”刪除即可。

	/**
	 * @method addEdge add edges of double directions
	 * @param a,b 2 persons being linking with an edge
	 * @param A,B 2 nodes of the 2 persons
	 */

	public void addEdge(Person a, Person b) {
		if (a == b) {
			System.out.println("They are the same one.");
			System.exit(0);
		}
		Node A = a.node, B = b.node;
		A.addNodeEdge(B);
		B.addNodeEdge(A);
		return;
	}

3.3.1.5 Method getDistance()

  • 該方法要計算任意兩個Person之間的“距離”,若沒有任何社交關係則輸出“-1”。兩個Person之間計算使用BFS,默認邊權爲1,則在搜索到邊時加1即可,搜索到目標點退出;特殊情況根據要求輸出0或-1。

	/**
	 * @method getDistance
	 * @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 == end)
			return 0;
		Queue<Person> qu = new LinkedList<Person>();
		for (Node p = head; p != null; p = p.next) {
			p.vis = false;
			p.dis = 0;
		}
		sta.node.vis = true;
		for (qu.offer(sta); !qu.isEmpty();) {
			Person p = qu.poll();
			for (Node.Edge e = p.node.lastEdge; e != null; e = e.nextEdge) {
				if (!e.terminal.vis) {
					qu.offer(e.terminal.person);
					e.terminal.vis = true;
					e.terminal.dis = p.node.dis + 1;
					if (e.terminal.person == end)
						return end.node.dis;
				}
			}
		}
		return -1;
	}

3.3.2 設計/實現Person類

  • 該類的目標是將每一個人對應到一個Person對象,並存儲名字的信息。此外,在我的設計中,爲了方便起見,我將每個Person對象在FriendshipGraph中對應的Node存儲在對應Person的成員變量中。
public class Person {
    public String Name;
    public FriendshipGraph.Node node = null;

    public Person(String PersonName) {
        Name = PersonName;
    }
}

3.3.3 設計/實現客戶端代碼main()

3.3.3.1 重複名字錯誤測試

    /**
     * TODO to test program exit when graph has the same names.
     */
    @Test
    void ExceptionProcess() {
        FriendshipGraph graph = new FriendshipGraph();

        Person a = new Person("a");
        graph.addVertex(a);

        Person b = new Person("b");
        graph.addVertex(b);

        // Person c = new Person("a");
        // graph.addVertex(c);
    }

3.3.3.2 簡單圖測試

    /**
     * Basic Network Test
     */
    @Test
    void GraphTest1() {
        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));// 1
         * System.out.println(graph.getDistance(rachel, ben));// 2
         * System.out.println(graph.getDistance(rachel, rachel));// 0
         * System.out.println(graph.getDistance(rachel, kramer));// -1
         */
        assertEquals(1, graph.getDistance(rachel, ross));
        assertEquals(2, graph.getDistance(rachel, ben));
        assertEquals(0, graph.getDistance(rachel, rachel));
        assertEquals(-1, graph.getDistance(rachel, kramer));
    }

3.3.3.3 複雜圖測試

    /**
     * Further Test
     */
    @Test
    void GrpahTest2() {
        FriendshipGraph graph = new FriendshipGraph();

        Person a = new Person("A");
        Person b = new Person("B");
        Person c = new Person("C");
        Person d = new Person("D");
        Person e = new Person("E");
        Person f = new Person("F");
        Person g = new Person("G");
        Person h = new Person("H");
        Person i = new Person("I");
        Person j = new Person("J");

        graph.addVertex(a);
        graph.addVertex(b);
        graph.addVertex(c);
        graph.addVertex(d);
        graph.addVertex(e);
        graph.addVertex(f);
        graph.addVertex(g);
        graph.addVertex(h);
        graph.addVertex(i);
        graph.addVertex(j);

        graph.addEdge(a, b);
        graph.addEdge(a, d);
        graph.addEdge(b, d);
        graph.addEdge(c, d);
        graph.addEdge(d, e);
        graph.addEdge(c, f);
        graph.addEdge(e, g);
        graph.addEdge(f, g);
        graph.addEdge(h, i);
        graph.addEdge(i, j);

        assertEquals(2, graph.getDistance(a, e));
        assertEquals(1, graph.getDistance(a, d));
        assertEquals(3, graph.getDistance(a, g));
        assertEquals(3, graph.getDistance(b, f));
        assertEquals(2, graph.getDistance(d, f));
        assertEquals(2, graph.getDistance(h, j));
        assertEquals(0, graph.getDistance(i, i));
        assertEquals(-1, graph.getDistance(d, j));
        assertEquals(-1, graph.getDistance(c, i));
        assertEquals(-1, graph.getDistance(f, h));
    }

3.3.4 設計/實現測試用例

3.3.4.1 重複名字錯誤測試

  • 測試當有重複名字“a”時,程序是否能終止
        Person a = new Person("a");
        graph.addVertex(a);
        Person b = new Person("b");
        graph.addVertex(b);
        Person c = new Person("a");
        graph.addVertex(c);

3.3.4.2 簡單圖測試

  • 根據題目中的社交網絡圖:

  • 分別測試:
  1. Rachel和Ross距離是1,Rachel和Ben距離是2
  2. Rachel和Rachel距離是0
  3. Rachel和Kramer距離是-1

3.3.4.3 複雜圖測試

  • 設計10個點、10條邊的社交網絡圖:

  • 分別測試:
  1. AE距離2,AD距離1,AG距離3,BF距離3,DF距離2,HJ距離2
  2. II距離0
  3. DJ距離-1,CI距離-1,FH距離-1

3.3.4.4 Junit測試結果

4 實驗進度記錄

5 實驗過程中遇到的困難與解決途徑

6 實驗過程中收穫的經驗、教訓、感想

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章