OO2021-第三单元作业总结
Posted BUAA-Panzer
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OO2021-第三单元作业总结相关的知识,希望对你有一定的参考价值。
(1)总结分析自己实现规格所采取的设计策略
这里选取一些比较有代表性的方法,简单介绍一下我的设计策略,较为详细的方法性能分析在第(4)部分。
Network类
isCircle
:
第一次作业时,采用dfs深度优先搜索的方式完成。
第二、三次作业时,改为并查集的方式实现,在
addPerson
时对映射关系进行初始化,在addRelation
时对映射关系进行更改维护。
deleteColdEmoji
:
通过HashMap的removeIf()方法实现对emojiId、emojiHeat的维护。
通过Iterator实现对messages的维护。
sendIndirectMessage
:
主要难点——最短路径通过迪杰斯特拉算法计算,通过添加
private void myDijkstra(int s, int n)
方法实现堆优化的迪杰斯特拉算法,可以计算出起点到终点的最短距离。
Group类
getValueSum
、getAgeMean
、getAgeVar
:
建立三个私有属性int:
totalValue
,totalAge
,totalAge2
,在MyGroup
加入人、删除人以及MyNetwork
加入关系时对相关的属性进行维护。调用方法时,利用三个int值结合this.people.size()
进行简单计算即可。
Person类、Message类、RedEnvelopeMessage类、NoticeMessage类、EmojiMessage类
实现3个具体消息类的时候,直接复制粘贴课程网站上的规格指示,写的是:
public class MyRedEnvelopeMessage implements RedEnvelopeMessage {
······
}结果IDEA提示说要实现接口的具体方法,想到
RedEnvelopeMessage
继承自Message
,所以就改为:public class MyRedEnvelopeMessage extends MyMessage implements RedEnvelopeMessage {
······
}就可以解决问题。(这一点本身不值一提,只是当时确实产生了一点点困扰,所以写在博客里)这几个类的关系如下图所示:
规格的具体实现较为简单,不再赘述。
异常类
包括PersonIdNotFoundException、EqualPersonIdException、RelationNotFoundException、EqualRelationException、GroupIdNotFoundException、EqualGroupIdException、EqualMessageIdException、MessageIdNotFoundException、EmojiIdNotFoundException、EqualEmojiIdException
所有的异常类的具体实现大同小异,我的思路是建立TotalCounter
类,对总异常数进行统计,并用HashMap
对各个id导致的异常进行统计:
import java.util.HashMap;
public class TotalCounter {
private int total;
private HashMap<Integer, Integer> line;
public TotalCounter() {
this.total = 0;
this.line = new HashMap<>();
}
public void setTotal(int in) {
this.total = in;
}
public int getTotal() {
return this.total;
}
public HashMap<Integer, Integer> getLine() {
return this.line;
}
}
然后,实现各个具体异常类时,建立静态TotalCounter
对象(private static TotalCounter counter = new TotalCounter();
),即可进行异常抛出的累计统计。注意print()
的文字正确性。
(2)结合课程内容,整理基于JML规格来设计测试的方法和策略
通过课程网站的第三单元训练项目学习了Junit
的简单使用。对于一些易于测试的方法,直接用Junit
测试方法进行测试,包括空集状态删除等极端情况。
此外,通过几个同学组队,互相构造数据,然后修改主函数输出到文本进行对拍也是进行测试的有效方法,在此期间也能互相交流算法实现的经验,促进代码的进一步优化。比如,第三次作业的最短路径一开始写的是比较暴力的算法,后来和室友讨论之后才上网学习了堆优化的写法。
在构造数据时,更倾向于大量重复出现方法实现较为复杂的命令,以此尝试找出问题所在,比如第一次作业重点考察qci(query_circle)
,qbs(query_block_sum)
,第三次作业重点考察sim(send_indirect_message)
,dce(delete_cold_emoji)
等等。
(3)总结分析容器选择和使用的经验
本单元作业主要使用ArrayList和HashMap两种容器。
-
ArrayList
这是我使用最熟练的容器,从寒假的OO Pre开始就经常使用。
这一段时间的应用中主要有一个发现,导致我在第一次作业的自测中出现了IndexOutOfBoundException
的问题。
这个问题出现在当ArrayList
的数据类型为Integer时。当时使用了ArrayList
实现的visited数组进行dfs计算,有一条语句是visted.remove(item.getId());
,结果爆出了IndexOutOfBoundException
的异常,经过试验,修改为visted.remove((Integer) item.getId());
,解决了问题。经过IDEA的提示以及上网查阅资料,才知道该类容器有两个remove方法,分别定义如下:
public E remove(int index);
public boolean remove(Object o);
当直接传入int类型的参数时,会默认自动调用第一个方法,即处理为index而非我们想要的Object,所以需要进行强制类型转化。后来改为了并查集实现,就不用考虑这个问题了。
-
HashMap
这个容器接触较晚,相对生疏一些,所以我第一次作业基本都用的是ArrayList
,但是后来迭代的时候,将部分容器更换为了HashMap
,比如规格里的emojiIdList
、emojiHeatList
,直接通过一个private HashMap<Integer,Integer> emojiList
实现,维护难度更小。感觉确实Map好用一些,因为查找更加高效迅速,而且写起程序来更有高级感。
(4)针对本单元容易出现的性能问题,总结分析原因;如果自己作业没有出现,分析自己的设计为何可以避免
本单元作业的很多方法都强调算法性能,如果不认真优化很可能超时。
queryNameRank
:
采用遍历的方式计算名字排名,需要调用比较函数。一开始的时候,使用的是规格要求实现的
compareName
方法,先取出两个对象,判断是否为null,然后再比较。后来发现这里会浪费许多时间,因为在本方法比较名字的时候,两个Person对象都确定会在容器中,就不用经过判断“是否存在”的步骤了,所以直接改为:// faster than compareName
if (person.getName().compareTo(item.getName()) > 0) { i++; }
queryBlockSum
:
由于第二次作业刚开始时,对并查集了解不够深入,考虑不够细致,把
isCircle
方法改为并查集后,不知道queryBlockSum
也可以直接用并查集实现,还是调用的isCircle
方法,浪费大量的时间。后来改为遍历people容器,统计映射对象等于自身的Person数目,即为所求结果。
queryGroupValueSum
,queryGroupAgeMean
,queryGroupAgeVar
(getValueSum
、getAgeMean
、getAgeVar
):
Network里的实现很简单,直接调用相关Group对象的对应方法即可。但是在Group里,如果相关方法每次都直接从头计算则会浪费较长时间,所以在Group类里设置3个新属性:
private int totalValue;
private int totalAge;
private int totalAge2; // 平方当向组内加人、删人的时候,对这三个int属性进行维护。当加关系的时候,也要进行判断,对ar命令里的person1、person2的共有Group的
totalValue
进行维护。这样,最终的函数就较为快捷了。需要注意的是,getValueSum
在早期维护时仅仅是单向的计算value和,最后返回的应该是totalValue * 2
。getAgeVar
也不能直接照着JML规格翻译成遍历的形式,可以计算(totalAge2 + getAgeMean() * getAgeMean() * this.people.size() - 2 * mean * totalAge) / this.people.size()
。
sendIndirectMessage
:
采用迪杰斯特拉算法计算最短路径。刚开始写的是类似离散数学PPT上的那种计算过程,通过10000 * 10000的二维数组记录关系,O(n ^ 2)的方法计算最短路径(当时想着先写出来,之后再改,所以比较暴力)。后来优化的时候,改成用储存“边”的“图”记录关系,然后用堆优化的方法计算person1到所有人员节点的最短距离,然后返回到person2的值即可。
另外,还有一点比较重要:初始版本的作业,我都是先用contains()方法判断对象是否存在,然后再取出使用;研讨课上听了助教的提醒,改为了方法开头直接按照id取出对象,然后再通过检测是否为null判断是否存在,之后直接使用。这样能减少容器的遍历,提高性能。
(5)梳理自己的作业架构设计,特别是图模型构建与维护策略
MyNetwork
:
通过
people
存储所有的人员,通过groups
存储所有的小组,通过messages
存储所有的消息,通过disjoint
存储各个人员对象的映射关系,通过emojiList
存储所有emoji的id与heat(整合了
MyGroup
:
通过
people
存储小组内所有人员,通过totalValue
存储小组内所有人员彼此之间的value值的一半(单向遍历),通过totalAge
存储小组内所有人的年龄和,通过totalAge2
存储小组内所有人的年龄的平方和。
MyPerson
:
通过
acquaintance
存储本人员对象的所有联系人,通过value
存储对应的value值,通过messages
存储本人员的消息,通过group
存储本人员对象所在的所有小组(便于添加关系(ar)之后维护所在小组内相关属性)。
对于图模型构建与维护策略,主要在MyNetwork
里添加了graph
,它是一个存储ArrayList<MyEdge>()
的容器,作用就是构建社交网络的图模型。
以及新建的类MyEdge
:
public class MyEdge implements Comparable<MyEdge> {
private int toTarget;
private int weight;
public MyEdge() {
toTarget = 0;
weight = 0;
}
public MyEdge(int in1,int in2) {
toTarget = in1;
weight = in2;
}
public int getToTarget() {
return toTarget;
}
public int getWeight() {
return weight;
}
public int compareTo(MyEdge obj) {
return this.weight - obj.getWeight();
}
}
这样,每当添加新的关系(ar)时,只需要向graph
的两个对应的ArrayList<MyEdge>()
里,分别加入一条指向对方的边即可完成图的维护。在迪杰斯特拉算法实现求最短路径时,采用借助PriorityQueue<MyEdge> que = new PriorityQueue<MyEdge>();
实现的堆优化算法,可以求出person1到所有人员节点的最短路径,然后取出person2对应项即可。
单元感想
-
一定要多想、多测试!!!第三单元的作业成绩不太理想,很大一部分原因还是因为测试不够全面,这直接导致强测、互测出了不少bug。现在回想起来,很多问题都是写代码的时候就已经想到了的,结果自己测试的时候没能构造对应的数据,检查代码的时候也没有跳出惯性思维,等到了bug修复阶段才弥补过失。时间、精力的花费总量是基本固定的,不如在强测之前把该做的工作做好,这样分数也高,bug修复也轻松······
-
与他人交流依旧十分重要。第12周的研讨课上,老师、助教的一些提醒给了我很大的帮助;与室友和其他同学的交流帮我发现、改进了许多问题。这些都带来了很多收获。
-
JML规格刚接触的时候感觉很繁琐,现在来看确实有很多优点,比如通过前置条件、后置条件、副作用组成的规约对代码功能进行更精确的描述,保证正确性等等。
以上是关于OO2021-第三单元作业总结的主要内容,如果未能解决你的问题,请参考以下文章