3.2.1 Problem 1: Clone and import
3.2.2 Problem 3: Turtle graphics and drawSquare
3.2.3 Problem 5: Drawing polygons
本次实验通过求解三个问题,训练基本 Java 编程技能,能够利用 Java OO 开发基本的功能模块,能够阅读理解已有代码框架并根据功能需求补全代码,能够为所开发的代码编写基本的测试程序并完成测试,初步保证所开发代码的正确性。另一方面,利用 Git 作为代码配置管理的工具,学会 Git 的基本使用方法。
- 基本的 Java OO 编程
- 基于 Eclipse IDE 进行 Java 编程
- 基于 JUnit 的测试
- 基于 Git 的代码配置管理
2.实验环境配置
2.1配置JDK
首先进入官网https://www.oracle.com/java/technologies/javase-downloads.html下载对应版本的jdk(注意实验要求的是jdk8或jdk11),可以选择免安装版或安装版。下载完成后解压压缩包,然后开始配置。
右键此电脑点击属性,打开高级系统设置:
然后点击环境变量,在系统变量中新建 JAVA_HOME 变量 ,变量值填写解压后jdk文件夹所在的目录
随后在变量path最后添加%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;(注意别落下分号)
最后在系统变量中新建变量CLASSPATH,变量值填写 .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar(注意开头的.别忘了)
运行cmd 输入 java -version (java 和 -version 之间有空格),若如图所示 显示版本信息 则说明安装和配置成功。
2.2安装IDE
在官网下载安装IDEA,设置JRE以及其他preferences。新建一个Workspace,建立项目、包以及java源代码,IDEA配置成功的结果如图。
2.3安装Git
在官网下载Git并安装,设置用户名、邮箱等信息,Git配置成功的结果如图。
2.4安装Junit
Junit是java的一种单元测试框架,可以理解为一个库,里面放着各种用来对程序的某一模块进行测试的方法,同时还附加了一个可视化窗口和错误提示。Junit的使用方式和java编程没有太多区别,基本上就是调用各种函数去测试你的代码,看看它们在给定的输入下是否能给出正确的输出。具体的测试类及方法可以上网参考,这里就不赘述了。
IDEA 一般默认安装了插件 JUnit,如下图所示:可在 settings 中的 Plugins 选项卡中的 Installed 一栏中搜索 JUnit 查看。
在项目根目录下新建 Test 文件夹(文件名可以任意),并在菜单栏文件选项下的项目结构中选择 项目设置里的模块选项, 标记为该文件夹为 Test(默认为 Source),用以保存生成的测试类。标记后,自动生成的测试类都会自动保存在此文件夹下。
右键点击需要测试的类名(在代码栏中的类名) -> Go To -> Test,选择 "create new test...",打开生成界面。Testing Library 中可以选择junit版本,然后在下方选择需要测试的方法,点击ok即可自动在Test文件夹下生成测试类。
在这里给出你的GitHub Lab1仓库的URL地址。
GitHub Lab1仓库的URL地址:
3.实验过程
请仔细对照实验手册,针对四个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但无需把你的源代码全部粘贴过来!)。
为了条理清晰,可根据需要在各节增加三级标题。
Magic Squares
Magic Squares即幻方,n阶幻方是一个正方形n×n个数(通常是不同的整数)的排列,使得所有行、所有列和两个对角线中的n个数总和为同一常数。
isLegalMagicSquare()
本题要求在main()函数中调用五次isLegalMagicSquare()函数,将5个文本文件名分别作为参数输入进去,看其是否得到正确的输出(true,false)。首先打开文件
//打开文件
FileReader fr = new FileReader(fileName);
fileName = fileName.split("/")[3];
BufferedReader br = new BufferedReader(fr);
读取数据并判断是否符合格式,在while循环中读取时检测格式
//Square二维数组存储MagicSquare
List<int[]> Square = new ArrayList<>();
//读取MagicSquare
String[] nums = br.readLine().split("\t");
int col = nums.length;
while (true) {
if (nums.length != col) {
for (int i = 0; i < nums.length; i++) {
if (nums[i].contains(" ")) {
System.out.println(fileName + "没有用tab分隔");
return false;
}
if (nums[i].contains("-") || nums[i].contains(".") || nums[i].equals("0")) {
System.out.println(fileName + "输入数字中含非正整数");
return false;
}
}
System.out.println(fileName + "并非矩阵");
return false;//行数不相等的情况
}
int[] arr = new int[col];
for (int i = 0; i < col; i++) {
if (nums[i].contains(" ")) {
System.out.println(fileName + "没有用tab分隔");
return false;
}
if (nums[i].contains("-") || nums[i].contains(".") || nums[i].equals("0")) {
System.out.println(fileName + "输入数字中含非正整数");
return false;
}
arr[i] = Integer.parseInt(nums[i]);
}
Square.add(arr);
try {
nums = br.readLine().split("\t");
} catch (NullPointerException e) {
break;
}
}
//关闭reader
fr.close();
br.close();
if (Square.size() != col) {
System.out.println(fileName + "行列数不相等");
return false;
}
之后判断该矩阵是否是幻方
检查行列:
//检查各行的和是否相等
int sum = Arrays.stream(Square.get(0)).sum();//获取第一行的和
//检查行列
for (int i = 0; i < col; i++) {
//检查行
if (sum != Arrays.stream(Square.get(i)).sum()) {
System.out.println(fileName + "第" + (i + 1) + "行和不相等");
return false;
}
//检查列
int finalI = i;
if (sum != Square.stream().mapToInt(a -> a[finalI]).sum()) {
System.out.println(fileName + "第" + (i + 1) + "列和不相等");
return false;
}
}
检查斜边:
//检查对角线
int left = 0;
int right = 0;
for (int i = 0; i < col; i++) {
left += Square.get(i)[i];
right += Square.get(col - i - 1)[i];
}
if (left != sum || right != sum) {
System.out.println(fileName + "斜行不相等");
return false;
}
generateMagicSquare()
首先计算初始位置(0,n/2),赋值为1,之后每次取当前位置的右上角的位置,设置的值每次加1,如果当前行是第一行,则下一行为最后一行,如果当前列是最右边的列,则下一次取左边第一列,如此重复平方次,就对整个矩阵赋完值了,且满足幻方定义。
public static boolean generateMagicSquare(int n) throws IOException {
try{
try {
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;//数字 1 放在首行最中间的格子中
//向右上角斜行,依次填入数字
if (i % n == 0)
row++;//1~n填过一遍后换行
else {
if (row == 0)
row = n - 1;//如果右上方向出了上边界,就以出框后的位置为基准,将数字竖直降落至底行对应的格子中;
else
row--;//向右上角斜行,依次填入数字
if (col == (n - 1))
col = 0;//同上,向右出了边界,就以出框后的位置为基准,将数字平移至最左列对应的格子中;
else
col++;//向右上角斜行,依次填入数字
}
}
//输出生成的矩阵
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++)
System.out.print(magic[i][j] + "\t");
System.out.println();
}
FileOutputStream fo = new FileOutputStream("src/P1/txt/6.txt");
OutputStreamWriter osw = new OutputStreamWriter(fo);
for (int k = 0; k < magic.length; k++) {
for (int l = 0; l < magic.length; l++) {
osw.write(magic[k][l] + "");
if (l != magic.length - 1)
osw.write("\t");
}
if (k != magic.length - 1)
osw.write("\n");
osw.flush();
}
}
catch (NegativeArraySizeException e){
System.out.println("Error:输入的n不能为负数");
return false;
}
}
catch (ArrayIndexOutOfBoundsException e){
System.out.println("Error:输入的n不能为偶数");
return false;
}
return true;
}
Turtle Graphics
该任务需要我们clone已有的程序后,利用turtle按照要求画图,其中需要利用几何知识设计一些函数简化编程,最后可以发挥想象力进行Personal Art。首先分析turtle的package组成,了解类成员。
Problem 1: Clone and import
https://github.com/ComputerScienceHIT/Lab1-1183710109.git
- 在IDE中打开即可
Problem 3: Turtle graphics and drawSquare
该函数需要实现:已知边长,画出边长为指定数值的正方形。参数是海龟对象turtle和编程sidelength。
首先将海龟画笔设置为黑色。然后执行4次的前进sidelength长度、转完90度,即可完成一个边长为sidelength的正方形。下图是边长为40的正方形:
public static void drawSquare(Turtle turtle, int sideLength) {
for (int i = 0; i < 4; i++) {
turtle.forward(sideLength);
turtle.turn(90);
}
}
Problem 5: Drawing polygons
该问题首先希望已知正多边形边数的情况下计算正多边形的内角度。根据几何知识可以推导得计算内角的函数:
public static double calculateRegularPolygonAngle(int sides) {
return ((double)sides-2)*(180/(double)sides);
}
Junit测试结果如下:
该问题还希望已知正多变型得边数和边长画出一个正多边形。参照画正方形的方法,可以先前进sidelength,再使海龟旋转一个角度,执行“边数”次。其中,这个角度是正多边形内角的补角,利用calculateRegularPolygonAngle的功能计算出多边形内角,再用180°减去这个值即可。边长为20的正六边形效果如下:
public static void drawRegularPolygon(Turtle turtle, int sides, int sideLength) {
double angle = calculateRegularPolygonAngle(sides);
for (int i = 0; i < sides; i++) {
turtle.forward(sideLength);
turtle.turn(180-angle);
}
}
Problem 6: Calculating Bearings
该问题首先希望解决,已知起点和当前朝向角度,想知道到终点需要转动的角度。例如,如果海龟在(0,1)朝向 30 度,并且必须到达(0,0)它必须再转动 150 度。只需要考虑currentY==targetY的情况后计算atan与目前朝向的差值即可。
代码:
public static double calculateBearingToPoint(double currentBearing, int currentX, int currentY, int targetX, int targetY) {
if(targetY==currentY)
return targetX>currentX?(90-currentBearing+360)%360:(270-currentBearing+360)%360;
else {
return (Math.atan((double) (targetX - currentX) /(double) (targetY - currentY)) * 180 / Math.PI - currentBearing + 360) % 360;
}
}
junit测试:
基于上一个问题,此时有若干个点,想知道从第一个点开始到第二个点,再从第二个点到第三个点……以此类推每次转向的角度。将“起点”选为第一个点,循环n-1次(n为点的个数),每次将第i+1号点设置为“终点”,通过上一个函数计算旋转角度并存储到List中,将下一次的“起点”用当前“终点”更新,继续循环,结束后返回list即可
代码:
public static List<Double> calculateBearings(List<Integer> xCoords, List<Integer> yCoords) {
List<Double> bearings = new ArrayList<>();
int x = xCoords.get(0),y = yCoords.get(0);
double currentD = 0;
for (int i = 0; i < xCoords.size()-1; i++) {
int targetx = xCoords.get(i+1),targety = yCoords.get(i+1);
Double bear = calculateBearingToPoint(currentD,x,y,targetx,targety);
bearings.add(bear);
currentD = (bear+currentD)%360;
}
return bearings;
}
junit测试:
-
-
- Problem 7: Convex Hulls
-
给定平面上一堆点集,输出位于凸包上的点,我们采用Gift−Wrapping算法解决。任意凸包上的点,以该点建立一个极角坐标系,该点连结其它所有点的极角中,该点逆时针方向的第一凸包点到该点极角最小,例如P0,到所有点的极角中P0P1极角最小。算法中首先找到最左边的点,这个点必然在凸包上,然后计算该点连接点极角最小的,找到P1后,就可以从P1开始,接着顺次找到P2,又以P2为起点,依次循环直到返回第一节点。
因为每次的起点都是上次找到的凸包点,因此外层循环的复杂度为O(H),H为凸包上的点,内层循环每次都会全部遍历点,因此时间复杂度为 O(n) ,因此总的是间复杂度为 O(nH) ,在一般情况下 凸包上的点的期望为logn ,算法复杂度为 O(nlogn) ,极端情况下,如下所示,所有点都在类似圆弧上的话,外层循环也是n,因此会达到O(n^2)
代码实现:
public static Set<Point> convexHull(Set<Point> points) {
if (points.size() <= 3) return points;
Set<Point> convexHullPoints = new HashSet<Point>();
Point a = new Point(Double.MAX_VALUE, Double.MAX_VALUE);
for (Point i : points) {
if (i.x() < a.x() || (i.x() == a.x() && i.y() < a.y()))
a = i;
}
Point curPoint = a, minPoint = null, lastPoint = a;
double x1 = 0.0, y1 = -1.0;
do {
convexHullPoints.add(curPoint);
double minTheta = Double.MAX_VALUE, x2 = 0.0, y2 = 0.0;
for (Point i : points) {
if ((!convexHullPoints.contains(i) || i == a) && (i != lastPoint)) {
double x3 = i.x() - curPoint.x(), y3 = i.y() - curPoint.y();
double Theta = Math
.acos((x1 * x3 + y1 * y3) / Math.sqrt(x1 * x1 + y1 * y1) / Math.sqrt(x3 * x3 + y3 * y3));
// System.out.println(i.x() + " " + i.y() + " " + Theta);
if (Theta < minTheta || (Theta == minTheta && x3 * x3 + y3 * y3 > Math.pow(curPoint.x() - minPoint.x(), 2)
+ Math.pow(curPoint.y() - minPoint.y(), 2))) {
minPoint = i;
minTheta = Theta;
x2 = x3;
y2 = y3;
}
}
}
x1 = x2;
y1 = y2;
lastPoint = curPoint;
curPoint = minPoint;
// System.out.println(curPoint.x() + " " + curPoint.y());
} while (curPoint != a);
return convexHullPoints;
}
junit测试:
Problem 8: Personal art
运用%取模生成了富有规律的图案:
public static void drawPersonalArt(Turtle turtle) {
turtle.forward(120);
Map<Integer,String> colors = new HashMap<>();
colors.put(1,"PINK");
colors.put(2,"ORANGE");
colors.put(3,"YELLOW");
colors.put(4,"GREEN");
colors.put(5,"CYAN");
colors.put(6,"BLUE");
colors.put(0,"MAGENTA");
for (int i = 0; i < 1000; i++) {
turtle.color(PenColor.valueOf(colors.get(i%7)));
drawRegularPolygon(turtle,50,1);
turtle.turn(180);
if(i%20==0){
turtle.forward(120);
drawRegularPolygon(turtle,5,120);
turtle.turn(180);
drawRegularPolygon(turtle,5,120);
turtle.forward(120);
turtle.turn(180);
for (int j = 0; j < 100; j++) {
drawRegularPolygon(turtle,4,10);
turtle.forward(20);
turtle.turn(3.6*6);
}
}
turtle.turn(180);
turtle.forward(i%20 + i%40);
turtle.turn(3.6*4);
}
}
Submitting
git add * -> git commit -m “” -> git push提交到Lab1仓库
Social Network
利用图结构实现一张人际关系网
设计/实现FriendshipGraph类
Friendship中定义两个成员变量:
private Map<String,List<Person>> friendList:存储关系图的邻接表表示
private Set<String> names:存储现有的人名
成员方法增加getter和setter,其他的是给定的方法。
给定的方法实现:
1.getDistance:运用bfs算法搜索从一个人通过朋友到达另一个人的最近 距离,当两人中有人不存在时报错并退出,bfs算法的原理不再赘述, 代码实现如下:
public int getDistance(Person rachel, Person ross) {
if(!friendList.containsKey(rachel.getName())||!friendList.containsKey(ross.getName())){
System.out.println("不能计算不存在的人");
System.exit(0);
}
if(rachel.getName().equals(ross.getName())) return 0;
Set<String> visited = new HashSet<>();
Queue<Person> q = new ArrayDeque<>();
String target = rachel.getName();
int distance = 0;
q.add(rachel);
while(!q.isEmpty()){
Person poll = q.poll();
visited.add(poll.getName());
List<Person> people = friendList.get(poll.getName());
if(poll.getName().equals(ross.getName())) return distance;
if(target.equals(poll.getName())){
target = people.get(people.size()-1).getName();
distance++;
}
for (Person i :people) {
if(!visited.contains(i.getName())) q.add(i);
}
}
return -1;
}
2.addEdge:运用邻接表实现关系添加,当两人中有人不存在或重复时报错并退出。代码如下:
public void addEdge(Person rachel, Person ross) {
if(!friendList.containsKey(rachel.getName())||!friendList.containsKey(ross.getName())){
System.out.println("不能添加不存在的人");
System.exit(0);
}
List<Person> people = friendList.get(rachel.getName());
if(people.contains(ross)){
System.out.println("不能添加已有的朋友");
System.exit(0);
}
people.add(ross);
}
3.addVertex:运用List邻接表实现顶点添加,当有人重复时报错并退出。代码如下:
public void addVertex(Person rachel) {
if(names.contains(rachel.getName())){
System.out.println("不能添加重复的人");
System.exit(0);
}
names.add(rachel.getName());
friendList.put(rachel.getName(),new ArrayList<>());
}
设计/实现Person类
成员变量name
给出构造函数和getter,setter方法
public class Person {
private String name;
//构造函数
public Person(String name) {
this.name = name;
}
//getter
public String getName() {
return name;
}
//setter
public void setName(String name) {
this.name = name;
}
}
设计/实现客户端代码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));
//should print ‐1
}
设计/实现测试用例
整体如下:给出了FriendshipGraph的各方法测试
测试方法:测试getDistance时写了simple和complex两个测试方法,分别测试在简单图和复杂图上的正确性
@Test
public void testGetDistanceSimple() {
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);
assertEquals(1,graph.getDistance(rachel, ross));
assertEquals(2,graph.getDistance(rachel, ben));
assertEquals(0,graph.getDistance(rachel, rachel));
assertEquals(-1,graph.getDistance(rachel, kramer));
}
@Test
public void testGetDistanceComplex(){
FriendshipGraph fg = new FriendshipGraph();
fg.addVertex(new Person("A"));
fg.addVertex(new Person("B"));
fg.addVertex(new Person("C"));
fg.addVertex(new Person("D"));
fg.addVertex(new Person("E"));
fg.addVertex(new Person("F"));
fg.addVertex(new Person("G"));
fg.addVertex(new Person("H"));
fg.addVertex(new Person("I"));
fg.addVertex(new Person("J"));
fg.addEdge(new Person("A"),new Person("B"));
fg.addEdge(new Person("B"),new Person("A"));
fg.addEdge(new Person("D"),new Person("B"));
fg.addEdge(new Person("B"),new Person("D"));
fg.addEdge(new Person("A"),new Person("D"));
fg.addEdge(new Person("D"),new Person("A"));
fg.addEdge(new Person("C"),new Person("D"));
fg.addEdge(new Person("D"),new Person("C"));
fg.addEdge(new Person("D"),new Person("E"));
fg.addEdge(new Person("E"),new Person("D"));
fg.addEdge(new Person("C"),new Person("F"));
fg.addEdge(new Person("F"),new Person("C"));
fg.addEdge(new Person("F"),new Person("G"));
fg.addEdge(new Person("G"),new Person("F"));
fg.addEdge(new Person("E"),new Person("G"));
fg.addEdge(new Person("G"),new Person("E"));
fg.addEdge(new Person("H"),new Person("I"));
fg.addEdge(new Person("I"),new Person("H"));
fg.addEdge(new Person("J"),new Person("I"));
fg.addEdge(new Person("I"),new Person("J"));
assertEquals(2,fg.getDistance(new Person("A"),new Person("E")));
assertEquals(1,fg.getDistance(new Person("A"),new Person("D")));
assertEquals(3,fg.getDistance(new Person("A"),new Person("G")));
assertEquals(2,fg.getDistance(new Person("D"),new Person("F")));
assertEquals(3,fg.getDistance(new Person("B"),new Person("F")));
assertEquals(2,fg.getDistance(new Person("J"),new Person("H")));
assertEquals(0,fg.getDistance(new Person("I"),new Person("I")));
assertEquals(-1,fg.getDistance(new Person("D"),new Person("J")));
assertEquals(-1,fg.getDistance(new Person("C"),new Person("I")));
assertEquals(-1,fg.getDistance(new Person("F"),new Person("H")));
}
测试另外两个方法就很正常
@Test
public void testAddVertex() {
FriendshipGraph fg =new FriendshipGraph();
fg.addVertex(new Person("ross"));
//fg.addVertex(new Person("ross"));
assert(fg.getNames().contains("ross")&&fg.getFriendList().containsKey("ross"));
}
@Test
public void testAddEdge(){
FriendshipGraph fg =new FriendshipGraph();
Person ross = new Person("ross");
Person bob = new Person("bob");
fg.addVertex(ross);
fg.addVertex(bob);
fg.addEdge(ross,bob);
//fg.addEdge(ross,bob);
//fg.addEdge(ross,new Person("Jack"));
assert (fg.getFriendList().get("ross").contains(bob));
}