实验目标
本次实验通过求解三个问题,训练基本 Java 编程技能,能够利用 Java OO 开
发基本的功能模块,能够阅读理解已有代码框架并根据功能需求补全代码,能够
为所开发的代码编写基本的测试程序并完成测试, 初步保证所开发代码的正确性。
另一方面,利用 Git 作为代码配置管理的工具,学会 Git 的基本使用方法。
- 基本的 Java OO 编程
- 基于 Eclipse IDE 进行 Java 编程
- 基于 JUnit 的测试
- 基于 Git 的代码配置管理
实验过程
Magic Square
第一部分
要求
You are to write a Java program (MagicSquare.java) for checking the
row/column/diagonal values of a matrix and then judging whether it is a magic
squares
- We give you five text files: 1.txt, 2.txt, …, 5.txt. Download them from https://github.com/rainywang/Spring2022_HITCS_SC_Lab1/tree/master/P1
and add them into your project directory \src\P1\txt; - For each file: open the file, and check that all rows indeed sum to the same
constant. - Check that the columns and the diagonal also sum to the same constant.
Return a boolean result indicating whether the input is a magic square or
not. - 函数规约: boolean isLegalMagicSquare(String fileName)
- 在 main()函数中调用五次 isLegalMagicSquare()函数,将 5 个文本
文件名分别作为参数输入进去,看其是否得到正确的输出( true,
false)。 - 需要能够处理输入文件的各种特殊情况,例如:文件中的数据不符合
Magic Square 的定义(行列数不相等、并非矩阵等)、矩阵中的某些数
字并非正整数、数字之间并非使用\t 分割、等。若遇到这些情况,终止
程序执行(isLegalMagicSquare 函数返回 false),并在控制台输出
错误提示信息。
实现
为了完成实验要求需要解决两部分问题,首先需要考虑文件的读取并记录数据,然后需要根据读入的数据判断是否满足幻方条件
- 文件读取与处理
数据
nums 临时存储从文件中读入的每行数据对应的字符串数组
matrix 存储文件读入并进行处理后的矩阵
have 用于记录已存在的数据便于判断是否存在重复
int row = 0,matrix_n = 0;
ArrayList<String> nums;
ArrayList<ArrayList<Integer>> matrix = new ArrayList<ArrayList<Integer>>();
HashSet<Integer> have=new HashSet<Integer>();
读取与处理
首先根据文件路径创建Scanner对象
然后对每行数据进行读取,然后对读取的字符串使用split()进行拆分,拆分后使用parseInt将字符串转化为整形(再过程中要判断是否存在非整型数据,数据是否已经存在),记录第一行数据个数,记录行序号
之后以同样的方式重复读入并处理每行数据,并判断数据个数是否于第一行数据个数相同(判断是否为幻方),并记录行序号
再文件读取完成后
判断行数是否于行数据个数相等,不等则证明不满足幻方条件
try {
Scanner input = new Scanner(new File(fileName));
while(input.hasNextLine()) {
nums = new ArrayList<String>(List.of(input.nextLine().split("\t")));
row++;
if (matrix_n == 0) matrix_n = nums.size();
else if (matrix_n != nums.size()) {
System.out.print(row);
System.out.println("行间数字个数不等,应以'\\t'进行区分");
return false;
}
try {
ArrayList<Integer> num_row=new ArrayList<Integer>();
for (int i = 0; i < matrix_n; i++) {
int value = Integer.parseInt(nums.get(i));
num_row.add(value);
if(!have.contains(value)){
have.add(value);
}else{
System.out.println("重复数字");
return false;
}
if (value <= 0) {
System.out.println("存在输入不为正数");
return false;
}
}
matrix.add(num_row);
} catch (NumberFormatException e) {
System.out.println("存在不为整数的输入");
return false;
}
}
}catch(FileNotFoundException e){
System.out.println("文件读取异常");
return false;
}
if(row != matrix_n) {
System.out.println("行列数目不等");
}
if(matrix_n==0){
System.out.println("无输入");
return false;
}
- 幻方判断
根据文件读入行数创建统计每列和的数组,再创建统计对角线和的变量
计算第一行数据和值并记录,并更新各列的和值于对角线和值
对每行数据求和,并根据该行的数据更新列和值与对角线和值,判断行间和值是否相等,不等则不是幻方
再每行数据都处理完成之后,比较行列和与对角线和,若存在不等则不为幻方
int sum = 0,sum1 = 0, k1, k2;
int[] num=new int[matrix_n];
k1=matrix.get(0).get(0);
k2=matrix.get(0).get(matrix_n-1);
for(int i=0;i<matrix_n;i++){
num[i]=matrix.get(0).get(i);
sum+=matrix.get(0).get(i);
}
for(int i = 1; i < matrix_n; i++) {
k1 += matrix.get(i).get(i);
k2 += matrix.get(i).get(row - i - 1);
for(int j = 0; j < matrix_n; j++) {
sum1 += matrix.get(i).get(j);
num[j]+=matrix.get(i).get(j);
}
if(sum1 != sum){
System.out.println(i);
System.out.println("行和与之前的行和不等");
return false;
}
sum1=0;
}
for(int i=0;i<matrix_n;i++){
if(sum!=num[i]){
System.out.println(i);
System.out.println("列和与行间不等");
return false;
}
}
if(k1 != sum || k2 != sum) {
System.out.println("对角线和值与行列和不等");
return false;
}
return true;
第二部分
阅读以下代码,将其加入你的 MagicSquare 类中作为一个静态函数,并试着
在 main()中测试它。利用你前面已经写好的 isLegalMagicSquare()函数,在 main()函数判断该函数新生成的文本文件 6.txt 是否符合 magic square 的定义
要求
public static boolean generateMagicSquare(int n) {
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;
if (i % n == 0)
row++;
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();
}
return true;
}
实现
该函数再输入为奇数时表现正常,在输入为偶数时会出现数组下标越界
通过函数判断可知该函数产生的数据满足幻方条件
Turtle Graphics
第一部分 绘制多边形
- 要求
实施calculateRegularPolygonAngle
运行我们提供的公开测试。在 6.031 中,我们使用 JUnit,一个被广泛采用的 Java 测试库。我们将在即将到来的课程中看到更多关于 JUnit 以及一般测试的信息。现在,让我们专注于针对您的实现运行提供的测试。按照惯例,JUnit 测试位于名称以 结尾的类中Test,因此该问题集的公共测试位于TurtleSoupTest.java.
通过 JUnit 测试 calculateRegularPolygonAngle
要在 中运行 JUnit 测试,请在 Package Explorer的文件夹下TurtleSoupTest找到,右键单击它,然后转到Run As选项。单击JUnit Test,您应该会看到 JUnit 视图出现。TurtleSoupTest.javatest
如果您的实现calculateRegularPolygonAngle是正确的,您应该会在结果列表旁边看到一个绿色复选标记。calculateRegularPolygonAngleTest
大 红色条 表示至少一项测试失败。这不应该让我们感到惊讶,因为其他测试正在执行我们尚未实现的方法。当所有测试通过时,条将变为绿色!
如果testAssertionsEnabled失败,您没有按照入门指南中的说明进行操作。 入门第 4 步包含在使用 Eclipse 之前必须执行的设置。
尝试破坏您的实现并TurtleSoupTest再次运行。
对 calculateRegularPolygonAngle 的 JUnit 测试失败
您应该在 JUnit 视图旁边看到一个红色的 X或蓝色的 X ,如果您单击,您将在底部的框中看到一个堆栈跟踪,它提供了错误原因的简要说明。双击故障堆栈跟踪中的一行将在跟踪中显示该帧的代码。这对于与您的代码对应的行最有用;此堆栈跟踪还将包含 Java 库或 JUnit 本身的行。calculateRegularPolygonAngleTestcalculateRegularPolygonAngleTest
尝试双击堆栈跟踪中的行calculateRegularPolygonAngleTest(),然后查看测试代码。在该行上,您应该看到对您的calculateRegularPolygonAngle实现的调用,其参数具有特定值,在调用assertEquals()中检查您的实现的返回值是否正确。JUnit 测试通常具有这种形式:它调用您的实现,然后对您的实现返回的值做出一个或多个断言。
- 实现
计算内角
内角计算公式(180*(边数-2))/ 边数
if(sides>2) {
return (sides - 2) * 180.0 / sides;
}else{
throw new IllegalArgumentException("参数错误");
}
绘制多边形
首先确定边数和边长
重复执行边数次以下操作
1,旋转180-内角度数
2,前进边长的长度
效果
第二部分 计算方位
- 要求
实施calculateBearingToPoint。
该函数计算turn从当前点到目标点所需的参数,以当前方向作为附加参数。例如,如果海龟在 (0,1) 处面向 30 度,并且必须到达 (0,0),它必须再转 150 度,所以calculateBearingToPoint(30, 0, 1, 0, 0)会返回150.0。
实施calculateBearings。
确保在calculateBearingToPoint此处使用您的实现。有关如何使用 JavaList接口和实现它的类的信息,请查看java.util.ListJava 库文档。请注意,对于n个点的列表,您将返回n-1 个航向调整;此调整列表可用于将海龟引导到列表中的每个点。例如,如果输入列表由xCoords=[0,0,1,1]和yCoords=[1,0,0,1](表示点 (0,1)、(0,0)、(1,0) 和 (1,1))组成,则返回的列表将由 组成[180.0, 270.0, 270.0]。
此时,JUnit 现在应该为 和 显示绿色calculateBearingToPointTest检查calculateBearingsTest。
- 实现
海龟默认初始角度为90度即在Y轴正方向
通过数学知识可知,以下实现可以满足条件
double angle = Math.atan2(targetY - currentY, targetX - currentX) * 180.0 / Math.PI;
angle=angle<0?-angle:360-angle;
return (angle-currentBearing+90)%360;
第三部分 凸包
- 要求
实现convexHull,它计算凸包,即包含一组输入点中所有点的最小凸集。礼品包装算法是解决这个问题的一种简单方法,还有其他算法。
上面的规范注释convexHull要求您返回“形成凸包周长顶点的输入点的最小子集”。该语句在任何一组输入点上都是明确定义的。 - 实现
首先找到最下的最左点作为当前点加入集合
然后找到以当前点为起点的相对x轴正方向向左偏转最小的点,若在集合中则集合中的点满足闭包,否则作为当前点记录偏转角重复该步骤
测试结果
第四部分 个人艺术
- 要求
在此功能中,您可以自由绘制您想要的任何艺术品。您的作品将根据美学和用于绘制它的代码进行评判。你的艺术不需要很复杂,但应该不止几行。使用辅助方法、循环等,而不是简单地列出 forward 和 turn 命令。
For drawPersonalArtonly,您也可以使用更改笔颜色的color方法。Turtle 您只能使用提供的颜色 - 实现
Social Network
Implement and test a FriendshipGraph class that represents friendships in a
social network and can compute the distance between two people in the graph. An
auxiliary class Person is also required to be implemented.
You should model the social network as an undirected graph where each person
is connected to zero or more people, but your underlying graph implementation
should be directed. 注:本问题拟刻画的社交网络是无向图,但你的类设计要能
够支持未来扩展到有向图。正因为此,如果要在两个 Person 对象 A 和 B 之间增
加一条社交关系, 那么需要同时调用 addEdge(A,B)和 addEdge(B,A)两条语句。
Your solution must work with the following client implementation
- 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);
10.graph.addEdge(rachel, ross);
11.graph.addEdge(ross, rachel);
12.graph.addEdge(ross, ben);
13.graph.addEdge(ben, ross);
14.System.out.println(graph.getDistance(rachel, ross));
//should print 1
15.System.out.println(graph.getDistance(rachel, ben));
//should print 2
16.System.out.println(graph.getDistance(rachel, rachel));
//should print 0
17.System.out.println(graph.getDistance(rachel, kramer));
//should print ‐1
FriendshipGraph
要实现的方法
- addVertex
- addEdge
- getDistance
数据存储
private HashMap<Person,HashSet> friends;
addVertex 向friends中添加键值person,集合为空的元素
addEdge 向两个person对应的集合中添加彼此
getDistance 使用广搜方案
if(friends.get(p1)==null){
System.out.println("查找的第一个人不在关系网中");
return -11;
}
if(friends.get(p2)==null){
System.out.println("查找的第二个人不在关系网中");
return -12;
}
int distance=0;
HashSet<Person> s=new HashSet<Person>();
HashMap<Integer,HashSet<Person>> m=new HashMap<>();
m.put(0,new HashSet<Person>());
m.get(0).add(p1);
do{
if(m.get(distance).contains(p2)){
return distance;
}else{
s.addAll(m.get(distance));
m.put(distance+1,new HashSet<Person>());
for(Person p:m.get(distance)){
if(friends.get(p).size()!=0) {
for (Person _p : friends.get(p)) {
if(!s.contains(_p)){
m.get(distance + 1).add(_p);
}
}
}
}
}
}while(m.get(++distance).size()!=0);
return -1;
Preson
仅仅保留了名字之后可以根据需要添加其他内容
String Name;
public Person(String Name) {
this.Name = Name;
}
各部分完整代码
MagicSquare
isLegalMagicSquare
public static boolean isLegalMagicSquare(String fileName) {
int row = 0,matrix_n = 0;
ArrayList<String> nums;
ArrayList<ArrayList<Integer>> matrix = new ArrayList<ArrayList<Integer>>();
HashSet<Integer> have=new HashSet<Integer>();
try {
Scanner input = new Scanner(new File(fileName));
while(input.hasNextLine()) {
nums = new ArrayList<String>(List.of(input.nextLine().split("\t")));
row++;
if (matrix_n == 0) matrix_n = nums.size();
else if (matrix_n != nums.size()) {
System.out.print(row);
System.out.println("行间数字个数不等,应以'\\t'进行区分");
return false;
}
try {
ArrayList<Integer> num_row=new ArrayList<Integer>();
for (int i = 0; i < matrix_n; i++) {
int value = Integer.parseInt(nums.get(i));
num_row.add(value);
if(!have.contains(value)){
have.add(value);
}else{
System.out.println("重复数字");
return false;
}
if (value <= 0) {
System.out.println("存在输入不为正数");
return false;
}
}
matrix.add(num_row);
} catch (NumberFormatException e) {
System.out.println("存在不为整数的输入");
return false;
}
}
}catch(FileNotFoundException e){
System.out.println("文件读取异常");
return false;
}
if(row != matrix_n) {
System.out.println("行列数目不等");
}
if(matrix_n==0){
System.out.println("无输入");
return false;
}
int sum = 0,sum1 = 0, k1, k2;
int[] num=new int[matrix_n];
k1=matrix.get(0).get(0);
k2=matrix.get(0).get(matrix_n-1);
for(int i=0;i<matrix_n;i++){
num[i]=matrix.get(0).get(i);
sum+=matrix.get(0).get(i);
}
for(int i = 1; i < matrix_n; i++) {
k1 += matrix.get(i).get(i);
k2 += matrix.get(i).get(row - i - 1);
for(int j = 0; j < matrix_n; j++) {
sum1 += matrix.get(i).get(j);
num[j]+=matrix.get(i).get(j);
}
if(sum1 != sum){
System.out.println(i);
System.out.println("行和与之前的行和不等");
return false;
}
sum1=0;
}
for(int i=0;i<matrix_n;i++){
if(sum!=num[i]){
System.out.println(i);
System.out.println("列和与行间不等");
return false;
}
}
if(k1 != sum || k2 != sum) {
System.out.println("对角线和值与行列和不等");
return false;
}
return true;
}
generateMagicSquare
public static boolean generateMagicSquare(int n) {
try {
int[][] magic = new int[n][n];
int row = 0, col = n / 2, i, j, square = n * n;
for (i = 1; i <= square; i++) { //从0行 n/2列开始赋值斜向右上
magic[row][col] = i; //将对应行列的值赋值为i
if (i % n == 0) //当赋n个值后行下标加一
row++;
else {
if (row == 0)//为第0行时即行到顶将行下标赋值为n-1即最下
row = n - 1;
else//如果不在第0行则行减一
row--;
if (col == (n - 1))//如果在最右列则列下标移到最左边=
col = 0;
else//若不在最右列向有移动一位
col++;
}
}
File f = new File("E:/code/Java/Lab1/src/P1/6.P1.txt");
FileOutputStream fop = new FileOutputStream(f);
OutputStreamWriter writer = new OutputStreamWriter(fop, StandardCharsets.UTF_8);
for (i = 0; i < n; i++) {
for (j = 0; j < n-1; j++)
writer.append(magic[i][j] + "\t");
writer.append(magic[i][n-1]+"\n");
}
writer.close();
fop.close();
return true;
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("数组下标超出范围,请输入一个奇数");
}catch(NegativeArraySizeException e){
System.out.println("数组大小不合法,请输入正数");
}catch(FileNotFoundException e){
System.out.println("打开文件异常");
}catch(UnsupportedEncodingException e){
System.out.println("写文件编码异常");
}catch(IOException e){
System.out.println("写文件异常");
}
return false;
}
Turtle
drawSquare
public static void drawSquare(Turtle turtle, int sideLength) {
turtle.forward(sideLength);//前进
turtle.turn(90);//向右转90度
turtle.forward(sideLength);
turtle.turn(90);
turtle.forward(sideLength);
turtle.turn(90);
turtle.forward(sideLength);
}
calculateRegularPolygonAngle
public static double calculateRegularPolygonAngle(int sides) {
if(sides>2) {
return (sides - 2) * 180.0 / sides;
}else{
throw new IllegalArgumentException("参数错误");
}
}
calculatePolygonSidesFromAngle
public static int calculatePolygonSidesFromAngle(double angle) {
int i;
for(i=3;angle*i>(i-2)*180.0;i++);
if(angle*i-(i-2)*180>0.000001){
throw new IllegalArgumentException("参数不合法");
}
return i;
}
drawRegularPolygon
public static void drawRegularPolygon(Turtle turtle, int sides, int sideLength) {
if(sides>2){
double angle=180-TurtleSoup.calculateRegularPolygonAngle(sides);
for(int i=0;i<sides;i++){
turtle.turn(angle);
turtle.forward(sideLength);
}
}else{
throw new IllegalArgumentException("参数不合法");
}
}
calculateBearingToPoint
public static double calculateBearingToPoint(double currentBearing, int currentX, int currentY, int targetX, int targetY) {
double angle = Math.atan2(targetY - currentY, targetX - currentX) * 180.0 / Math.PI;
angle=angle<0?-angle:360-angle;
return (angle-currentBearing+90)%360;
}
calculateBearings
public static List<Double> calculateBearings(List<Integer> xCoords, List<Integer> yCoords) {
if(xCoords.size()!=yCoords.size()){
throw new IllegalArgumentException("输入参数异常");
}
double angle=0,angle1=0;
List<Double> ans=new ArrayList<Double>();
for(int i=0;i<xCoords.size()-1;i++){
angle1=calculateBearingToPoint(angle,xCoords.get(i),yCoords.get(i),xCoords.get(i+1),yCoords.get(i+1));
angle+=angle1;
ans.add(angle1);
angle1=0;
}
return ans;
}
convexHull
public static Set<Point> convexHull(Set<Point> points) {
if(points.size()<=3) return points;
Set<Point> ans=new HashSet<Point>();
Double angle=360.0,length=0.0,angle2=0.0;
Point a=new Point(Double.MAX_VALUE,Double.MAX_VALUE);
for(Point i:points){
if(i.y()<a.y()||i.y()==a.y()&&i.x()<a.x()) a=i;
}
ans.add(a);
for(int i=0;i<points.size();i++){
angle=360.0;
length=0.0;
Double angle1=360.0,length1=0.0;
Point next=null;
for(Point k:points){
if(Math.abs(k.x()-a.x())>0.00000001||Math.abs(k.y()-a.y())>0.00000001) {
angle1=Math.atan2(k.y()-a.y(),k.x()-a.x());
angle1 = angle1 < 0 ? 360 + angle1 : angle1;
length1 = Math.sqrt((k.x() - a.x()) * (k.x() - a.x()) + (k.y() - a.y()) * (k.y() - a.y()));
if ((angle1 < angle || Math.abs(angle - angle1) < 0.000000001 && length < length1)&&angle1>angle2) {
angle = angle1;
length = length1;
next = k;
}
}
}
if (next != null) {
if (ans.contains(next)) {
break;
}
ans.add(next);
a=next;
angle2=angle;
} else {
break;
}
}
return ans;
}
drawPersonalArt
public static void drawPersonalArt(Turtle turtle) {
for(int i=10;i<150;i+=20){
int j=36;
for(PenColor color:PenColor.values()){
turtle.color(color);
drawSquare(turtle,i);
turtle.turn(j);
}
}
}
FriendshipGraph
FriendshipGraph
使用了广搜
package P3;
import java.util.HashMap;
import java.util.HashSet;
public class FriendshipGraph {
private HashMap<Person,HashSet<Person>> friends;
public int init=0;
public FriendshipGraph(){
friends=new HashMap<Person,HashSet<Person>>();
init =1;
}
/**
*
* @param p isn't exist forever
* @return success is 0 and failure is -1
*/
public int addVertex(Person p){
if(friends.get(p)==null){
friends.put(p,new HashSet<Person>());
return 0;
}else{
System.out.println("该角色已存在存在");
return -1;
}
}
/**
*
* @param p1 is the start point and exist forever
* @param p2 is the end point and exist forever
* @return success is 0 , the first person isn't exist is 1, the second person isn't exist is 2,the edge is exist is -1
*/
public int addEdge(Person p1,Person p2){
if(friends.get(p1)==null){
System.out.println("第一个人不在关系网中");
return 1;
}
if (friends.get(p2)==null){
System.out.println("第二个人不在关系网中");
return 2;
}
if(friends.get(p1).contains(p2)){
System.out.println("该关系已存在");
return -1;
}else {
friends.get(p1).add(p2);
return 0;
}
}
/**
*
* @param p1 is the start point and exist forever
* @param p2 is the end point and exist forever
* @return success is the shortest distance from p1 to p2 the first person isn't exist is -11, the second person isn't exist is -12,the edge isn't exist is -1
*/
public int getDistance(Person p1,Person p2){
if(friends.get(p1)==null){
System.out.println("查找的第一个人不在关系网中");
return -11;
}
if(friends.get(p2)==null){
System.out.println("查找的第二个人不在关系网中");
return -12;
}
int distance=0;
HashSet<Person> s=new HashSet<Person>();
HashMap<Integer,HashSet<Person>> m=new HashMap<>();
m.put(0,new HashSet<Person>());
m.get(0).add(p1);
do{
if(m.get(distance).contains(p2)){
return distance;
}else{
s.addAll(m.get(distance));
m.put(distance+1,new HashSet<Person>());
for(Person p:m.get(distance)){
if(friends.get(p).size()!=0) {
for (Person _p : friends.get(p)) {
if(!s.contains(_p)){
m.get(distance + 1).add(_p);
}
}
}
}
}
}while(m.get(++distance).size()!=0);
return -1;
}
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
}
}
Person
之后可以加入其他信息
package P3;
public class Person {
String Name;
public Person(String Name) {
this.Name = Name;
}
}