目录
2.2.1Problem 6:Calculating Bearings
一、实验目标概述
本次实验通过求解三个问题,训练基本Java编程技能,能够利用Java OO开发基本的功能模块,能够阅读理解已有代码框架并根据功能需求补全代码,能够为所开发的代码编写基本的测试程序并完成测试,初步保证所开发代码的正确性。
另一方面,利用Git作为代码配置管理的工具,学会Git的基本使用方法。
二、实验内容
2.1Magic Squares
编写程序判断给出的5个文件中的数字序列是否为幻方,返回true或false。在遇到错误时要判断错误类型,给出错误提示,并返回false。可能出现的错误有数字之间没有用“\t”分隔,数字序列不是矩阵,数字序列是矩阵但行列数不同,数字不为正整数,数字的行之和与列之和和对角线之和不相等。
2.1.1isLegalMagicSquare()函数
首先,使用字符流从文件中按行读入数字序列,将读入的内容以“\t”分隔,分成若干个字符串,存入字符串数组中,将所有内容全部读入完并且分割好以后存入二维数组中。
然后将每个字符串转换成单精度浮点数,判断结果是否大于0,如果小于等于0,则给出错误提示“不是正数”,同时返回false。然后将单精度浮点数转换为整数再转换回单精度浮点数,判断结果与转换前是否相等,如果不相等,则给出错误提示“不是整数”,同时返回false。然后如果上述条件都不满足,那么再将每个数字的字符串转换成整数,存入另一个int类型的二维数组中。在上述过程中如果抛出了异常,那么给出错误提示“数字间不是以\t分隔”,返回false。
之后再判断每行的数字数量是否都相等,如果不相等,给出错误提示“输入不是矩阵”,返回false。如果是矩阵,那么再判断行数与列数是否相等,如果不相等,给出错误提示“矩阵行数与列数不相等”,返回false。
最后就是将每行、每列、两个对角线上的数字加和,判断这些和是否全部相等,如果有不相等的,则返回false。
如果经过上述所有判断都没能结束函数,那么说明这个矩阵是一个幻方,返回true。
这个函数源代码如下:
static boolean isLegalMagicSquare(String fileName) {
File f=new File("src\\P1\\txt\\"+fileName);//相对路径,如果没有前面的src,就在当前目录创建文件
if(!f.exists()){
System.out.println("file does not exist");
return false;
}
FileReader f1 = null;
BufferedReader f2=null;
String[][] matrix=new String[1000][];
int[][] int_matrix=new int[1000][1000];
String[] line=null;
int i=0;
int[] num=new int[1000];
try {
f1=new FileReader(f);
f2=new BufferedReader(f1);
String str=null;//定义一个字符串
while((str=f2.readLine())!=null) {
line=str.split("\t");
num[i]=line.length;
matrix[i]=line;
i++;
}
}
catch(Exception e){
}finally {//如果没有捕捉到异常,将进行下面的操作
try {
f2.close();
f1.close();
}
catch(Exception e2) {
}
}
float z;
for(int m=0;m<=i-1;m++) {
for(int n=0;n<=num[m]-1;n++) {
try {
z=Float.parseFloat(matrix[m][n]);
if(z<=0.0) {
System.out.print("number is not positive\t");
return false;
}
else if((float)((int)z)!=z) {
System.out.print("numbers are not integers\t");
return false;
}
int_matrix[m][n]=Integer.valueOf(matrix[m][n]);
}
catch(Exception e) {
System.out.print("Numbers are not separated by '\\t' ");
return false;
}
}
}
int val=num[0];
for(int m=0;m<=i-1;m++) {
if(num[m]!=val) {
System.out.print("input is not a matrix\t");
return false;
}
}
if(val!=i){
System.out.print("Matrix rows and columns are not equal\t");
return false;
}
int addition=0;
int add=0;
for(int m=0;m<=i-1;m++) {//判断每行的和是否相等
for(int n=0;n<=val-1;n++) {
add+=int_matrix[m][n];
}
if(m==0) {
addition=add;
}
else {
if(add!=addition) {
return false;
}
}
add=0;
}
for(int n=0;n<=val-1;n++) {//判断每列的和是否相等
for(int m=0;m<=i-1;m++) {
add+=int_matrix[m][n];
}
if(add!=addition) {
return false;
}
add=0;
}
for(int m=0;m<=val-1;m++) {//对角线1加和
add+=int_matrix[m][m];
}
if(add!=addition) {//判断对角线1的和是否跟之前相等
return false;
}
add=0;
for(int m=0;m<=i-1;m++) {
add+=int_matrix[m][val-m-1];
}
if(add!=addition) {
return false;
}
add=0;
return true;
}
2.1.2generateMagicSquare()函数
这个函数是已经提供出来的,此处给出函数的流程图如下:
2.2Turtle Graphics
这个任务是MIT的一项实验内容,实验的具体网址为http://web.mit.edu/6.031/www/fa18/psets/ps0/
主要是通过一步步编写代码,完成各种功能的函数,如计算多边形内角、旋转角度等,最后实现使用forward和turn函数进行个人艺术图形绘制。
下面我将对于其中一部分问题给出解释。
2.2.1Problem 6:Calculating Bearings
在calculateBearingToPoint中,使用反三角函数的方法计算从一个点顺时针旋转到另一个点的旋转角度。先使用arccos计算两个点的连线与y轴正方向的夹角,然后判断目标点的横坐标与源点的横坐标大小关系,如果目标点的横坐标更大,那么不发生改变;如果源点的横坐标更大,那么刚才计算的夹角改为360减原来的角度。最后使用这个角度减去乌龟在源点面朝的方向与y轴正方向之间的夹角,如果差值小于零,那么加上360度,否则结果值不变。
完成上一个函数之后,再在calculateBearings函数中连续计算多个点之间转换需要旋转的角度,这一部分就是对于calculateBearingToPoint函数的应用,并没有什么技巧。
calculateBearingToPoint函数的源代码如下:
public static double calculateBearingToPoint(double currentBearing, int currentX, int currentY,int targetX, int targetY) {
double vectorX = targetX - currentX;
double vectorY = targetY - currentY;
double degree = Math.toDegrees(Math.acos( vectorY / Math.sqrt(vectorX * vectorX + vectorY * vectorY))) ;
if (vectorX < 0)
degree = 360 - degree;
double cbtp = degree - currentBearing;
if (cbtp < 0)
return 360.0 + cbtp;
else
return cbtp;
}
calculateBearings函数的源代码如下:
public static List<Double> calculateBearings(List<Integer> xCoords, List<Integer> yCoords) {
int num=0;
double currentAngle=0.0,adjust=0.0;
List<Double> angle =new ArrayList<Double>();
for(Integer a:xCoords) {
num++;
}
for(int i=0;i<=num-2;i++) {
if(i==0) {
adjust=calculateBearingToPoint(0.0,xCoords.get(i),yCoords.get(i),xCoords.get(i+1),yCoords.get(i+1));
angle.add(adjust);
currentAngle=(0.0+adjust)%360.0;
}
else {
adjust=calculateBearingToPoint(currentAngle,xCoords.get(i),yCoords.get(i),xCoords.get(i+1),yCoords.get(i+1));
angle.add(adjust);
currentAngle=(currentAngle+adjust)%360.0;
}
}
return angle;
}
2.2.2Problem 7:Convex Hulls
在这个函数中,需要计算输入的点集中的最小凸集,使用了礼品包装算法,先选择所有点中最左下角的那个点,即x和y均是所有点中最小的点,将这个点加入到最小凸集中,令此时乌龟面向角度为180度,即面朝y轴负方向。然后计算从这一点顺时针旋转到其他点的旋转角度,选择旋转角度最小的那个点,将这个点加入到最小凸集中,然后保持乌龟的面朝角度不变,基于这个点再次进行上述旋转、选择最小旋转角度的操作,之后一直重复这些动作,直到某次选择中,选到的是最初的那个点,结束这一循环。
此时就已经产生了这一点集的最小凸集了,另外还要注意,当点集中点的数目小于等于三时,最小凸集就是这个点集本身。
下面是我在这个函数上遇到的困难和解决方法!!!
这个函数我在编写的过程中深受折磨,并不是我不理解算法原理,也不是算法编写错误,主要就是测试怎么都通不过,具体情况是我点集中点的数量是正确的,但是看测试反馈好像就是点不对,然而我这个算法编写绝对是没问题的,找bug找了一晚上,后来我才发现了其中的玄机。
在将点加入到最小凸集中时,要从输入的点集中直接将Point对象加入到最小凸集,不能新建一个Point对象,将需要加入的点的属性赋值给新的Point对象,否则在测试的时候,会发生错误的,因为测试时是检验两个点集中Point对象是否相同,比较的是Point对象的地址,所以如果新建的Point对象,地址就会与原来的点的地址不同,从而发生错误。
后来想明白了再看一下我当时测试的反馈,确实是点的数量对,但是反馈出来点的地址不正确,当时就完全没想到这个方向。唉,还得继续努力。
2.2.3Problem 8:Personal art
这部分就是使用上面写好的若干个函数,进行个人创作,但是不能只是简单的画个多边形,得使用稍微复杂一些的操作,尽量组合出一个漂亮的图案,从这个问题的设计确实能看出MIT对于各个方面的要求都是到位的。
2.3Social Network
这个任务就是要求构建一个社交网络图,需要人作为图的顶点,顶点之间需要存在有向边,因为这个任务要构建的是一个无向图,所以当确定两个人之间是朋友关系时,需要添加双向有向边。
这个社交网络图需要可以加入顶点、添加朋友关系、计算两个人在社交网络上的最短距离,同时还要能检测是否有重名的人,Person这个类里面至少要有名字这个属性。
2.3.1设计和实现FriendshipGraph类
在类中首先创建了Person类型的名为people的List列表,用于存储社交网络的顶点,即加入到社交网络的人。
然后在addVertex函数中,将传进来的“人”加入到顶点列表中。在这个函数中,可以判断即将加入的人与顶点列表中的人是否重名,如果重名将会直接终止程序。
在addEdge函数中,在两个Person类型的参数person1、person2之间建立从person1到person2的朋友关系,在这个函数中是调用了person1中的方法,在person1的朋友列表中加入person2。在这个函数中,会首先判断作为参数的两个人是否在顶点列表中,如果有人不在顶点列表中,则会终止函数,并且给出错误提示。
在getDistance函数中,会传递两个Person类型的参数person1、person2,获取在社交网络上从person1到person2的最短距离,这个目的即是求单源最短路径,所以使用广度优先算法,最终函数返回两个人之间的最短距离,如果是自己到自己,返回值为0,如果是两个人之间没有路径,返回值为-1。这个函数在一开始还会检查顶点列表是否为空、作为参数的两个人是否在顶点列表中,如果出错,会终止函数,给出错误提示。
下面是上面三个方法的源代码:
public void addVertex(Person person) {
for(Person person1:people) {
if(person1.getName().equals(person.getName())) {
System.out.println("Error:'"+ person.getName()+"' has been renamed!");
System.exit(0);
}
}
people.add(person);
}
public void addEdge(Person person1,Person person2) {
if(!people.contains(person1)) {
System.out.println("Error:this person does not exist!");
return ;
}
if(!people.contains(person2)) {
System.out.println("Error:this person does not exist!");
return ;
}
person1.buildFriendship(person2);
}
public int getDistance(Person person1,Person person2) {
List<Person> list=new LinkedList<Person>();//队列
if(people.isEmpty()) {
System.out.println("Error:graph is empty!");
return -1;
}
if(!people.contains(person1)) {
System.out.println("Error:this person does not exist!");
return -1;
}
if(!people.contains(person2)) {
System.out.println("Error:this person does not exist!");
return -1;
}
int num1=1;//上一层的元素数量
int num2=0;//下一层的元素数量
int distance=0;//距离
Person element;//出队的人
List<Person> record=new ArrayList<Person>();//记录出现在队列中过的人,防止重复
list.add(people.get(0));
record.add(people.get(0));
while(true) {
element=((LinkedList<Person>) list).poll();
num1--;
if(element==null) {
return -1;//从person1没有到person2的路径
}
else if(element==person2) {
return distance;
}
if(num1==0) {
distance++;
}
for(Person next:element.getOut()) {
if(!record.contains(next)) {
list.add(next);
record.add(next);
num2++;
}
}
if(num1==0) {
num1=num2;
num2=0;
}
}
}
2.3.2设计和实现Person类
Person类中的属性有名字(name)、与该人有朋友关系的出边(out),还有设置和获取name、out的值的方法。
Person类的源代码如下:
public class Person {
private String name;
private List<Person> out=new ArrayList<Person>();
public Person(String name) {
this.name=name;
}
public ArrayList<Person> getOut() {
return (ArrayList<Person>) out;
}
public void setOut(Person out) {
this.out.add(out);
}
public String getName() {
return name;
}
public void buildFriendship(Person friend) {
setOut(friend);
}
}
实验内容就到此结束啦!
三、总结和感悟
3.1此次实验的经验和教训
要善于利用java中各个封装好的函数,这样可以省下非常多的编写时间,但是想要做到充分利用函数,还是需要多加练习java,这样才会更广泛地了解在哪一部分有什么样的函数。
在使用某个对象的属性值时,最好是从对象中现取,不要单独将属性取出来赋值到另一个变量上,在进行操作时最好是以对象为单位。
在一个问题上想非常久还想不出来时,可以上网查一查,不要固执地非要自己想出来,这样自己想出来的有可能又笨拙又错误,还会浪费很多时间,得不偿失。
3.2对于MIT和CMU作业的感受
MIT的作业涉及的知识范围比较广,而且形式也非常新奇,可以说这个作业是数学、计算机、美术的融合,完成作业就像游戏闯关一样。
CMU的作业比较中规中矩,可以通过案例充分练习Java的知识点,对Java知识的学习和融会贯通有很大帮助。
3.3对于软件构造的初体验
感觉接触到了很多现实的东西,不再全部都是理论,有很多事情能与现实工作相关,渐渐地有了真正的实践操作的感觉。可能这门课是上大学以来与现实联系最紧密的一门课了吧,在这门课上也可以听到老师讲讲真正开发软件时的一些流程和事情。
都说现在的大学学的内容与工作脱节,大学学的内容老旧、工作用不上,我确实有一些感受,但是我觉得大学里那些看似实用性不太大的课程,其实就是在潜移默化地改变着我们的思维。大学不是一个单纯学技术的地方,更是一个培养思想和人格的地方,但是还是希望能够在课程中多多实践,这种亲自创造事物的成就感是非常迷人的。