BUAA_OO_第三单元总结
摘要
-
Homework9、10、11:本次作业最终需要实现一个社交关系模拟系统。可以通过各类输入指令来进行数据的增删查改等交互。
前言
本单元主要学习根据课程组提供的接口中的JML规格,实现自己的方法。 JML是一种形式化的、面向JAVA的行为接口规格语言,起初会比较陌生,但是在熟悉后还是比较直白易懂的,而且比较直接也非常详细地给出了思路,当然了,本次作业中有许多处的容器和数据结构是需要自己设计和优化的。这也是三次作业主要的工作量。不过总体而言,工作量相比前两单元还是少了很多。
设计策略
本单元总体上需要设计的地方不如求导单元和电梯单元多,我的设计流程如下
-
浏览各官方接口,理清各个方法目的。
-
分析各类的属性,进行合适的容器选取。
-
分析方法的复杂度,对根据规格实现可能超时的方法设计合理的优化算法。
所以设计也就集中在算法处。
基于JML规格来设计测试的方法和策略
本次的测试,最开始还是自己粗略检查是否完备地实现了JML所要求的各种琐碎的细节。当然了,这个层次的检查也只是可以纠正一些简单的手误或者低级错误。
进一步的测试则是在跑过测评机的大量数据后,与同学对拍实现的。由于作业中社交系统的功能十分复杂,且互相间的承接也比较重要,比如一定量的人和关系是其他一切功能的基础,所以纯随机出的测试样例,并没有办法直击要害,也出现了多次对拍后仍然暴毙的惨状。所以之后的数据增加了对边界情况的考虑和对性能的考察,也起到了不错的效果。在对拍中,参与的几个朋友都出现了不同程度的问题。
第一次作业
容器选择
因为事前看了一些博客,所以Person类的熟人和Network里的人直接选择无脑莽HashMap,Network里还加了一个HashMap,进行算法中使用的并查集的祖先的存储。
性能问题
本次需要考虑的性能问题应该就是isCircle和queryBlockSum这两个方法。
-
isCircle选择了并查集的算法。刚开始在跑评测机随机出来的大量数据时,出现了栈溢出的问题。之后学习了助教的博客之后做出了一些优化,解决了问题。一是在调fd时,要先存储fd出的祖先,再替换和返回。其次就是添加关系时合并两人的父节点。fd方法如下:
public int fd(int x)
{
if (fdlist.get(x) == x) {
return x;
}
int ans = fd(fdlist.get(x));
fdlist.replace(x, ans);
return ans;
}
-
queryBlockSum部分倒没有太认真地去设计,
可能是最后一个方法了就摸了。new了一个HashMap,遍历NetWork里的人,要是还没有他祖先id的key值,就塞进去,sum++,有了就算了,非常朴素。
Bug分析 & Hack策略
本次作业在最初弱测有个点卡了一会,后来看群里同学的讨论解决了。在加入新人时,没有设置其连通自己,导致后续出现了别的问题。给我的教训就是,在本单元根据JML实现各个方法时,很容易产生疏离感,让你感觉自己只在写手头上这个方法,而忘却了这个社交系统的整体性和各部分间的联系。就像高中刷的《小题狂做》,一上头就变成《小题狂错》!所以在写的时候还是要时刻提醒自己在写一个社交系统,不忘初心。
强测和互测均未出现bug。
五一旅游了所以没有Hack,房里出现的bug就是有同学的isCircle复杂度过高导致了CTLE。
第二次作业
容器选择
第二次作业新增了Group类和Message类,相关的数据还是使用HashMap存储。和某个同学交流的时候获知他某处使用了链表?思考后并没有觉得哪里使用链表比较合适。
性能问题
本次需要考虑的问题是对group中人员的ValueSum、AgeMean、AgeVar的相关计算,其中ValueSum是O()的复杂度,所以极易CTLE。
解决方式就是进行相关值的缓存,并实现相应的维护。
private int ageSum;
private int age2Sum;
public void addPerson(Person person) {
MyPerson p = (MyPerson) person;
people.put(p.getId(), p);
ageSum += p.getAge();
age2Sum += (p.getAge() * p.getAge());
}
public void delPerson(Person person) {
MyPerson p = (MyPerson) person;
int id = p.getId();
ageSum -= p.getAge();
age2Sum -= p.getAge() * p.getAge();
people.remove(id);
}
Bug分析 & Hack策略
因为JML规格更新的问题,最初出现了1111人之后继续添加的情况,不过更新了强测数据之后就没问题了。读JML还是要耐下性子,不能遗漏。
其余就是最开始没有缓存相应数据,出现了CTLE,缓存后修复了此Bug。
Hack部分根据评测机随机出的大量数据,Hack出了部分同学的CTLE。这部分其他确实很难出问题,只有复杂度这部分你可以做文章。
第三次作业
容器选择
新增了Emoji Message及其热度的存储,我使用了两个HashMap,现在看来其实是冗余的,两个完全可以合并为一个,还可以简化部分编码,所以也没必要无脑服从JML。
性能问题
本次的问题集中在sendIndirectMessage这一方法。我选择了堆优化的迪杰斯特拉算法,新建Edge类,属性为to和cost,表示到指定人的value。
public int dijkstra(int id1, int id2) {
HashMap<Integer, Integer> vis = new HashMap<>();
HashMap<Integer, Integer> dis = new HashMap<>();
Queue<Edge> que = new PriorityQueue<>();
que.add(new Edge(id1, 0));
dis.put(id1, 0);
while (!que.isEmpty()) {
Edge now = que.poll();
int u = now.getTo();
if (dis.get(u) < now.getCost()) {
continue;
}
if (vis.containsKey(u)) {
continue;
}
vis.put(u, 1);
for (Integer key:((MyPerson)getPerson(u)).getAcquaintance().keySet()) {
int next = key;
int cost = getPerson(u).queryValue(getPerson(key));
if (!vis.containsKey(next)) {
if (!dis.containsKey(next)) {
dis.put(next, dis.get(u) + cost);
que.add(new Edge(next, dis.get(next)));
} else if (dis.get(next) > dis.get(u) + cost) {
dis.replace(next, dis.get(u) + cost);
que.add(new Edge(next, dis.get(next)));
}
}
}
}
return dis.get(id2);
}
在写的过程中还遇到了一个问题,我最初使用了ArrayList来存储算法中的几个数据,直接通过id来映射,后来发现,id的范围是无法限制的,所以使用ArrayList一定是不可行的,遂换为HashMap。更换容器后,对算法中的部分细节进行了修改。
Bug分析 & Hack策略
第一个bug是在遍历HashMap过程中进行了删除,更换了removeIf的写法之后修复了bug。这个bug也让自己对迭代器及HashMap的一些基础知识有了进一步的理解和认识。
第二个就是引入emojiId之后,出现了两个id混淆的情况,这就是个人写代码时是否足够细致的问题。
强测中T了一个点,因为数据中各种指令都有,所以我也不清楚到底是哪部分的问题。自己的猜测是ValueSum的历史遗留问题,我采用的优化差不多是个伪优化,即不遍历整个people的HashMap,只遍历person的熟人,如果给person和people里的所有人加上关系,这个优化等于是个无效优化。其他就是可能自己的迪杰斯特拉还有需要优化的地方。
互测中未被hack,阅读房友的代码后发现正确性没有问题,要考虑复杂度的话,全是堆优化的迪杰斯特拉,也不知道咋下手,就没有再进一步hack。
心得体会
关于JML
不可否认,JML具有其高效性,能清晰地提出要求,划清边界,但是在本单元学习之后,也只是培养了根据JML实现功能的能力,要自己动手写的话可能是亚托谢特。
同时,本单元每次作业的几个主要任务的JML,长度还是有点恶心的,加上藏在注释里灰溜溜一片,看上去就不想读。不过一行一行理清头绪,分块后耐心思考,也还是能比较轻松地搞清楚其功能的。看多了之后也能总结出为了实现某种功能的JML的大致套路。
同时,不能让JML成为自己的桎梏,还是要结合自己的思考,如果确信,要敢于迈出JML。
关于评测
这样还是比较高效的,对拍过程确实找出了每个人各个地方出现的奇怪bug。同时,根据对拍出的异常结果迅速定位bug并解决的能力也有提升。
关于基础
一是java基础。当初寒假的预习作业让人产生了自己java行了的错觉,其实不然,很多基础部分都模棱两可。就像第三次作业的遍历HashMap后删除的问题,也是在细读讨论区加上自行搜索后才搞清了其中的原理。所以这部分还是不能忽略的。
二是算法基础。还没有系统学习算法,数据结构说实话也记忆模糊。本单元使用的算法,基本是只记得大概原理,具体实现还是现学。

浙公网安备 33010602011771号