语言:Java
开发工具:IntelliJ IDEA 2022.1社区版
1.实验目标本次实验通过求解选自MIT和CMU三个问题,训练基本 Java 编程技能,能够利用 Java OO 开发基本的功能模块,能够阅读理解已有代码框架并根据功能需求补全代码,能够为所开发的代码编写基本的测试程序并完成测试,初步保证所开发代码的正确性。 另一方面,利用 Git 作为代码配置管理的工具,学会 Git 的基本使用方法。
基本的 Java OO 编程
基于 Eclipse IDE 进行 Java 编程
基于 JUnit 的测试
基于 Git 的代码配置管理
2.环境配置配置过程:下载IDEA—配置JDK11—新建项目—Module中添加JUnit4
3.实验过程 3.1Magic Squares3.1.1isLegalMagicSquare()
编写isLegalMagicSquare()函数,判断以txt形式输入的矩阵是否满足幻方要求,同时需要考虑程序应对非法输入的健壮性;
新建MagicSquare类
- 读取txt文件:
- 使用bufferReader按行读取,读取到temp;
- 使用temp.split(“\t”)将字符串分开;
- 使用Integer.valueOf()函数将字符串转化为数字;
- 读取时对输入进行判断:
- 非数字字符输入;
- 数字重复输入;
- 输入负数;
- 输入不满足幻方要求的形状
- 判断行列元素之和、对角线元素之和是否相等:
- 计算两条对角线元素之和并比较,相等则作为基值参与后面比较,否则返回false;
- 计算每一行列的元素和,与基值比较,相等则为幻方,否则不是;
public static boolean isLegalMagicSquare(String fileName) throws IOException {
File file = new File(fileName);
FileReader Freader = new FileReader(file);
BufferedReader buf = new BufferedReader(Freader);
int[][] numbers = new int[Max][Max];
boolean[] judge_repeat = new boolean[Max*Max+1];//判断是否有重复元素
Arrays.fill(judge_repeat,false);
String temp = null;
String[] line = null;
int m = 0,n = 0;
temp = buf.readLine();
line = temp.split("\t");
n = line.length;
int old_n = n;
while(temp!=null){
for(int i=0;i
3.1.2generateMagicSquare()
理解并修改generateMagicSquare()的代码,使之能够把生成的幻方写到txt文件里
1.原函数流程图
2.异常分析
1.当输入为偶数时,出现数组越界异常,以输入2为例:
第一次引用右上角元素,第二次引用左下角元素,此时i为2,恰好是阶数2的倍数,
使得第三次引用第三行的元素,发生越界访问;
2. 当输入为负数时,由于不允许数组大小是负数,所以出现数组大小为负数的异常
3.函数扩展
1.引入PrintWriter将产生的幻方数组写到txt文件中;
2.在函数开始,对输入的阶数n进行合法化检查
public static boolean generateMagicSquare(int n) throws IOException {
if(n<0||n%2==0){
System.out.println("输入阶数为负数或为偶数!");
return false;
}
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,2,。。。,n*n
if (i % n == 0)//如果i是阶的倍数,下一个元素填充在下一行
row++;
else {
if (row == 0)//如果当前在第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;
}
main函数:
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
for(int i=1;i<=5;i++){
System.out.println(isLegalMagicSquare("src/P1/txt/"+i+".txt"));
}
System.out.println("输入幻方的阶数");
int n = sc.nextInt();
if(generateMagicSquare(n)){
System.out.println("generateMagicSquare函数生成的幻方是否满足要求:"+isLegalMagicSquare("src/P1/txt/6.txt"));
}
}
3.2Turtle Graphics
在给定的Turtle程序中实现几个方法,然后通过这些方法,实现凸包算法、绘制图像等,并利用JUnit进行测试
注:以下方法在TurtleSoup类下
Problem 1: Clone and import
从GitHub上下载代码到自己的项目里,需要注意,由于目录结构变化,下载的每个class文件开头的package需要在前面添加P2.
Problem 3: Turtle graphics and drawSquare
实现:通过调用forward和turn函数,分别控制每次前进sideLength的距离、顺时针转动90度,重复四次即可
Problem 5: Drawing polygons
实现:首先实现calculateRegularPolygonAngle方法,计算正n边形的内角大小,然后类比drawSquare方法,控制每次前进距离和转动方向即可,需要注意的是,转动方向是180-内角大小
Problem 6: Calculating Bearings
calculateBearingToPoint实现:利用反正切函数获取两点间连线与y轴夹角,但是反正切函数返回的是-pi/2到pi/2的值,而题目要求返回沿顺时针转动的角度,所以要根据两点的位置关系对结果进行处理;
calculateBearings实现:不断调用calculateBearingToPoint方法,记录当前点和偏转角度即可
Problem 7: Convex Hulls
实现:选择最左下方的顶点作为基准点,然后扫描其余顶点,通过calculateBearingToPoint方法选取从基准点到该点,所需转动角度最小的点,如果存在两个点,从基准点到达它们所需的转动角度一样小,则选取离基准点较远的那一个,即确保求得的凸包是最小的,将新选取的点作为基准点,重复上述过程,直到选取的点为开始的顶点为止。
Problem 8: Personal art
实现:利用上面的函数绘制正五边形,然后通过forward和turn绘制螺旋线
TurtleSoup类代码
package P2.turtle;
import java.util.*;
public class TurtleSoup {
/**
* Draw a square.
* @param turtle the turtle context
* @param sideLength length of each side
*/
public static void drawSquare(Turtle turtle, int sideLength) {
float angle = 90;
for (int i = 0; i < 4; i++) {
turtle.forward(sideLength);
turtle.turn(angle);
}
}
/**
* Determine inside angles of a regular polygon.
*/
public static double calculateRegularPolygonAngle(int sides) {
double angle = 1.0 * (180 * sides - 360) / sides;
return angle;
}
/**
* Determine number of sides given the size of interior angles of a regular polygon.
*/
public static void drawRegularPolygon(Turtle turtle, int sides, int sideLength) {
double angle = 180.0 - calculateRegularPolygonAngle(sides);
for (int i = 0; i < sides; i++) {
turtle.forward(sideLength);
turtle.turn(angle);
}
}
/**
* Given the current direction, current location, and a target location, calculate the Bearing
*/
public static double calculateBearingToPoint(double currentBearing, int currentX, int currentY,
int targetX, int targetY) {
if(currentBearing < 0||currentBearing >= 360){
throw new IllegalArgumentException("input out of bound!");
}
double tan = 1.0 * (targetX - currentX) / (targetY - currentY);
double rawBearing = Math.toDegrees(Math.atan(tan));
if (targetX - currentX > 0 && targetY - currentY < 0)
rawBearing += 180;
else if (targetX - currentX < 0 && targetY - currentY >= 0)
rawBearing += 360;
else if (targetX - currentX <= 0 && targetY - currentY < 0)
rawBearing += 180;
if (rawBearing < currentBearing)
return rawBearing - currentBearing + 360;
return rawBearing - currentBearing;
}
/**
* Given a sequence of points, calculate the Bearing adjustments needed to get from each point
*/
public static List calculateBearings(List xCoords, List yCoords) {
List angles = new ArrayList<>();
double cb = 0;
int x0 = xCoords.get(0);
int y0 = yCoords.get(0);
int x, y;
for (int i = 1; i < xCoords.size(); i++) {
x = xCoords.get(i);
y = yCoords.get(i);
angles.add(calculateBearingToPoint(cb, x0, y0, x, y));
cb = (cb + angles.get(i - 1)) % 360;
x0 = x;
y0 = y;
}
return angles;
}
/**
* Given 2 points, compute the distance of the points
*/
public static double getDistance(Point a, Point b) {
double X = Math.abs(b.x() - a.x());
double Y = Math.abs(b.y() - a.y());
return Math.sqrt(X * X + Y * Y);
}
/**
* Given a set of points, compute the convex hull, the smallest convex set that contains all the points
*/
public static Set convexHull(Set points) {
if (points.size() <= 2)
return points;
Set ConvexHull = new HashSet();
Iterator it = points.iterator();
Point start = it.next();
Point current = start;
double angle = 0, current_angle = 0;
for (Point p : points) {
if (p.x() < start.x() || (p.x() == start.x() && p.y() < start.y()))
start = p;
}
ConvexHull.add(start);
Point base = start;
while (true) {
double min_angle = Double.MAX_VALUE;
for (Point p : points) {
if (p == base)
continue;
angle = calculateBearingToPoint(current_angle, (int) base.x(), (int) base.y(), (int) p.x(), (int) p.y());
if (angle < min_angle) {
current = p;
min_angle = angle;
} else if (angle == min_angle) {
double dis1 = getDistance(base, p);
double dis2 = getDistance(base, current);
if (dis1 > dis2) {
current = p;
}
}
}
if (current == start)
break;
else {
ConvexHull.add(current);
current_angle = min_angle;
base = current;
}
}
return ConvexHull;
}
/**
* Draw your personal, custom art.
*/
public static void drawPersonalArt(Turtle turtle) {
int Size = 400, Step = 1, Densi = 1, ColorNum = 6,sideLength = 100;
for(int i=0;i<3;i++){
for(int j=1;j<=Size;j++){
switch (j % ColorNum) {
case 0:
turtle.color(PenColor.BLUE);
break;
case 1:
turtle.color(PenColor.CYAN);
break;
case 2:
turtle.color(PenColor.GREEN);
break;
case 3:
turtle.color(PenColor.YELLOW);
break;
case 4:
turtle.color(PenColor.RED);
break;
case 5:
turtle.color(PenColor.MAGENTA);
break;
}
drawRegularPolygon(turtle,5,sideLength);
turtle.turn(9);
}
sideLength += 100;
}
for (int i = 1; i <= Size; i++) {
switch (i % ColorNum) {
case 0:
turtle.color(PenColor.BLUE);
break;
case 1:
turtle.color(PenColor.CYAN);
break;
case 2:
turtle.color(PenColor.GREEN);
break;
case 3:
turtle.color(PenColor.YELLOW);
break;
case 4:
turtle.color(PenColor.RED);
break;
case 5:
turtle.color(PenColor.MAGENTA);
break;
}
turtle.forward(Step * i);
turtle.turn(360 / ColorNum + Densi);
}
}
/**
* Main method.
*/
public static void main (String[] args){
DrawableTurtle turtle = new DrawableTurtle();
drawPersonalArt(turtle);
turtle.draw();
}
}
Submitting
如何通过Git提交当前版本到GitHub上你的Lab1仓库:通过git status查看改变的文件,将改变的文件add到原有git仓库中,再用git commit提交,最后git push到GitHub的远程仓库上。
3.3Social Network设计Person类存储人的信息,设计FriendshipGraph类实现简单的社交网络,包括添加顶点、添加边和求任意两顶点的距离
1.设计/实现FriendshipGraph类
成员变量:顶点序号num、顶点表verTable和邻接表adjTable。顶点序号为int类型,在新增顶点之后自动加一;顶点表是HashMap
主要方法:
public void addVertex(Person person):
将person加入顶点表中,如果出现重名则抛出异常,终止程序
public void addEdge(Person startPerson,Person endPerson):
在图中加入边:startPerson->endPerson,即在startPerson对应顶点的链表中加入endPerson对应的定点序号,如果已存在则不加入,如果参数对应的顶点不在图中则抛出异常;
public int getDistance(Person startPerson,Person targetPerson):
计算图中两个顶点间距离,两顶点相同返回0,两顶点之间没有路径返回-1。使用广度优先搜索算法,维护一个队列存放待搜索的顶点,取出队列第一个元素,遍历它的邻接表,如果出现目标顶点则停止,不出现则把遍历到的顶点加入队列,重复上述过程,直至队列为空。需要注意的是,需要记录队列中每个元素距离开始顶点的距离,这里使用node内部类定义一个新的数据类型,类似于C语言结构体的用法。
FriendshipGraph类代码:
package P3;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;
import static java.lang.System.exit;
public class FriendshipGraph {
private class node{
int vertex;
int distance;
public node(int vertex,int distance) {
this.vertex = vertex;
this.distance = distance;
}
}
private int num;
private HashMap verTable;
private HashMap> adjTable;
public FriendshipGraph() {
this.num = 1;
this.verTable = new HashMap<>();
this.adjTable = new HashMap<>();
}
public void addVertex(Person person){
for(Person p: verTable.keySet()){
if(p.getName() == person.getName()){
throw new IllegalArgumentException("The graph already exists a identical name");
}
}
verTable.put(person,this.num);
adjTable.put(this.num,new LinkedList<>());
this.num++;
}
public void addEdge(Person startPerson,Person endPerson){
if(!verTable.containsKey(startPerson)||!verTable.containsKey(endPerson)){
throw new IllegalArgumentException("The graph doesn't exist such vertex please add first");
}
int startNum = verTable.get(startPerson);
int endNum = verTable.get(endPerson);
LinkedList listOfStart = adjTable.get(startNum);
for(Integer i:listOfStart){
if(i == endNum){
System.out.println("不需要重复添加边");
return;
}
}
listOfStart.add(endNum);
}
public int getDistance(Person startPerson,Person targetPerson){
if(!verTable.containsKey(startPerson)||!verTable.containsKey(targetPerson)){
throw new IllegalArgumentException("The start vertex or target vertex doesn't exist");
}
int start = verTable.get(startPerson);
int target = verTable.get(targetPerson);
if(start == target)
return 0;
int distance = 0;
boolean[] visited = new boolean[verTable.size()+1];
Queue Q = new LinkedList<>();
LinkedList curList;
Q.offer(new node(start,distance));
while(!Q.isEmpty()){
node current = Q.poll();
visited[current.vertex] = true;
curList = adjTable.get(current.vertex);
if(!curList.isEmpty()){
for(int i:curList){
if(i == target){
return current.distance+1;
}
else if(!visited[i]){
Q.offer(new node(i,distance+1));
}
}
}
}
return -1;
}
public HashMap getVerTable() {
return verTable;
}
public HashMap> getAdjTable() {
return adjTable;
}
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);
//should print 1
System.out.println(graph.getDistance(rachel, ross));
//should print 2
System.out.println(graph.getDistance(rachel, ben));
//should print 0
System.out.println(graph.getDistance(rachel, rachel));
//should print -1
System.out.println(graph.getDistance(rachel, kramer));
}
}
2.设计/实现Person类
Person类包括name一个成员变量、getName方法来获取实例的name属性。
package P3;
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
3.设计/实现客户端代码main()
借鉴实验指导书上的客户端代码
4.设计/实现测试用例
主要对三个方法进行测试:
public void addVertex(Person person):
1.加入重复顶点,测试是否抛出异常
2.加入10000个顶点,测试程序对大量数据的适用性
public void addEdge(Person startPerson,Person endPerson):
1.输入不存在的顶点,测试是否抛出异常
2.加入10000条边,测试程序对大量数据的适用性
public int getDistance(Person startPerson,Person targetPerson):
1.输入不存在的顶点,测试是否抛出异常
2.输入每个顶点至多一条边的图
3.输入存在顶点有多条边的图
JUnit测试代码(test.P3目录下):
package P3;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.util.*;
import static org.junit.Assert.*;
public class FriendshipGraphTest {
/**
* Testing Strategy:
* addVertex:
* 两个顶点人名相同
* 添加大量顶点
* addEdge:
* 为不存在的定点添加边
* 添加大量的边
* getDistance:
* 输入中含有不存在的顶点
* 每个顶点至多一条边
* 存在顶点有多条边
*/
@Rule
public ExpectedException expectedEx = ExpectedException.none();
//covers 违反“ Each person has a unique name ”的约束条件
@Test
public void testAddDuplicatedVertex() throws IllegalArgumentException{
expectedEx.expect(IllegalArgumentException.class);
expectedEx.expectMessage("The graph already exists a identical name");
FriendshipGraph graph = new FriendshipGraph();
Person rachel = new Person("Rachel");
Person ross = new Person("Rachel");
Person ben = new Person("Ben");
Person kramer = new Person("Kramer");
graph.addVertex(rachel);
graph.addVertex(ross);
graph.addVertex(ben);
graph.addVertex(kramer);
}
//covers 添加10000个顶点
@Test
public void testAddManyVertex(){
FriendshipGraph graph = new FriendshipGraph();
for(int i=0;i<9999;i++){
Person test = new Person("test"+ i);
graph.addVertex(test);
}
Person test = new Person("test9999");
graph.addVertex(test);
HashMap result = graph.getVerTable();
assertEquals(Integer.valueOf(10000),result.get(test));
}
//covers 给不存在的顶点添加边
@Test
public void testAddEdgeForNonExistVertex() throws IllegalArgumentException{
expectedEx.expect(IllegalArgumentException.class);
expectedEx.expectMessage("The graph doesn't exist such vertex please add first");
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.addEdge(rachel,kramer);
}
//covers 给一个顶点添加10000条边
@Test
public void testAddManyEdge(){
FriendshipGraph graph = new FriendshipGraph();
Person base = new Person("Base");
graph.addVertex(base);
for(int i=1;i<10001;i++){
Person test = new Person("test"+ i);
graph.addVertex(test);
graph.addEdge(base,test);
}
LinkedList test = new LinkedList<>();
for(int i=2;i<10002;i++){
test.add(Integer.valueOf(i));
}
HashMap> res = graph.getAdjTable();
LinkedList result = res.get(Integer.valueOf(1));
assertEquals(test,result);
}
//covers 待搜索的人名不存在
@Test
public void testGetNonExistDistance() throws IllegalArgumentException{
expectedEx.expect(IllegalArgumentException.class);
expectedEx.expectMessage("The start vertex or target vertex doesn't exist");
FriendshipGraph graph = new FriendshipGraph();
Person rachel = new Person("Rachel");
Person ross = new Person("Ross");
Person ben = new Person("Ben");
Person kramer = new Person("Kramer");
Person johnson = new Person("Johnson");
graph.addVertex(rachel);
graph.addVertex(ross);
graph.addVertex(ben);
graph.addVertex(kramer);
graph.addEdge(rachel,kramer);
graph.getDistance(rachel,johnson);
}
//covers 所有的边都是单向的
@Test
public void testGetDistance(){
FriendshipGraph graph = new FriendshipGraph();
Person rachel = new Person("Rachel");
Person ross = new Person("Ross");
Person ben = new Person("Ben");
Person kramer = new Person("Kramer");
Person johnson = new Person("Johnson");
graph.addVertex(rachel);
graph.addVertex(ross);
graph.addVertex(ben);
graph.addVertex(kramer);
graph.addVertex(johnson);
graph.addEdge(rachel,ross);
graph.addEdge(ross,ben);
graph.addEdge(ben,kramer);
assertEquals(1,graph.getDistance(rachel,ross));
assertEquals(3,graph.getDistance(rachel,kramer));
assertEquals(-1,graph.getDistance(kramer,rachel));
assertEquals(-1,graph.getDistance(johnson,ben));
}
//covers 每个顶点至多一条边
@Test
public void testGetDistance2(){
FriendshipGraph graph = new FriendshipGraph();
Person rachel = new Person("Rachel");
Person ross = new Person("Ross");
Person ben = new Person("Ben");
Person kramer = new Person("Kramer");
Person johnson = new Person("Johnson");
graph.addVertex(rachel);
graph.addVertex(ross);
graph.addVertex(ben);
graph.addVertex(kramer);
graph.addVertex(johnson);
graph.addEdge(rachel,ross);
graph.addEdge(rachel,ben);
graph.addEdge(rachel,kramer);
graph.addEdge(kramer,johnson);
assertEquals(1,graph.getDistance(rachel,ross));
assertEquals(2,graph.getDistance(rachel,johnson));
assertEquals(-1,graph.getDistance(kramer,rachel));
assertEquals(-1,graph.getDistance(johnson,ben));
}
}
4.实验过程中收获的经验、教训、感想
4.1.实验过程中收获的经验和教训
通过这次实验,我对Java编程的基础知识有了更深刻的理解,对面向对象的编程思想有了更深刻的认识,同时,我学会了对Java数据类型如HashMap、LinkedList的使用,初步了解了JUnit单元测试、git版本控制等新知识。与此同时,我通过本次实验清晰认识到对程序进行单元测试的重要性,比如编写问题3的getDistance时,一开始的实现过程是:每从队列中取一个元素就把距离+1,这样对于一个顶点有多条边的情况,计算错误,而这个错误正是通过编写测试用例发现的,在今后的实验过程中,要提前想好测试用例,及时对程序进行测试,做到防患于未然。
4.2.针对以下方面的感受1.Java编程语言是否对你的口味?与你熟悉的其他编程语言相比,Java有何优势和不足?
刚接触Java语言有些许的不习惯,对面向对象的编程思想认识不足。个人认为,与C语言相比,Java语言的优点在于取消复杂的指针 *** 作,提供了许多方便快捷的数据类型,不足在于编译运行速度稍慢。
2.关于Eclipse或IntelliJ IDEA,它们作为IDE的优势和不足;
IDE | 优势 | 不足 |
Eclipse | 开源、免费、占用内存较少、一个页面可以打开多个项目 | 界面较为传统、代码提示不够智能 |
IntelliJ IDEA | 界面美观、代码提示智能 | 专业版需要付费、占用内存较多、一个页面只能打开一个项目 |
3.关于Git和GitHub,是否感受到了它在版本控制方面的价值;
使用git可以轻松实现版本控制,修改过的程序git会自动检查、提交新版本时只需处理修改过的程序即可;使用GitHub托管平台可以轻松实现代码获取和提交,便于多人协作。
4.关于CMU和MIT的作业,你有何感受;
内容很新颖、很有新意,在锻炼编程能力的同时解决了实际问题。
5.关于本实验的工作量、难度、deadline;
工作量适中,deadline也合适,不过对于自己这样基础较薄弱的同学,难度稍大, 自己会课下多加练习,以满足课程要求。
6.关于初接触“软件构造”课程;
接触这门课之前,认为它是关于如何开发软件的,即教会我们写软件,但上了两堂课之后,发现这门课并不仅仅涵盖写软件这样浅层次的知识,更多的是在试图传授思维,让我们对软件开发进行具体实践的同时,能在宏观上把握一个软件的整体。
水平有限,欢迎批评指正~
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)