3.1.2 generateMagicSquare(). 1
3.2.1 Problem 1: Clone and import 2
3.2.2 Problem 3: Turtle graphics and drawSquare. 2
3.2.3 Problem 5: Drawing polygons. 2
3.2.4 Problem 6: Calculating Bearings. 2
3.2.5 Problem 7: Convex Hulls. 2
3.2.6 Problem 8: Personal art 2
本次实验通过求解四个问题,训练基本 Java 编程技能,能够利用 Java OO 开发基本的功能模块,能够阅读理解已有代码框架并根据功能需求补全代码,能够为所开发的代码编写基本的测试程序并完成测试,初步保证所开发代码的正确性。另一方面,利用 Git 作为代码配置管理的工具,学会 Git 的基本使用方法。
- 基本的 Java OO 编程
- 基于 Eclipse IDE 进行 Java 编程
- 基于 JUnit 的测试
- 基于 Git 的代码配置管理
实验环境:
系统:window10 64bit
ide:Eclipse IDE for Eclipse Committers 2022-03
编辑器:Visual Studio Code 2019
构建工具:Apache Ant,Travis CI
版本管理:git
代码托管:Github
配置过程:
从OracleJDK官网下载JDK并解压到系统目录,并在~/.bash_profile中配置PATH、CLASSPATH和JAVA_HOME。
在使用Eclipse进行Junit单元测试时会出现org.junit不存在的情况,主要是由于junit没有正确导入,只需要右键项目->Build Path->Add Library并选择junit,导入junit4即可。
P4的Main.java中的main方法在运行前会检查是否启用assertion。只需要在run->Run Configurations中的arguments选项卡下的VM arguments中添加JVM参数“-ea”即可。
有时P1的MagicSquare.java中的main方法读取txt包下的矩阵文件时可能会读取不到,这是因为运行时运行的是class字节码文件,而编译时txt文件没有被复制到编译文件的位置导致无法读取。这时读取目录即可采用相对项目根目录读取,new File("").getCanonicalPath()获取的就是项目的根目录,若要获取src下P1包中 的txt文件夹下的1.txt的路径即可写成new File("").getCanonicalPath() + “/src/P1/txt/1.txt”。
P2的TurtleSoup.java中有一个convexHull()方法,求一组给定点点最小凸包,可采用Wikipedia上的gift wrapping算法。P3的FriendshipGraph.java中有getDistance()方法,求给定两个点在有向图中的最短距离,可用邻接矩阵(List)存储邻接关系,并使用广度优先搜索,记录遍历的层数即可。P4的getMentionedUsers()方法,找出一组给定的tweet的text中提到(@-mentioned)的用户。可使用正则表达式匹配,按照给定的规则(1-140字符,有效字符为大小写字母、数字、下划线和-,且前后不得紧邻有效的字符),正则表达式为“(?<![A-Za-z0-9_-])@[A-Za-z0-9_-]{1,140}(?![A-Za-z0-9_-])”,其中使用了零宽断言来规定前后紧邻的字符。P4的guessFollowsGraph()方法猜测一组tweets中的关注关系,除了给定的@-mentioned可以判断以外,我还使用如果提到相同的话题(#-mentioned),那么两人互相关注的判断。
由于Lab0中要求脱离Eclipse独立构建,而Maven和Gradle都使用了配置式依赖导入,而且默认的目录结构与要求结构不符(要求src与test平级),故选用Apache Ant构建。
安装Apache Ant需要将下载下来的压缩包解压到系统目录,并在~/.bash_profile中配置环境变量ANT_HOME和PATH。
使用Ant构建需要在项目根目录下新建配置文件build.xml。
这里有一个问题是,由于在Eclipse中test测试类与其他文件都在同一包下,而项目中需要将测试文件与其它文件分开,单独放在test文件夹中。就需要编译成class时将test和src下的文件编译到同一个目录下,而且要保证src和test目录的包结构相同。
用Ant运行P2时可能会报“Cannot load com.apple.laf.AquaLookAndFeel”的错误,这是因为Ant使用默认的JVM运行时,无法加载系统的界面库,需要转换为系统的JVM运行。在build.xml中运行P2的java标签下加入属性fork=”true”即可。
使用Ant运行P4的Main和其它测试类同样需要开启断言。在对应的java标签和junit标签下加上” <jvmarg value="-ea"/>”标签即可。
由于Lab0还提到了Travis-CI自动构建,于是就尝试了一下。在项目根目录下新建.travis.xml文件,指定language、jdk、install和script。jdk需要指定为oraclejdk11,否则可能由于版本问题无法构建。Travis CI默认支持Maven、Gradle和Ant,取决于根目录下的配置文件。将Travis CI与Github关联,将项目push到Github后,Travis CI就会自动开启一个虚拟机,并按照配置文件构建项目并实时输出日志。
在这里给出你的GitHub Lab1仓库的URL地址。
https://github.com/ComputerScienceHIT/HIT-Lab1-L180300101
请仔细对照实验手册,针对四个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但无需把你的源代码全部粘贴过来!)。
为了条理清晰,可根据需要在各节增加三级标题。
要求验证某个读入的矩阵是否是幻方,读入的文件要求数字间以\t分割且都是正整数,幻方要求为方阵且行列对角线之和相等。
设计与实现:
首先初始化一个List<List<Integer>>二维List以存储矩阵,使用文件输入缓冲流BufferedReader从目标文件中读取一个矩阵,并将按行读入的String分割转换为List<Integer>,存入二维List中。该步骤可能由于文件不存在而造成IOException,或者存在负数、小数或者不是用“\t”分割而造成NumberFormatException。读取完成后验证该矩阵是否为方阵,如果不是,输出错误并返回false,最后再分别计算各行、各列以及对角线之和,如果相等,则为Magic Square,否则不是,输出错误并返回false。
结果:
以下为在main方法中调用五次isLegalMagicSquare()验证txt文本的结果:
以下检测矩阵1.txt
true
以下检测矩阵2.txt
true
以下检测矩阵3.txt
该矩阵不是方阵!
false
以下检测矩阵4.txt
文件数字不规范!请检查文件!
java.lang.NumberFormatException
at P1.MagicSquare.convertTointList(MagicSquare.java:116)
at P1.MagicSquare.readFromFile(MagicSquare.java:79)
at P1.MagicSquare.isLegalMagicSquare(MagicSquare.java:53)
at P1.MagicSquare.main(MagicSquare.java:24)
false
以下检测矩阵5.txt
文件数字不规范!请检查文件!
java.lang.NumberFormatException: For input string: "12673 12796"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.base/java.lang.Integer.parseInt(Integer.java:652)
at java.base/java.lang.Integer.parseInt(Integer.java:770)
at P1.MagicSquare.convertTointList(MagicSquare.java:115)
at P1.MagicSquare.readFromFile(MagicSquare.java:79)
at P1.MagicSquare.isLegalMagicSquare(MagicSquare.java:53)
at P1.MagicSquare.main(MagicSquare.java:24)
false
设计与实现:
给出的代码使用Merzirac法生成奇阶幻方:
- 首先向第一行正中的方格内填写1
- 以下依次向右上角的方格内填写2、3、4……
- 若右上角的方格内已经有数字,则向下移动一格继续填写
- 若右上角的方格超出矩阵的行,则移到矩阵下一列的最下端继续填写
- 若右上角的方格超出矩阵的列,则移到矩阵下一行的最左端继续填写
给出的代码没有对2.1步骤进行判断,而是使用当前填入的数字是否是行列数的倍数来决定,如果是行列数的倍数,则右上角必然有数字,需要下移。
流程图如下:
结果:
以下为main方法中调用generateMagicSquare()方法并使用3.1.1中的方法检验的结果:
调用参数为5时:
要求2:以下测试generateMagicSquare()方法:
17 24 1 8 15
23 5 7 14 16
4 6 13 20 22
10 12 19 21 3
11 18 25 2 9
以下检测矩阵6.txt
true
调用参数为6时:
要求2:以下测试generateMagicSquare()方法:
请输入偶数值!
java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 6
at P1.MagicSquare.generateMagicSquare(MagicSquare.java:199)
at P1.MagicSquare.main(MagicSquare.java:37)
false
调用参数为-7时:
要求2:以下测试generateMagicSquare()方法:
请输入正整数!
java.lang.NegativeArraySizeException: -7
at P1.MagicSquare.generateMagicSquare(MagicSquare.java:196)
at P1.MagicSquare.main(MagicSquare.java:37)
false
该题估计起源于Python的turtle库,控制一个小乌龟在屏幕上爬行,画出各种图案。MIT用Java改写,提供了基础接口的实现(如forward和turn等),需要我们自己实现更加高级一点的接口。
在本地新建一个文件夹,使用终端cd到该文件夹中,git clone,将整个项目clone下来(git不允许下载单文件)。并将其中的P2文件夹复制到自己的项目目录中,在src下与P1平级。接着在Eclipse中继续开发即可。
-
-
- Problem 3: Turtle graphics and drawSquare
-
该Problem要求使用现有的接口(forward和turn)实现drawSquare方法,要求该方法调用时,根据给定的变长画出一个正方形。使用一个简单的for循环即可解决。
该Problem需要实现两个方法:calculateRegularPolygonAngle()和drawRegularPolygon()方法,并使用Junit测试。calculateRegularPolygonAngle()方法要求根据给定的边数作为参数,计算出对应的正多边形的内角。只需要使用公式insideAngle=(sides-2)×180sides 即可,由于insideAngle为double类型,而参与计算的数字都是int整型,如果直接计算可能会发生截断,需要在写成2d或180d即可转为浮点运算。drawRegularPolygon()方法要求以变长与边数作为参数,控制turtle画出正多边形。使用循环即可,计算角度时可调用calculateRegularPolygonAngle()方法计算,唯一要注意的就是turtle旋转的角是外角,而不是计算出来的内角。
该Problem需要实现两个方法:calculateBearingToPoint()和calculateBearings()方法,并进行Junit测试。calculateBearingToPoint()方法要求计算在当前点、当前朝向的情况下,顺时针转向目标点所需要转动的角度。给定的参数有当前朝向的度数、当前点的X与Y值和目标点的X与Y值,当前朝向的度数以向上方为0度。基本是个数学问题,比较繁琐,分多种情况讨论即可,需要用到反正切函数Math.atan2方法。calculateBearings()方法的参数为两个List,第一个List中为X的值,第二个为Y的值,需要返回一个List,包含所有需要转动的角度。只需要遍历两个集合,调用calculateBearingToPoint()方法即可,可设置一个变量里路当前朝向的角度。
该Problem涉及一个著名的图形学概念:凸包。凸包可以理解为给定的点集的一个子集,该子集中点点连线可以将全集都囊括在内。要求实现的convexHull()方法就是在给定的Point的Set中寻找一个最小的凸包。由于是最小的凸包,如果同一条直线上的多个点都是凸包边界上的点,只取两端的点以保证最小。可使用gift wrapping(礼物包装)算法。从最左上(左下)角的点开始,逐个扫描点集中的点,选取与当前朝向偏角最小的点加入结果集,并将其作为下一个当前点继续循环。如果有多个点都是最小转角的点,则选择距离最远的点即可。
实现drawPersonalArt(),可以自己画一些有趣的东西,可作出一些简单的图形之后偏移一个较小的角度,并循环,即可作出较美观的花纹。
在项目的根目录打开终端,使用git add .命令将所有文件的改变存储在暂存去,再使用git commit -m “提交内容”将暂存区的内容提交到本地的版本库,创建一个新的版本。最后使用git push origin master将本地版本库的master分支推送到远程服务器(origin)的master分支上,这里的远程服务器就是GitHub的服务器。
要求实现一个模拟的社交网络,形成一个图结构,可以向图中添加点(人)和边(关系),并且可以计算各个点之间最短的距离。
-
-
- 设计/实现FriendshipGraph类
-
FriendshipGraph类是社交网络的抽象,描述了一个社交网络关系图。描述图有两种方法:邻接表和邻接矩阵,为了计算距离方便使用邻接矩阵。由于图中的点数不是固定的,故使用List嵌套,描述邻接矩阵的属性类型为List<List<Integer>>,并添加一个List<Person>来存储当前的图中的点。所有属性私有,并添加相应的setter/getter方法。根据示例代码,该类需要实现addVertex()方法、addEdge()方法和getDistance()方法。addVertex()方法用于向图中添加一个点,由于不可重复添加,添加之前需要首先验证该点是否已存在,是则跑出异常。添加人还需要扩充邻接矩阵。addEdge()方法是在两点之间添加一条边,由于要求中说明添加社交关系需要调用addEdge()两次,所以addEdge()方法仅添加一条有向边。getDistance()方法用于计算两个点之间点最短距离。由于有向路的权值都为1,仅需要采用广度优先遍历,记录遍历的层次即可,遍历到目标点时到层次即为最短的距离。
-
-
- 设计/实现Person类
-
该类为对社交网络中的点(人)点抽象。根据示例代码,仅需添加属性name并加入getter/setter即可。
-
-
- 设计/实现客户端代码main()
-
1
2
0
-1
和预期输出相同
-
-
- 设计/实现测试用例
-
测试用例使用如下的图:
共测试两个方法:getDistance()方法和addVertex()方法。getDistance()测试是否可以得到合理的输出。addVertex()主要测试当添加相同的人时抛出异常。
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 | 时间段 | 任务 | 实际完成情况 |
2022-05-01 | 13:00~ | 编写完成问题1,并添加了Ant自动化构建 编写完成问题2,并完成了Travis CI在线 编写完成问题3,将junit整合进ant | 按计划完成 |
2022-05-03 | 14:00~ | 审查全部实验,补上测试用例 修改了凸包问题的算法 | 按计划完成 |
2022-05-06 | 18:00~ | 完成实验报告 | 按计划完成 |
遇到的困难 | 解决途径 |
不懂convexHull()的意义与实现算法 | Google搜索,Wikipedia搜索 |
不会使用正则表达式匹配字符串 | 菜鸟教程速成 |
深入理解了工程的构建方式与在线持续集成(CI)的方式,代码版本管理方式,单元测试的编写,更接近现代工程的管理。了解了Git的运作方式,提交和版本的概念与运用等。
Java语言被设计为一门完全的面向对象的语言,故思维模式比较接近正常人的思维过程,易于理解和掌握。Java的IDE使用的Eclipse是一个开源的项目,由Eclipse基金会负责维护,由于是开源项目,自我感觉没有商业软件Intellij IDEA用起来顺手。Git是版本管理软件,而Github是Git项目的托管平台,Github Classroom确实方便了计算机专业的教学。CMU和MIT的题目出的比较有水平,简单的作业融合了比较前沿的知识和背景,简单而有深度。实验的工作量较大,但不是很难,而且时间充裕,足够完成并优化。软件构造确确实实是一门教授写出更精彩代码的课程,实验都这么有水平