HIT 软件构造 2019spring Lab-1 总结


w(゚Д゚)w大的同学们你们好!老师又让我们写博客了,终于写完了实验报告,程序健壮性需要考虑的东西很多,到最后脑子一片浆糊,边写报告边写博客两边懵逼,(@_@😉

抵制一切盲目互佬的行为,构建良好的开源生态环境

查阅足迹

先不多废话了,也许在写完报告后回来完善博客,这里先记录一下在实验过程中的查阅足迹:

  1. Eclipse写JAVA的相对路径总结
  2. Java throw:异常的抛出
  3. Java读取txt文件和写入txt文件
  4. java中向上向下及四舍五入取整的方法,double型强制转换成int型的取整方法?
  5. 删除github中某个文件夹
  6. git中出现“non-fast-forward”errors时的终极解决方案
  7. 关于HashSet的各种使用方法总结
  8. 凸包问题的五种解法
  9. java中数据字典的使用
  10. 菜鸟教程-Java Stack 类
  11. Java队列(Queue)了解及使用
  12. 菜鸟教程-Java 实例 - 队列(Queue)用法
  13. No JUnit tests found’ in Eclipse
  14. 在JAVA中如何根据枚举索引值来获取枚举值(范型适用)
  15. java.util.ConcurrentModificationException 异常问题详解

【坑】字符串中的路径表示法

在Java中,或其它某些编程语言中,我们再进行文件操作时,常常要用到一个字符串变量filepath来表示文件路径,小伙伴们注意了,字符串中可是有转义字符的噢~

相对路径

如果你想用相对路径寻址的话,当然这是值得推荐的方式,同平台的可移植性较高。如果你用的Eclipse IDE的话,那么项目路径默认是在项目文件夹内,在当前状态下,诸如src bin文件夹都是和当前路径平级的,这些文件夹下再寻址就要用到斜杠或者反斜杠了。

可以注意到,Windows系统中,资源管理器中的文件路径显示都是反斜杠 \,像这样:
在这里插入图片描述
而反斜杠在Java字符串中属于转义字符(在markdown标记语言和其它许多编程语言中都是这样),那么在用反斜杠时,切记连续键入两个反斜杠,以忽略转义!

不过我们也可以用正斜杠 / 来写目录路径,虽然在Windows中使用反斜杠,而正斜杠通常用于Linux系统中。

绝对路径

不建议这种方式来描述文件位置,可移植性差,极差。但它的斜杠要求和上面说的相同,只是你需要从根目录开始逐级寻址。

【记】读写文件时的善后工作

在读写文件时,我们可能会创建这样的变量

			FileReader reader = new FileReader(fileName);
			BufferedReader br = new BufferedReader(reader);

记得在程序可能退出的位置之前,打上这样的两行,或者只打下面的第二行:

br.flush();
br.close();

第一行的意思大概是清空缓冲区,这里面有很多讲究,没有仔细探究。

第二行是把这个I/O流关闭,可以防止资源泄露,这行还是很重要的。

【记】Git命令初步

在初识Git时,有一些概念有必要了解一下。

  1. 本地仓库。本地仓库是在本地目录时用git init命令之后,生成的一个.git隐藏文件,它是你本次建立git仓库的仓库管理文件。其中保存着仓库历史记录等仓库相关信息。
  2. 远程仓库。远程仓库通常是在互联网上的一个服务器(很庞大的服务器),GitHub就属于一个服务器,也有人搭建自己的Git服务器。当你用git push命令把本地仓库推送到远程时,就把自己的本地仓库备份到了互联网上,如果你的仓库类型是public公开的话,其他人也可以很方便地浏览。
  3. 分布式版本控制系统。Git是典型的代表,与集中式不同,分布式的分布指的是既在本地保存了仓库,也在远程服务器保存了仓库,版本控制管理更加灵活,也是当下大势所趋。
  4. Git Bash。Git Bash是下载安装Git后,我们应用Git的主要工具,起始还可以使用Git GUI,但是初学不建议使用GUI操作,用Bash操作有利于我们学习代码指令。

在本次实验中,我们的Git操作是这样的:

  • 先按照实验环境声明创建远程仓库,然后在本地某个目录下打开Git Bash开始clone任务代码,记下目标仓库的HTTPS路径(或SSH路径),在相应目录下Git Bash中clone。clone前要先使用命令git init以初始化git仓库,然后clone,git clone https://github.com/rainywang/Spring2019_HITCS_SC_Lab1.git,然后任务相关代码就到了该目录下。

这里有可能遇到clone速度慢的情况,可以参考个人总结博客https://blog.csdn.net/qq_41662115/article/details/86993426.

  • 然后你可以做实验了。
  • 做完实验后开始提交。将远程仓库的HTTPS添加到标识符origin中。如果之前已经添加过,那么就会出现如下fatal提示:
    在这里插入图片描述
    这步是为了用origin表示远程仓库,相当于在远程和本地之间搭了一座桥。
  • 为了上传提交或更新提交我们在本地的项目开发,先将代码上传到暂存区,git add .即为将当前目录下所有文件上传到暂存区。
    在这里插入图片描述
  • 然后用git commit命令提交更新,-m参数后面可以添加描述本次提交信息的字串。
    在这里插入图片描述
  • 这时,本次提交就已经写入到本地Git管理历史中了。要想上传到远程仓库,用这个。可能要输入密码,这个密码是SSH密钥,是一种安全的信息传输协议。
    在这里插入图片描述
  • 然后在远程仓库中我们可以看到本次提交的信息,和更改状态。还可以查看本地的提交历史日志。下图给出的是我的最新的几次提交。
    在这里插入图片描述

【坑】已知正多边形内角求多边形边数

这是上一个问题的拓展,上一个问题描述是这样的

已知正多边形边数,求内角度数

上一个问题的解决方法,我们小学的时候就学过公式了,设内角度数为 α α α,边数为 n n n,那么有 α = 180 × ( n − 2 ) ÷ n α = 180 \times (n - 2) \div n α=180×(n2)÷n。这里面有一个问题是, n n n既然作为分母,结果难免会出现除不尽的情况,比如说正七边形,那么我们在Java中做浮点数除法时,得到的浮点数类型结果,必然不是精确值,但这种精确程度我们也可以接受了。

但是,在做这个问题的时候,我们要求边数 n n n,那么结果是一个整数,我们会出现浮点数舍入到整数的情况。在没有指定舍入方法的情况下,默认是向零舍入,正数向下,负数向上,这样如果你在计算过程中得到的 n n n是5.99…的话,那么结果就是5了,而不是应该舍入的6.

解决方案

  • 先加0.5,再强转为int类型
  • Math.round()方法来做四舍五入。
	public static int calculatePolygonSidesFromAngle(double angle) {
		int sides = (int) Math.round(360 / (180 - angle));
		return sides;
	}

【记】小乌龟转向问题

这段也算是一个比较麻烦的功能实现吧,跟二维平面上的几何关系有关,数学知识,直接把我的实验报告搬过来吧。

这部分要一个方法,calculateBearingToPoint,是在给定一个向量和y正半轴夹角,该向量是小乌龟的面朝向,再给定小乌龟的二维坐标,和目的坐标,求小乌龟想要到达目的地前,最少需要顺时针旋转的角度。

我在纸上对两给点的方位给出了8种情况的分析,最终合并8种情况为4种情况,两点互为上下或左右分别是两种情况,这种情况比较特殊,考虑起来很简单。再有就是两点并不互为上下左右,这样的话除了考虑当前点的朝向,计算旋转角度时还要考虑两点夹角,这里就把两点连线和水平线所夹的锐角作为参考,并认为这个锐角是-90°到90°之间的,记为β。这样的话,如果目标点在当前点的一三象限方位,那么β是正的,否则β是负的。合并四个方位发现,虽然一四象限和二三象限时的β正负各自不同,但是得到的结果是一样的,所以最后是四种情况。

我的情况分析是这样的:

  • 两点互为上下。
    • 目标点在上
      • 返回(360-α)%360
    • 目标点在下
      • α在0~180°
        • 返回180-α
      • α在180~360°
        • 返回540-α
  • 两点互为左右。
    • 目标点在左
      • α在0~270°
        • 返回270-α
      • α在270~360°
        • 返回630-α
    • 目标点在右
      • α在0~90°
        • 返回90-α
      • α在90~270°
        • 返回450-α
  • 目标点在左上或左下
    • α在0~270°
      • 270-α-β
    • α在270~360°
      • 630-α-β
  • 目标点在右上或右下
    • α在0~90°
      • 90-α-β
    • α在90~270°
      • 450-α-β

简单的角度图凑活看吧
在这里插入图片描述

【记】凸包问题

先描述一下问题。给定平面上一些点的二维坐标,要求一个尽可能小的点集,该集合中的点在平面上围成一个凸多边形,且该凸多边形包含给定的所有点。

6.031给出的凸包问题建议算法,可以用外包裹算法,Jarvis算法。这个算法是这样的,先找到给定点集的边缘点,比如说最左下的那个点,以它作为凸包的起点,然后寻找第二个点,第二个点应该满足和起点连线与x正半轴夹角最小,从第三个点开始遍历,从第一次遍历开始,将第一个找到的点记为P1,第二个找到的点记为P2,要寻找的下一个点记为P3,每次寻找P3时,应满足P3P2和P2P1的夹角最小,这样步进寻找,得到的边连接起来可以囊括所有的点,直到找到的P3回到起点为止。

在构建凸包过程中,对P3的寻找,可能会遇到P2P3直线上有多个点,那么就要找距离P2最远的那一个,放进凸包中。还有就是只有一个点和两个点的情况,这时规定给定点全是凸包中的点,这时凸包是一个点,或是一条线。

附上角度示意图。总是希望两向量夹角θ是最小的,并且取最远的那个点。
在这里插入图片描述

【记】关于迭代器Iterator

对Set,List,Map.keySet这种具有集合性质的集遍历时,我们通常会使用两种方式,一种是比较间接的for-each,另一种是创建一个Iterator对象,用它来做迭代工作。给出一个示例:

Iterator iterator = points.iterator();
while (iterator.hasNext()) {
	next = iterator.next();
	/* 找到给定点集最左下的点作为起点 */
	if (next.y() < p1.y())
		p1 = next;
	else if (next.y() == p1.y() && next.x() < p1.x())
		p1 = next;
}

它的迭代原理似乎是比较复杂的,简单介绍来说呢,就是用hasNext()判断迭代过程中,是否还有下一个迭代项,如果是,就继续迭代,否则退出。获取迭代对象用next()方法,这相当于一次步进。

问题在于,假设我们的某一迭代器的迭代工作由于某种原因中断了,比如说以达成目标,break跳出循环while,而在接下来的工作中还需要迭代这同一个对象,那么,我们的正确做法,应该是重置迭代器iterator,绝不能在没有重置的情况,继续使用它。

一个迭代器只能用于一次迭代工作,如果它被中断了(不能说终止,接下来可能还会用到),再回到它来迭代时,是继续断点迭代的。如果我们想重新遍历这一对象,就必须要iterator = XXX.iterator()来重置迭代器,开启全新的遍历过程。

【记】关系网络的距离求解

问题描述。规定一个无向图,顶点代表人,边代表边上的两点之间存在关系,若AB存在关系,则认定AB距离为1,若A到P有路,则认定AP距离为路的长度。若A到P无路,则认定AP距离为-1.

该问题的实质是图问题中的最短路径问题,要求的是源点到给定点的最短路径,图的各条边权值均为1,图为无向图,但是后期可能要扩展到有向图。点和边手动输入。要求两个人的距离,也就是从一个人到另外一个人的最短路长。

使用的算法可以用广度优先搜索算法,记录下源点到各点的路径,然后从目标点回溯的源点,对路长计数,得出答案。

【坑】使用迭代器进行迭代时,改变迭代对象

问题:在做getsmarter问题的时候,我选择了triadic closure来实现进一步的关注推测,对于三角关系的处理,我自然想到的是三层循环,判断三者关系,错误代码如下:

// BUGGY CODE
for(String A : guessFollowsGraphMap.keySet()) {
	for(String B : guessFollowsGraphMap.get(A)) {
		for(String C : guessFollowsGraphMap.get(B)) {
			if(guessFollowsGraphMap.get(B).contains(A) && guessFollowsGraphMap.get(C).contains(B)) {
				if(!guessFollowsGraphMap.get(A).contains(C))
					guessFollowsGraphMap.get(A).add(C);
				if(!guessFollowsGraphMap.get(C).contains(A))
					guessFollowsGraphMap.get(C).add(A);
			}
		}
	}
}

这里会爆出这样的异常
在这里插入图片描述
原因可以参考上述这位仁兄的博客,小弟在这里转载过来了
java.util.ConcurrentModificationException 异常问题详解

这个问题可能过于复杂,加上网上给出的多篇博客,都是单线程下对迭代对象remove,或者多线程下的解决方案,对此,我的代码中对迭代对象进行了add操作,没有找到合适的解决方案,不过最后还是换了一种思维,实现了triadic closure的变相实现方式,修改后代码如下:

HashMap<String, String> waitingAddedFollow = new HashMap<String, String>();
for(String A : guessFollowsGraphMap.keySet()) {
	for(String B : guessFollowsGraphMap.get(A)) {
		for(String C : guessFollowsGraphMap.get(B)) {
			if(guessFollowsGraphMap.get(B).contains(A) && guessFollowsGraphMap.get(C).contains(B)) {
				waitingAddedFollow.put(A, C);
			}
		}
	}
}
for(String A : waitingAddedFollow.keySet()) {
	String C = waitingAddedFollow.get(A);
	if(!guessFollowsGraphMap.get(A).contains(C))
		guessFollowsGraphMap.get(A).add(C);
	if(!guessFollowsGraphMap.get(C).contains(A))
		guessFollowsGraphMap.get(C).add(A);
}

【记】break跳出多层循环

当我们在使用多层for嵌套时,有可能要在内层循环使用break,希望跳出多层循环,但仅用break是做不到的。需要用到一个叫做循环标记的语法。比如:

firstloop: for(int i=1; i<10; i++)
	secondloop: for(int j=1; j<10; j++)
		thirdloop: for(int k=1; k<10; k++)
			break secondloop;

上述代码在执行到内层循环时,会直接跳出第二层循环,总的循环次数应该有

Eclipse的ctrl+shift+f贼强,有时间可以学习一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值