HIT 2020年春季学期
计算机学院《软件构造》课程
**Lab 1实验报告
目录
实验目标概述
本次实验通过求解三个问题,训练基本Java 编程技能,能够利用Java 开
发基本的功能模块,能够阅读理解已有代码框架并根据功能需求补全代码,能够
为所开发的代码编写基本的测试程序并完成测试,初步保证所开发代码的正确性。
另一方面,利用Git 作为代码配置管理的工具,学会Git 的基本使用方法。
-
基本的Java 编程
-
基于Eclipse IDE 进行Java 编程
-
基于JUnit 的测试
-
基于Git 的代码配置管理。
实验环境配置
实验电脑:windows64位,8G内存,1T固态内存
Java环境配置:下载安装jdk 8,并且配置JAVA_HOME环境变量截图如下:
命令行验证环境如下:
实验ide使用:由于实验中需要用到junit和git,但是可供选择的eclipse中继承这二者需要较多操作,因此我在安装eclipse后,采用idea方式进行编程,可以在内部集成git,直接右键push即可,如下图:
遇到的问题:在去年夏季学期中曾经安装过java环境,因此本次实验没有遇到与此相关的困难。利用idea集成git后提交时候,经常出现提交不上去,超时的情况,即使科学上网也并不稳定。后来经过查阅资料得知,可以在提交时候idea也有强制提交(-f)指令,利用这个指令就可以解决。
实验过程
请仔细对照实验手册,针对四个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但无需把你的源代码全部粘贴过来!)。
为了条理清晰,可根据需要在各节增加三级标题。
Magic Squares
在任务首先给出了幻方的定义,然后要求编程判断是否时幻方,并且对于不是幻方的情况指出是什么原因。用
抛出异常加异常处理的方式处理。最后还利用已有函数生成幻方,并用自己编写的函数判断。考察的是文件读写操作与java中基本操作的使用。
isLegalMagicSquare()
- 首先创建文件流,用于读取文件:利用FileReader和BufferedReader,以及readline函数,实现读取文件中一行内容。
-
对于读取到的内容进行处理:数字之间用”\t”间隔,所以使用了split函数。读取到的内容存放在String类型数组中。为了便于后续判断是否是幻方。我们记录下读取的总行数row,最大列数max_col,最小列数min_col。这是因为可能出现行列不相等,或者中间有的位置没有数字,导致最小的列数不等于最大列数。
-
对于异常情况进行判断,其中包括,含有的不是正整数。这个我创建了isnumber函数,用正则表达式筛选,如下:
此外,针对最小,最大列数不相等,说明有空数据;列数不等于行数,说明行列数量不相等都做出了判断。如果不是用”\t”分割,那么分割后的结果在判断时候也有问题,也可以判断出来。对于普通的求和之后不相等,利用数组matrix存储了每次读到的数值(这里用到了字符串转int类型),然后处理matrix数组即可。
-
对于正确的幻方,输出“这是幻方“
-
最终代码如下:
public static boolean isLegalMagicSquare(String fileName){
try {
FileReader reader1 = new FileReader(fileName);
BufferedReader br1 = new BufferedReader(reader1);
String myline;
int row = 0;//记录行数量
int max_col = 0;
int min_col = 0;
int flag = 0;//初始化标志
myline = br1.readLine();
String[] get_col = myline.split("\t");
int size = get_col.length;
int [][] matrix = new int[size][size];//存储数据
br1.close();
reader1.close();
FileReader reader = new FileReader(fileName);
BufferedReader br = new BufferedReader(reader);
while((myline = br.readLine())!=null){
String[] num = myline.split("\t");
int i = num.length;
row += 1;//读取一行
max_col = Math.max(i, max_col);
if(flag == 0 ){
min_col = i;
flag = 1;
}
min_col = Math.min(i, min_col);
for(int temp = 0; temp < i; temp++){
if(isNumber(num[temp])==false){
System.out.println("含有非正整数!");
return false;
}
}
for(int k = 0;k<i;k++){
matrix[row-1][k] = Integer.parseInt(num[k]);
// System.out.println(matrix[row-1][k]);
}
}
if(row != max_col){
System.out.println("不是幻方,行列数量不相等");
return false;
}
if(min_col != max_col){
System.out.println("不是幻方,含有空数据");
return false;
}
//验证幻方
int[] row_sum = new int[max_col];
int[] col_sum = new int[max_col];
int left_cross_sum = 0;
int right_cross_sum = 0;
for(int i = 0; i < row ; i++){
for(int j = 0;j<max_col;j++){
row_sum[i] += matrix[i][j];//第i行的和
}
if(i >=1 ){
if(row_sum[i-1]!=row_sum[i]){
System.out.println("行和数值不满足要求!");
return false;
}
}
}
for(int j = 0;j<max_col;j++){
for(int i = 0; i < row ; i++){
col_sum[j] += matrix[j][i];//第j列的和
}
if(j >=1 ){
if(col_sum[j-1]!=col_sum[j]){
System.out.println("列和数值不满足要求!");
return false;
}
}
}
for(int i = 0;i<row;i++){
left_cross_sum += matrix[i][i];//左对角线之和
}
for(int i = 0;i<row;i++){
right_cross_sum += matrix[i][max_col-i-1];//左对角线之和
}
if(left_cross_sum!=right_cross_sum){
System.out.println("左右对角线之和不相等");
return false;
}
}
catch(IOException e1){
System.out.println(e1);
}
catch(ArrayIndexOutOfBoundsException e2){
System.out.println("不是幻方,行列数量不相等");
return false;
}
catch(NegativeArraySizeException e2){
System.out.println("不是合理的矩阵!不是幻方!");
}
System.out.println("这是幻方!");
return true;
}
}
generateMagicSquare()
该模块要求利用已经给出的函数进行改造,针对输入的n为偶数,或者为负数,抛出异常,并且进行异常处理。用try
catch语句即可实现。最后将文件输入到6.txt中,用文件流操作创建文件即可。
- 针对n是偶数:这部分要求抛出的异常是数组越界。经过实验,输入偶数确实会导致这个异常,但是可以进行预处理,避免进行不必要的测试。在函数开头部分加上了如下的try:
try{
//偶数
int[] a = {1, 2};
if (n%2 == 0){
System.out.println(a[3]);
}
if(n < 0){
System.out.println(a[n]);
}
}
这就实现了n的两种情况的异常抛出,然后用catch处理即可:
- 正常调用函数,输出到文件:利用File writeName = new
File(地址),然后利用这一对象,创建文件,文件写入流,以及缓冲写入流。
File writeName = new File("./src/P1/txt/6.txt");
try{
writeName.createNewFile();
FileWriter fw = new FileWriter(writeName);
BufferedWriter bw = new BufferedWriter(fw);
for(int s = 0;s<n;s++){
for(int k = 0;k<n;k++){
bw.write(magic[s][k]+"\t");
}
bw.write("\n");
}
bw.flush();
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
写入时注意也要用\\t分割输入的数字,并且每行输入结束要输入\\n换行。
Turtle Graphics
考察利用现有的函数,如何去组合创造出我们想要的图案的能力。已经给出了基本的转向,前进等函数,利用这些实现例如画正方形,多边形等。同时,考察了数学思维和编程结合的思想:如何利用已有公式,快速获得正确结果。同时,要注意运算时对于浮点数的精度损失问题。
Problem 1: Clone and import
首先安装git,并且将git集成到本地的idea中,一键管理:
用git实现版本控制:首先在科学上网的情况下,在idea中登录github,并且授权登录。
然后新建项目时,选择建立版本控制的文件,如下图:
选择对应的仓库后,仓库上的代码就可以直接下载到idea中,并且进行编写。
利用普通方法:在利用github 给出的地址,点击clone,把代码保存到本地的github
desktop里
或者在网页版,找到对应的链接,在git
bash中运行下载到本地。个人使用的是第一种,比较方便。
下载后,注意我们放在了P2中,但是源文件中的代码,直接是turtle
的package,所以需要修改为package
P2.turtle,余下的一些库等等也同理修改。文件中的test文件,需要重构到test文件夹的P2目录下。这里我在idea中利用junit插件,首先创立文件夹test,并且指定为测试源,然后一键生成即可。如下图:
点击junit test,选择junit 4,就有如下文件产生:
然后把原有的TurtleSoupTest中的内容拖过来即可。
Problem 3: Turtle graphics and drawSquare
public static void drawSquare(Turtle turtle, int sideLength) {
for(int i = 0;i<4;i++){
turtle.forward(sideLength);
turtle.turn(90);
}
}
编写循环,一共四次,每次走定长,然后右转90度即可。
Problem 5: Drawing polygons
-
首先根据边数算出每个内角的度数,在转弯时调用这个函数。经过查阅资料,多边形内角和是angle
180*(边数-2).因此对于正多边形,每个角的度数就是n分之一倍的angle。不需要别的操作,直接return即可。注意,结果是double类型,这里保留两位小数,要用180.00
public static double calculateRegularPolygonAngle(int sides) {
return (180.00)*(sides-2)/sides;
}
-
根据内角度数,计算多边形边长。这里利用上一步的公式,推导出角度和边长的关系,是:sides
360/(180-angle)。注意,浮点数angle除法得到的数字需要进行舍入,所以要对于是加一还是减一做出判断,而不是简单的直接转换成int。
当结果是int类型,标志是对1取余等于0,就直接转成int输出;
当结果是浮点数,那么首先取出浮点数的前两位(这是上一个函数的要求,便于计算比较):
DecimalFormat df = new DecimalFormat("########.00");
然后分别对取整数后的sides,加一或者减一,带入上一步的函数中计算。如果恰好相等,就直接返回。如果都不行,那么就返回直接取整的本身(因为一共只有这三种可能)
public static int calculatePolygonSidesFromAngle(double angle) {
double temp = 360/(180-angle);
int t = (int) temp;
//temp是浮点
if(temp%1 != 0){
//限制小数点两位
DecimalFormat df = new DecimalFormat("########.00");
double add_one = (180.00)*(t-1)/(t+1);
add_one = Double.parseDouble(df.format(add_one));
if(add_one == angle) return t+1;
double minus_one = (180.00)*(t-3)/(t-1);
minus_one = Double.parseDouble(df.format(minus_one));
if(minus_one == angle) return t-1;
}
return (int)temp;
}
- 绘制正多边形。这里没看到要求是凸多边形或者可以是凹多边形。为了后面自定义画图时候好看点,采用了凸多边形。这里注意,要调整角度(前两行),否则会向内凹。
public static void drawRegularPolygon(Turtle turtle, int sides, int sideLength) {
double angle=180-calculateRegularPolygonAngle(sides);
turtle.turn(270.0+angle);
for(int i = 0;i<sides;i++){
turtle.turn(angle);
turtle.forward(sideLength);
}
}
Problem 6: Calculating Bearings
- 首先编写函数,求出从当前角度,沿当前点出发,到目标点的朝向角度应该转多大。注意这个数如果是负数,就要加上360;如果超过360,就要取余。计算公式如下:
return (360-currentBearing+angel)%360;
注意这里用到了atan2函数,正切值当x坐标是0,趋于无穷,这个我们要单独处理,如下:
double angel = Math.toDegrees(Math.atan2(width,high));
if(width == 0){
if(high>0){
//目标点在上方,转270度
angel = 0;
}
if(high == 0) angel =0;
if(high <0) angel = 180;
}
- 对于给出的点的列表,给出每次行动转动的角度,并返回列表。对于点列表中的每两个挨着的点,调用函数求出转角,并add到list中即可。
public static List<Double> calculateBearings(List<Integer> xCoords, List<Integer> yCoords) {
List<Double> angel = new ArrayList<Double>();
int x_size = xCoords.size();
angel.add(0,calculateBearingToPoint(0,xCoords.get(0),yCoords.get(0),xCoords.get(1),yCoords.get(1)));
for(int i = 2;i<x_size;i++){
angel.add(i-1,calculateBearingToPoint(angel.get(i-2),xCoords.get(i-1),yCoords.get(i-1),xCoords.get(i),yCoords.get(i)));
}
return angel;
}
Problem 7: Convex Hulls
凸包算法,没啥说的,都是火炬,查阅资料编写如下。
public static Set<Point> convexHull(Set<Point> points) {
try{
if(points.size()<3) return points;
Set<Point>Points = new HashSet<Point>();
Point[] P = points.toArray(new Point[points.size()]);
boolean[] exist = new boolean[points.size()];
int i,startpoint = 0,nextpoint, endpoint;
double minX = P[0].x();
for(i =1;i<P.length;i++){
if(P[i].x()<minX||(P[i].x()==minX&&P[i].y()<P[startpoint].y())){
minX = P[i].x();
startpoint = i;
}
}
endpoint = startpoint;
double currentBearing = 0.0;
do{
double minangle = 360.0;
nextpoint = startpoint;
for(i = 0;i<P.length;i++){
if(i != startpoint){
double angle = calculateBearingToPoint(currentBearing,(int)P[startpoint].x(),(int)P[startpoint].y(),(int)P[i].x(),(int)P[i].y());
if(!exist[i]){
if(angle<minangle){
minangle = angle;
nextpoint = i;
}
else if(angle == minangle){
double dis1 = Math.pow(P[i].x()-P[startpoint].x(),2.0)+Math.pow(P[i].y()-P[startpoint].y(),2.0);
double dis2 = Math.pow(P[nextpoint].x()-P[startpoint].x(),2.0)+Math.pow(P[nextpoint].y()-P[startpoint].y(),2.0);
if(dis1 > dis2){
nextpoint = i;
}
}
}
}
}
currentBearing = (currentBearing+minangle)%360;
startpoint = nextpoint;
exist[nextpoint] = true;
}
while(endpoint !=startpoint);
exist[endpoint] = true;
for(i = 0;i<P.length;i++){
if(exist[i]){
Points.add(P[i]);
}
}
return Points;
}
catch (Exception e){
throw new RuntimeException("implement me!");
}
}
Problem 8: Personal art
这个大家自己花花就行!
随便写几个循环,正方形,圆形,正多边形,来回画
里面有color,可以用6种不同颜色绘制,有正多边形和自己编写的圆形,并且设计了合理的步长,代码如下:
public static void drawPersonalArt(Turtle turtle) {
turtle.color(PenColor.PINK);
drawSquare(turtle,20);
for(int i = 1;i<=20;i++){
drawCircle(turtle,4);
drawCircle(turtle,4);
drawSquare(turtle,20);
turtle.turn(calculateRegularPolygonAngle(20));
drawRegularPolygon(turtle,10,30);
drawRegularPolygon(turtle,10,30);
if(i%6 ==1){
turtle.color(PenColor.PINK);
}
if(i%6 ==2){
turtle.color(PenColor.YELLOW);
}if(i%6 ==3){
turtle.color(PenColor.RED);
}if(i%6 ==4){
turtle.color(PenColor.GREEN);
}if(i%6 ==5){
turtle.color(PenColor.ORANGE);
}if(i%6 ==6){
turtle.color(PenColor.MAGENTA);
}
}
}
最后化成了这个样子:
Submitting
在待提交的文件右键,选中git,推送
Social Network
刻画一个社交网络表示出人与人之间的关系。注意要能够拓展到有向图,人与人之间的关系是相互的。注意在处理的过程中遇到异常情况要提醒并且退出。比如同时出现一样的名字等。
设计/实现FriendshipGraph类
需要两个列表,一个用来存储Person类的对象,一个用来记录都加入了谁,避免重复加点。
-
AddEdge的设计:对于两个加关系的人people1 和people2
,分别调用Person类的add_friend方法,将对方加到自己的朋友list中即可。 -
addVertex:注意加点时判断namelist中是否已经有该名字,用list的contains方法判断,如果重复了就提示,并退出代码设置为0.
public int addVertex(Person new_Person){
if(new_Person.getName() == ""){
System.out.println("名字未设置!");
return 1;
}
if(nameList.contains(new_Person.getName())){
System.out.println("名字重复");
return 2;
}else{
nameList.add(new_Person.getName());
}
people.add(new_Person);
return 0;
}
- getdiatance方法:利用广度优先搜索,采用队列的数据结构实现。
public int getDistance(Person Person1, Person Person2){
if(Person1 == Person2){
// System.out.println("They are the same guys!");
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);
List<Person> friend_List = topPerson.get_Friend_List();
for(Person ps: friend_List){
if(!distantMap.containsKey(ps)){
distantMap.put(ps, nowDis+1);
queue.offer(ps);
if(ps == Person2){
return distantMap.get(Person2);
}
}
}
}
return -1;
}
设计/实现Person类
Person类中有两个属性:人名String
name和朋友列表friend_list。注意在初始化时候需要输入人名,因此Person类需要构造器。
还有getName方法,便于查询人名;在设计getdistance方法中,需要找到队列中队首的所有朋友,因此设计了get_friendlist,找到一个对象的所有朋友。
最后还有add_friend方法,用于向对象的朋友列表属性中添加朋友。
设计/实现客户端代码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));
}
}
设计/实现测试用例
测试add_Edge,可能出现声明了人之后,没加入到图中的情况
@Test
public void addEdge() {
FriendshipGraph graph = new FriendshipGraph();
Person p1 = new Person("p1");
Person p2 = new Person("p2");
Person p3 = new Person("p3");
graph.addVertex(p1);
graph.addVertex(p2);
//p3没加入到图中
assertEquals(1, graph.addEdge(p2,p3));
assertEquals(0, graph.addEdge(p2,p1));
}
测试add_Vertex: 如果名字重复,return 2,如果名字为空,说明未输入,return 1;正常返回0
if(new_Person.getName() == ""){
System.out.println("名字未设置!");
return 1;
}
if(nameList.contains(new_Person.getName())){
System.out.println("名字重复");
return 2;
测试getdistance:自己加入点和边测试即可。自己到自己,一个点到不存在的点,以及经过多步长的距离。
Main的测试:
public void main() {
FriendshipGraph graph1 = new FriendshipGraph();
Person a = new Person("a");
Person b = new Person("b");
Person c = new Person("c");
Person d = new Person("d");
graph1.addVertex(a);
graph1.addVertex(b);
graph1.addVertex(c);
graph1.addVertex(d);
graph1.addEdge(a,b);
graph1.addEdge(b,a);
graph1.addEdge(c,b);
graph1.addEdge(b,c);
graph1.addEdge(b,d);
graph1.addEdge(d,b);
graph1.addEdge(c,d);
assertEquals("expected distance",1,graph1.getDistance(a,b));
assertEquals("expected distance",1,graph1.getDistance(b,c));
assertEquals("expected distance",1,graph1.getDistance(c,d));
assertEquals("expected distance",2,graph1.getDistance(a,c));
assertEquals("expected distance",1,graph1.getDistance(b,d));
assertEquals("expected distance",2,graph1.getDistance(a,d));
}
实验进度记录
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 | 时间段 | 任务 | 实际完成情况 |
---|---|---|---|
2021-5-13 | 17:04 | 从github classroom导入项目 | 按计划完成 |
2021-5-16 | 21:00-00:40 | 编写完成任务1 | 按计划完成 |
2021-5-21 | 19:00-0:09 | 编写完成任务2,并且一起push,修改GitHub上的文件目录结构 | 遇到困难,按计划完成 |
2021-5-22 | 10:00-22:00 | 完成任务3,写报告 | 按计划完成 |
2021-5-23 | 09:00-12:54 | 写完报告,push | 按计划完成 |
实验过程中遇到的困难与解决途径
遇到的难点 | 解决途径 |
---|---|
集成git与junit | 查阅csdn,,购买科学上网工具,自己动手解决 |
提交失败处理 | 询问同学,学会强制提交 |
Java的数据类型,类的构建方式,构造器不熟悉 | 看教程,看网课,问同学,自己动手试着更改 |
实验过程中收获的经验、教训、感想
实验过程中收获的经验和教训
多看文档,积累debug经验,任务要及时提交,不能拖延。有问题积极讨论。
针对以下方面的感受
-
Java编程语言是否对你的口味?
对口,很舒服
-
关于Eclipse IDE
不友好,功能很多,但是不熟练的难以上手,导入包之类的比较方便,但是下载包和插件太慢,不如idea。并且没自己嵌入junit和git,手动配置耽误时间。Eclipse下载安装也需要java环境。
-
关于Git和GitHub
不错的平台,用了很久了
Git命令现在可以用插件集成了,不用手动书写;新建分支等操作可以在idea中直接实现。
-
关于CMU和MIT的作业
不错,很有意思
-
关于本实验的工作量、难度、deadline
工作量适中,难度可以,deadline也算可以
-
关于初接触“软件构造”课程
动手敲代码很爽,谢谢。
延。有问题积极讨论。
针对以下方面的感受
-
Java编程语言是否对你的口味?
对口,很舒服
-
关于Eclipse IDE
不友好,功能很多,但是不熟练的难以上手,导入包之类的比较方便,但是下载包和插件太慢,不如idea。并且没自己嵌入junit和git,手动配置耽误时间。Eclipse下载安装也需要java环境。
-
关于Git和GitHub
不错的平台,用了很久了
Git命令现在可以用插件集成了,不用手动书写;新建分支等操作可以在idea中直接实现。
-
关于CMU和MIT的作业
不错,很有意思
-
关于本实验的工作量、难度、deadline
工作量适中,难度可以,deadline也算可以(?气抖冷)
-
关于初接触“软件构造”课程
动手敲代码很爽,谢(wo)谢(gan)。