2022年春季学期
计算学部《软件构造》课程
**Lab 1实验报告
**
姓名 | 李启明 |
---|---|
学号 | 120L021920 |
班号 | 2003006 |
电子邮件 | 1094583745@qq.com |
手机号码 |
目录
3.2.1 Problem 1: Clone and import 2
3.2.2 Problem 3: Turtle graphics and drawSquare 2
3.2.3 Problem 5: Drawing polygons 2
3.2.4 Problem 6: Calculating Bearings 2
3.2.5 Problem 7: Convex Hulls 2
3.2.6 Problem 8: Personal art 2
实验目标概述
本次实验通过求解三个问题,训练基本 Java 编程技能,能够利用 Java OO 开
发基本的功能模块,能够阅读理解已有代码框架并根据功能需求补全代码,能够
为所开发的代码编写基本的测试程序并完成测试,初步保证所开发代码的正确性。
另一方面,利用 Git 作为代码配置管理的工具,学会 Git 的基本使用方法。
基本的 Java OO 编程
基于 Eclipse IDE 进行 Java 编程
基于 JUnit 的测试
基于 Git 的代码配置管理
实验环境配置
(简要陈述你配置本次实验所需开发、测试、运行环境的过程,必要时可以给出屏幕截图。
特别是要记录配置过程中遇到的问题和困难,以及如何解决的。)
本次实验开发测试运行的环境为IDEA,在配置环境的过程中,主要遇到的是原来的版本Java JDK8与实验要求的JDK11不符,通过在IDEA上简单的调试即可解决上述问题。
在这里给出你的GitHub Lab1仓库的URL地址。
https://classroom.github.com/a/K_B5NllB
实验过程
请仔细对照实验手册,针对四个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但无需把你的源代码全部粘贴过来!)。
为了条理清晰,可根据需要在各节增加三级标题。
Magic Squares
幻方问题,首先考虑的是如何读写文件,其次是如何判断是否是幻方。
读:主要是读取文件需要
File file = new File(fileName);
FileReader reader = new FileReader(file);
BufferedReader bReader = new BufferedReader(reader);
写:在工作台和在写6.txt的时候需要
工作台键入
Scanner sc = new Scanner(System.in);
写6.txt
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();
}
isLegalMagicSquare()
按步骤给出你的设计和实现思路/过程/结果。
首先判断格式是否正确。格式判断包括是否为矩阵、输入的内容是否合法。
用length[]来记录每一行的长度,如果后一行不等于前一行,要么说明不是矩阵,要么说明不使用/t的格式;其次如果格式正确,那么验证格式内容,如果内容不是/d或者为负数或者与之前的元素重复,均会报错。
其次判断是否是幻方。这个任务可以被分为三部分:
- 主对角线是否和次对角线相等?
- Sum_row是否相等?
- Sum_collom是否相等?
通过设计循环即可进行判断,难度不大。
if (row != col)
return false;
else
n = col;
for (i = 0; i < n; i++) {
sum_row += Msquare[i][i];
sum_col += Msquare[n - i - 1][i];
}
if (sum_row == sum_col)
s = sum_row;
else
return false;
for (i = 0; i < n; i++) {
sum_row = 0;
for (j = 0; j < n; j++) {
sum_row += Msquare[i][j];
}
if (sum_row != s )
return false;
}
for (i = 0; i < n; i++) {
sum_col = 0;
for (j = 0; j < n; j++) {
sum_col += Msquare[j][i];
}
if (sum_col != s)
return false;
}
generateMagicSquare()
这个函数体现了一种实现幻方的方法。
首先初始化,生成一个空矩阵,元素第一个数组定位在Row=0,Col=n/2处。
然后通过n*n次循环填充上述矩阵,将矩阵的[row, col]位置填充为i,然后i++,随后使Row–,Col++,但是要注意不能使数组越界,因此添加了很多条件分支进行控制。
最终可以将填好的幻方打印出来,或者通过printwritter将其打印到全新的文件中利用上一问的函数来检验是否为幻方。
上述函数的流程图如下图所示:
如果输入的 n 为偶数,函数运行之后在控制台产生以下输出:
Exception in thread “main” java.lang.ArrayIndexOutOfBoundsException: 12
at MagicSquare.generateMagicSquare(MagicSquare.java:17)
at MagicSquare.main(MagicSquare.java:121)
这是因为输入偶数的话,上述的算法会失灵,存在数组越界的情况,抛出异常。
如果输入的 n 为负数,函数运行之后在控制台产生以下输出:
Exception in thread “main” java.lang.NegativeArraySizeException
at MagicSquare.generateMagicSquare(MagicSquare.java:11)
at MagicSquare.main(MagicSquare.java:121)
这是因为正常情况下数组下标不可能出现负数,因此程序抛出异常。
程序经过改写后,可以实现上述功能,并且将新生成的幻方写入了6.txt调用之前的isLegalMagicSquare()方法来检验是否为幻方;并且如果输入不合法,会返回False,“优雅”地退出。
Turtle Graphics
此题旨于根据提示一步一步实现基本功能,逐层深入,通过完成一些比较简单的方法,逐渐添加功能,最后得到一个较为复杂的方法。
Problem 1: Clone and import
(如何从GitHub获取该任务的代码、在本地创建git仓库、使用git管理本地开发。)
在GitHub上可以获取完整的P2文件夹,下载zip文件解压到当前IDEA工作文件夹,注意这个时候需要修改每个包的引用(因为路径发生了变化);在本地创建仓库非常简单,找到父文件夹,打开Git bash并输入git init指令即可完成初始化。接下来就可以利用add、commit等指令来管理文件;也可以通过连接GitHub上的远程仓库将master push到remote端。
Problem 3: Turtle graphics and drawSquare
乌龟画板的程序比较好理解,首先应该查看turtle类,看一看它为我们提供了哪些方法,例如:forward、turn、color等。基于此,绘制一个Square只需要一个循环,每次先前进side长度,在turn 90°,循环四次即可。
Problem 5: Drawing polygons
该问题要求绘制正n边型,那么我们通过数学公式可以推导出正N边型的内角,该角的补角即为每次turn的角度。
Problem 6: Calculating Bearings
double angle = Math.toDegrees(Math.atan2(targetY-currentY,targetX-currentX ));
计算每次方位改变所需要的夹角,这里我们选择math.atan2函数,通过角度的转化可以确定最终的Bearings。这样我们可以得到每两个点之间的角度的改变,得到方法calculateBearingToPoint;而要求我们做的Calculating Bearings给出了一系列的点,我们只需要依次调用上述的函数即可计算出这个关于角度的序列。
public static List<Double> calculateBearings(List<Integer> xCoords, List<Integer> yCoords) {
List<Double> angles = new ArrayList<>();
double currentBearing = 0.0;
int currentX = xCoords.get(0), currentY = yCoords.get(0), targetX, targetY;
int length = xCoords.size();
for (int i = 1; i < length; i++) {
targetX = xCoords.get(i);
targetY = yCoords.get(i);
angles.add(calculateBearingToPoint(currentBearing, currentX, currentY, targetX, targetY));
currentBearing = angles.get(i - 1);
currentX = targetX;
currentY = targetY;
}
return angles;
}
注意对点的初始化。
Problem 7: Convex Hulls
最小凸包问题
查阅资料,常见的解决最小凸包算法有两种卷包裹法和Graham-Scan算法。在这里选取了比较简单的卷包裹算法。
以上述图为例,假设1为起点,那么下一个点应该是距离该点极角最小的点,不难看出,这个点应当是5;如果距离该点的最小极角有多个点,那么应当取距离该点距离最短的(因为我们求的是最小凸包)。以此类推,直到最后回到了初始的点为止。这样凸包内的点就可以按顺序放在一个hashset中。
第一个点可以通过遍历选择最左下角的那个点。在这里利用向量的点乘来计算cos值,最终转化为目标的极角(或者用math.atan2)。再用迭代器进行迭代即可。
Problem 8: Personal art
这个部分考察自己的想象力,可以充分利用上述实现的方法,主要利用循环等方法设计自己独特的艺术画作。
Submitting
(如何通过Git提交当前版本到GitHub上你的Lab1仓库。)
提交过程比较简单,在项目文件夹中打开Gitbash,输入git push origin master即可。
Social Network
分析问题,要我们构建一个社交网络。
我们可以分两步解决:
-
定义Person类
在这个过程中,主要面对对象设计数据结构和基本方法,比如存储person的name,存储人的朋友的list——FriendshipList;此外例如AddFriend,getName,getFriendshipList等基本方法。
-
定义FriendshipGraph类
在这个类中我们将利用Person类来实现具体功能。
把问题分解为三个方法来实现,addVertex,addEdge,getDistance。
设计/实现FriendshipGraph类
把问题分解为三个方法来实现,addVertex,addEdge,getDistance。
-
addVertex
我们定义List<Person> people =new ArrayList()<>;来存储person的集合,在这里定义一个HashSet,用于除重。
public void addVertex(Person p){
if(nameSet.contains(p.getName())){
System.out.println(“This person has already existed.”);
return;
}
people.add§;
nameSet.add(p.getName());
}
-
addEdge
添加边的操作非常的简单,因为在Person类中已经定义了AddFriend,因此只需要1条语句即可:
//注意在这里的未来的数据结构将使用邻接表进行BFS,这里的是无向图的情况,因此要考虑加两条边
p1.AddFriend(p2);
-
getDistance
实际上我们此时图的存储结构为一个邻接表,那么我们利用广度优先搜索的策略即可计算最小距离。
public int getDistance(Person p1, Person p2){
if(p1 == p2)
return 0;
Map<Person,Integer> distance = new HashMap<>();//将图抽象为Hashmap
//如果这两个点是连通的,那么通过先广搜索可以确定最短路径。
Queue<Person> queue = new LinkedList<>();
queue.offer(p1);
distance.put(p1, 0);
while(!queue.isEmpty()){
Person t = queue.poll();
List<Person>FriendList = t.getFriendshipList();
for(Person e : FriendList){
if(!distance.containsKey(e)){
queue.offer(e);
distance.put(e, distance.get(t)+1);
if(e==p2) return distance.get(p2);
}
}
}
return -1;//广搜失败,未联通,返回-1;
}
设计/实现Person类
Person类只需要定义name和FriendshipList两个最基本的变量,剩下的是一些基本的方法。
public class Person {
private String name;
private List<Person> FriendshipList;
public Person(String name){
this.name = name;
FriendshipList = new ArrayList<>();
}
public void AddFriend(Person p){
FriendshipList.add§;
}
public String getName(){
return this.name;
}
public List<Person> getFriendshipList(){
return FriendshipList;
}
}
设计/实现客户端代码main()
Main()在实验指导书中已经给出,只需复制并进行测试即可:
1. FriendshipGraph graph = new FriendshipGraph();
2. Person rachel = new Person(“Rachel”);
3. graph.addVertex(rachel);
4. graph.addEdge(rachel, ross);
5. System.out.println(graph.getDistance(rachel, ross));
用户需要首先定义一个FriendshipGraph,在通过Person定义人,通过方法addVertex将其纳入图中,在通过addEdge来描述人与人之间的关系。
可以看到此时我们的输出与测试样例结果一致。
下面回答实验PPT中的几个问题:
1)如果注释掉第10行的话,新的关系图如下所示:
Rachel与任何人都不相邻,返回-1;且与自己的距离为0。
经过测试,发现程序的输出与上述猜想一致:
-
如果将第 3 行引号中的“Ross”替换为“Rachel”,你的程序会发生什么?
下面进行了一下测试,发现我的程序输出仍为1、2、0、-1;
下面对程序的健壮性进行修改,即用一个HashSet来对上述已经存在的Person类进行查重,一旦发现现在的Graph中已经存在了,将立即报错终止程序,效果如下:
可以看到我们的程序正常终止了,程序的健壮性得到了提高。
设计/实现测试用例
给出你的设计和实现思路/过程/结果。
- 对addVertix()的测试
在这里主要是测试加点的功能,第一个测试是否能加入,第二个测试重复的点加入后,是否会输出FALSE而使程序正常终止。
-
对addEdge()的测试
主要测试加入边后,原有的person.FriendshipList中是否加入了新的Person,即检验邻接表的正确性。
-
对getDistance()的测试
在这里我构建了一张有环的非连通图,这样各个情况都会被覆盖。
最终可以看到,我们的程序通过了上述进行的一系列的测试。
实验进度记录
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 | 时间段 | 任务 | 实际完成情况 |
---|---|---|---|
2022-04-27 | 12:30-14:30 | 编写问题1的isLegalMagicSquare函数并进行测试 | 按计划完成 |
2022-04-28 | 14:00-16:30 | 编写了问题2的主体部分 | 遇到困难,未完成,凸包算法未能完成 |
2022-04-28 | 17:30-18:15 | 编写了问题2的凸包算法 | 按计划完成 |
2022-04-28 | 18:30-20:00 | 编写了问题3 | 按计划完成 |
2022-04-30 | 13:00-15:00 | 优化了全部的程序,并编写测试 | 按计划完成 |
实验过程中遇到的困难与解决途径
遇到的困难 | 解决途径 |
---|---|
JDK的版本不符合要求 | 重新下载JDK11 ,发现IDEA可以切换不同版本的JDK,十分的方便。 |
对于Java的语法掌握的不够熟练,时常想好了做什么,但是找不到合适的对象、方法去实现自己的想法。 | 查阅CSDN和Java官方文档,不断地积累方法,总结编程经验。 |
对于IDEA的使用不够熟练,文件夹的设置不够熟练 | 上网查阅相关的使用方法,并不断地在编程的过程中使用,使得自己与IDEA磨合的更好 |
实验过程中收获的经验、教训、感想
实验过程中收获的经验和教训(必答)
在实验的过程中,首先感受到了Java面对对象编程的特点,最大的感受是,一定要清晰地想好如何解决问题,在动手编程,这样通过事先地抽象分析可以使我们的编程逻辑更加简单清晰,节约不少的时间;此外,程序的健壮性十分的重要,一个看似完美的程序实际上存在很多的漏洞,通过Junit单元测试进行“边测试边编程”的方式写出来的程序,往往具备更好的健壮性,而且便于debug查看问题的来源。
最大的教训是对Java的类库等语法的掌握非常不熟练,在实现的过程中因为语法受到了不小的阻力,希望有时间可以熟读Java核心技术,对语法有更加全面的理解。
针对以下方面的感受(必答)
-
Java编程语言是否对你的口味?与你熟悉的其他编程语言相比,Java有何优势和不足?
个人比较喜欢Java的风格。
优点:跨平台、可移植性,安全性,面向对象,简单性,高性能,分布式,多线程,健壮性。
缺点:运行速度较慢,不能和底层、操作系统等打交道,删除了指针、不够灵活。
-
关于Eclipse或IntelliJ IDEA,它们作为IDE的优势和不足;
我使用的是IDEA,我觉得IDEA的功能比较完善,比如refactor、单元测试等方法是非常便捷的,并且也会提示你是否用Git管理等等;
缺点是,IDEA 很多强大的功能都是基于其缓存与索引,当打开一个新项目的时候,IDEA 会自动建立索引。这个有时候对大型项目特别不友好,可能会出现卡顿现象。特别对于机械硬盘用户,这种现象会更加明显。
-
关于Git和GitHub,是否感受到了它在版本控制方面的价值;
Git的版本控制使我们的工作更加快捷,比如版本回退、时空穿梭等等;
GitHub让我们的代码开源,更加便于程序员的开发和对代码的管理。
-
关于CMU和MIT的作业,你有何感受;
更加喜欢MIT的作业,因为他会简单的为学生搭一点框架去逐步的实现功能,完成后比较有成就感。
-
关于本实验的工作量、难度、deadline;
由于初次接触,对Java语言本身的了解不多,导致编程的过程中算法和数据结构的实现受到了阻碍,使原本不多的工作量变得繁杂(因为要去搜索相关的文档),但是好在deadline是比较合理的。
-
关于初接触“软件构造”课程;
首先,是对Java语言的使用非常蹩脚;
其次,没有经验,对一些测试方法、框架理解的程度远远不够。
希望能够通过努力深入地学习这门课程。
于其缓存与索引,当打开一个新项目的时候,IDEA 会自动建立索引。这个有时候对大型项目特别不友好,可能会出现卡顿现象。特别对于机械硬盘用户,这种现象会更加明显。
-
关于Git和GitHub,是否感受到了它在版本控制方面的价值;
Git的版本控制使我们的工作更加快捷,比如版本回退、时空穿梭等等;
GitHub让我们的代码开源,更加便于程序员的开发和对代码的管理。
-
关于CMU和MIT的作业,你有何感受;
更加喜欢MIT的作业,因为他会简单的为学生搭一点框架去逐步的实现功能,完成后比较有成就感。
-
关于本实验的工作量、难度、deadline;
由于初次接触,对Java语言本身的了解不多,导致编程的过程中算法和数据结构的实现受到了阻碍,使原本不多的工作量变得繁杂(因为要去搜索相关的文档),但是好在deadline是比较合理的。
-
关于初接触“软件构造”课程;
首先,是对Java语言的使用非常蹩脚;
其次,没有经验,对一些测试方法、框架理解的程度远远不够。
希望能够通过努力深入地学习这门课程。