软件构造lab1
1 实验目标概述
本次实验通过求解三个问题,训练基本 Java 编程技能,能够利用 Java OO 开
发基本的功能模块,能够阅读理解已有代码框架并根据功能需求补全代码,能够
为所开发的代码编写基本的测试程序并完成测试,初步保证所开发代码的正确性。
另一方面,利用 Git 作为代码配置管理的工具,学会 Git 的基本使用方法。
l 基本的 Java OO 编程
l 基于 Eclipse IDE 进行 Java 编程
l 基于 JUnit 的测试
l 基于 Git 的代码配置管理
2 实验环境配置
简要陈述你配置本次实验所需开发、测试、运行环境的过程,必要时可以给出屏幕截图。
特别是要记录配置过程中遇到的问题和困难,以及如何解决的。
在这里给出你的GitHub Lab1仓库的URL地址。
https://github.com/ComputerScienceHIT/HIT-Lab1-120L020509
3 实验过程
请仔细对照实验手册,针对四个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但无需把你的源代码全部粘贴过来!)。
为了条理清晰,可根据需要在各节增加三级标题。
3.1 Magic Squares
此程序需要根据给出的文件路径读取文件,并按规定的输入格式进行拆分处理,判断输入是否满足格式。在几次循环中判断各个行、列、对角线之和是否相等,返回表示是否为magic square的boolean变量。
3.1.1 isLegalMagicSquare()
首先创建一个BufferReader对象,用bufferedReader.readLine()方法按行读取文件,用.split("\t")分割数据,并通过正则表达式匹配判断是否符合输入格式。若符合格式,则使用Integer.valueOf()将输入转换为整数,储存在一维动态数组line中,每读取完一行,将这一行储存在二维动态数组matrix中,从而实现了从文件到二维数组matrix之间的转化。
然后对矩阵matrix进行遍历,在几次循环中判断各个行、列、对角线之和是否相等,是否有重复数字,矩阵大小是否为n*n。
3.1.2 generateMagicSquare()
流程图:
解释:程序根据n的大小生成一个二维数组,并在for循环中遍历n*n次,给每个位置赋值。遍历之前会将开始位置赋初值。在第i次遍历中,先将当前位置赋值为i,然后对i的大小进行判断,如果i是n的整数倍,则下移一行;否则上移一行并右移一列,若超过边界则移动到对侧。
ArrayIndexOutOfBoundsException:用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。
产生该异常原因:当n为偶数时,存在i % n == 0和row == n - 1同时成立的情况,此时执行到if (i % n == 0)row++;会导致row = n,从而出现数组越界。
NegativeArraySizeException:如果应用程序试图创建大小为负的数组,则抛出该异常。
产生该异常原因:magic[][]初始化时的大小为n*n,如果输入的n为负数,将会尝试创建一个大小为负数的数组,显然不合法。
3.2 Turtle Graphics
需要按照网站上的实验手册逐步完善turtle的各个方法并执行测试,构建一个完整的Turtle类。
3.2.1 Problem 1: Clone and import
在获取P1的txt文件时已经通过git clone https://github.com/rainywang/Spring2022_HITCS_SC_Lab1命令克隆了包括P1和P2的整个文件夹。
Git仓库已在完成P1时创建完成,故只需要将clone下来的P2文件夹复制到src文件夹下。
使用git管理本地开发主要使用git status、git add .、git commit -m “”、git push四个指令。
3.2.2 Problem 3: Turtle graphics and drawSquare
要求调用forward和turn画正方形,只需要画四条直线,四次转向90°(第四次转向可有可无),代码如下:
turtle.forward(sideLength);
turtle.turn(90);
turtle.forward(sideLength);
turtle.turn(90);
turtle.forward(sideLength);
turtle.turn(90);
turtle.forward(sideLength);
turtle.turn(90);
也可以通过循环来实现。
for (int i = 0; i < 4; i++){
turtle.forward(sideLength);
turtle.turn(90);
}
3.2.3 Problem 5: Drawing polygons
- 只需要返回多边形内角公式计算后的结果,需要注意返回值的类型是double。代码如下:return (double)180 * (sides - 2) / sides;
- 说明中要求右键TurtleSoupTest.java选择Run As,但IDEA中没有这个选项,所以我打开了TurtleSoupTest.java,在calculateRegularPolygonAngleTest方法左侧点击运行按钮,结果如下:
- 要求破坏写好的calculateRegularPolygonAngle方法,故将代码修改为return 180 * (sides - 2) / sides;,再次运行calculateRegularPolygonAngleTest方法,出现以下错误提示信息:
- 恢复calculateRegularPolygonAngle方法。再次执行测试以验证正确性:
- 调用calculateRegularPolygonAngle获得转向角度,通过for循环画出sides条边。代码如下:
double angle = 180 - calculateRegularPolygonAngle(sides);
for (int i = 0; i < sides; i++){
turtle.forward(sideLength);
turtle.turn(angle);
}
在main方法中通过drawRegularPolygon(turtle,8,40);尝试画边长为40的正八边形,结果如下:
3.2.4 Problem 6: Calculating Bearings
a.通过向量夹角公式计算相应的转角,代码如下:
int dx = targetX - currentX;
int dy = targetY - currentY;
double vortexBearing = Math.toDegrees(Math.acos((double)dy / Math.sqrt((double)(dx * dx + dy * dy))));
if (dx < 0) {
vortexBearing = 360.0 - vortexBearing;
}
return (vortexBearing - currentBearing + 360.0) % 360.0;
需要注意的是要根据dx的大小、vortexBearing - currentBearing的正负对结果进行一些处理。
b.调用calculateBearingToPoint计算转角,代码如下:
int size = xCoords.size();
double currentBearing = 0;
int currentX = xCoords.get(0),currentY = yCoords.get(0);
List<Double> ans = new ArrayList<Double>();
for (int i = 1; i < size; i++){
int targetX = xCoords.get(i),targetY = yCoords.get(i);
double temp = calculateBearingToPoint(currentBearing,currentX,currentY,targetX,targetY);
ans.add(temp);
currentX = targetX;
currentY = targetY;
currentBearing = (currentBearing + temp) % 360;
c.测试结果如下:
3.2.5 Problem 7: Convex Hulls
根据对题意的分析,结合测试用例,当点数小于4时,直接返回输入的点。
使用gift-wrapping algorithm来计算凸包,核心代码如下:
public static double cross(Point p, Point q, Point r) {
return (q.x() - p.x()) * (r.y() - q.y()) - (q.y() - p.y()) * (r.x() - q.x());
}
public static Set<Point> convexHull(Set<Point> points) {
Set<Point> ans = new HashSet<Point>();
int n = points.size();
if (n < 4) return points;//若无法构成封闭图形或构成三角形,返回points
List<Point> temp = new ArrayList<Point>();//将points中的point保存到ArrayList中,方便遍历
for (Point t : points){
temp.add(t);
}
int leftMost = 0;//找出最左下侧的点作为起点
for (int i = 0; i < n; i++) {
if (temp.get(i).x() < temp.get(leftMost).x() || temp.get(i).x() == temp.get(leftMost).x() && temp.get(i).y() < temp.get(leftMost).y()) {
leftMost = i;
}
}
ans.add(temp.get(leftMost));
//Set<Integer> visited = new HashSet<Integer>();
int p = leftMost;
do {
int q = (p + 1) % n;
for (int r = 0; r < n; r++) {
//if (visited.contains((r))) continue;
//如果r在pq的右侧,或pqr共线且r距离p更远,则q = r
if (cross(temp.get(p), temp.get(q), temp.get(r)) < 0 ||
cross(temp.get(p), temp.get(q), temp.get(r)) == 0 &&
Math.pow(temp.get(r).x() - temp.get(p).x(),2) + Math.pow(temp.get(r).y() - temp.get(p).y(),2) > Math.pow(temp.get(q).x() - temp.get(p).x(),2) + Math.pow(temp.get(q).y() - temp.get(p).y(),2)) {
q = r;
}
}
//visited.add(q);
ans.add(temp.get(q));
p = q;
}while (p != leftMost);
return ans;
}
3.2.6 Problem 8: Personal art
选择用多种颜色画多个圆。代码如下:
for (int i = 0; i < 10; i++){
turtle.color(PenColor.values()[i]) ;
for (int j = 0; j < 10; j++){
drawRegularPolygon(turtle,360,1);
turtle.turn(3.6);
}
}
效果如下:
3.2.7 Submitting
在实验开始时已经通过git remote add命令添加远程库。要通过Git提交当前版本到GitHub上的Lab1仓库,只需要执行git add . + git commit -m “” + git push这三条指令即可。
3.3 Social Network
任务要求实现FriendshipGraph和Person类,使其满足给定的main方法的调用。
3.3.1 设计/实现FriendshipGraph类
需要按要求编写addVertex、addEdge、getDistance方法。
使用HashMap<Person,ArrayList<Person>>这种数据结构,可以同时用来保存顶点和边。addVertex和addEdge都通过对(人,出边)的键值对的修改来实现。
getDistance方法按照要求,使用广度优先搜索进行遍历,判断两点之间距离。
异常处理:
在addVertex方法中判断对应的名字是否已经添加,如果已经添加,则打印错误提示信息。
在addEdge方法中判断对应的边是否已经添加,如果已经添加,则打印错误提示信息。
在getDistance方法中判断对应的两个人是否已经添加,如果有人未添加添加,则打印错误提示信息。
3.3.2 设计/实现Person类
Person类中只需要保存每个人的名字,所以代码如下:
class Person {
private String name;
Person(String name){
this.name = name;
}
}
3.3.3 设计/实现客户端代码main()
main的代码已在实验手册中给出。
3.3.4 设计/实现测试用例
addVertex主要测试加入重复的人名是否按设计打印错误提示
addEdge主要测试缺少顶点或加入重复边的异常处理
getDistance主要测试是否能够正常计算距离,包括0,-1等特殊情况
要获取打印的错误提示,需要通过System.setOut(new PrintStream(bytes))来实现。
结果:
4 实验进度记录
日期 |
时间段 |
任务 |
实际完成情况 |
2022-04-30 |
23:00-24:00 |
编写问题1的isLegalMagicSquare核心功能,理解generateMagicSquare的功能 |
延期半小时完成 |
2022-05-01 |
17:00-18:30 |
完善了问题1的异常处理等部分并编写报告 |
按计划完成 |
2022-05-01 |
19:00-24:00 |
完成问题2并编写报告 |
基本按计划完成,但没有加异常处理 |
2022-05-02 |
13:00-15:40 |
完善问题2,完成问题3的核心部分 |
按计划完成 |
2022-05-04 |
14:00-16:00 |
完善问题3的异常处理等部分 |
按计划完成 |
2022-05-05 |
16:00-16:45 |
编写问题3测试 |
按计划完成 |
2022-05-08 |
16:00-17:00 |
完善报告 |
按计划完成 |
5 实验过程中遇到的困难与解决途径
遇到的困难 |
解决途径 |
使用常用的FileInputStream难以实现文件的按行读取 |
参考博客https://blog.csdn.net/weixin_45865491/article/details/117619612,使用BufferedReader解决 |
问题3的getDistance 方法中出现了一个bug,导致程序陷入死循环 |
经过不断地调试和修改,发现在广度优先搜索初始化中间变量的时候,将其初始化为对边的引用,导致后续对栈操作时,对边进行了修改,进入死循环。 修改:将其初始化为一个空数组,并赋初值。 ArrayList<Person> reach = new ArrayList<Person>(); |
无法通过junit中的方法直接判断控制台输出是否正确 |
参考https://www.cnblogs.com/owczhlol/p/12655046.html,通过System.setOut(new PrintStream(bytes)); 获取到控制台输出。 |
6 实验过程中收获的经验、教训、感想
6.1 实验过程中收获的经验和教训(必答)
经验:原来Java使用的较少,经过本次试验,对Java相关的知识更加熟练。熟悉了git的相关操作。经过实践学习了junit的测试方法。
教训:不要轻易把一个对象赋值为另一个对象的引用,尤其是当需要修改这个对象时,否则很容易出bug。
6.2 针对以下方面的感受(必答)
(1) Java编程语言是否对你的口味?与你熟悉的其他编程语言相比,Java有何优势和不足?
Java比较合我口味。
与c语言相比,java没有指针不够灵活,性能较差,占用内存较大;但Java是面向对象的语言,适合大型软件开发,安全性好,可移植性好,而c语言可移植性不好,在不同平台上的宏定义不同,程序要根据机器的指令集、位宽等进行调整。
与python相比,java性能强很多,但没有python方便,开发效率较低。
(2) 关于Eclipse或IntelliJ IDEA,它们作为IDE的优势和不足;
我平时使用较多的是VS Code。与之比较而言,IDEA整合程度更高,集成了丰富的功能,能够提高开发效率,但功能丰富也带来了不足:启动时间较长。
(3) 关于Git和GitHub,是否感受到了它在版本控制方面的价值;
感受到了。在一次代码修改中发现修改的不好,通过git checkout命令很方便地将修改撤回。
(4) 关于CMU和MIT的作业,你有何感受;
实验要求很清晰,相关文档直接放在实验指导的对应位置,方便查看资料。
(5) 关于本实验的工作量、难度、deadline;
工作量较大,难度中等,这周恰好有考试,deadline比较紧。
(6) 关于初接触“软件构造”课程;
目前对软件构造的认识还不是很深刻,前几节课主要讲了概念方面的内容,希望在几次实验之后能把学到的概念应用在实践中。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下