OO 第三单元总结
OO 第三单元总结
一、总述
-
本单元主要学习了如何正确解读JML规格,并根据给出的JML规格编写规范的代码,并实现异常处理功能。本单元的背景是模拟社交网络,包括维护NetWork(社交网络)、Group(群组)、Person(用户)、Message(消息)等类的信息,并且了解最小生成树、最短路等图算法。本单元没有性能分数,算是降低了一些压力,不用搞平衡树之类的优化。
二、作业分析
1. 第九次作业
(1)作业要求
-
本次作业主要目的为熟悉基本的JML规格描述,理解入门级JML规格并完成代码实现。本次作业的背景是模拟简单的社交网络,依照JML规格实现具体模拟和查询功能,并学习异常处理的相关知识。
(2)UML类图 & 具体实现
-
本次作业UML类图如下:
-
本次作业主要包括MyPerson、MyGroup、MyNetwork三个类(实现官方包接口),还有六个异常处理类。
-
首先是MyPerson以及MyGroup类,这两个类相对而言比较简单,仅需维护其内部属性,并提供修改以及查询属性的功能。可以把Person认为是一个个社交软件的用户,Group认为是群组,这样Person中的acquaintance可以认为是好友列表,value可以认为是亲密度;Group中的集体属性为群组信息,其中的People容器容纳的便是群组中的所有成员。这样来理解可以帮助我们更好地来实现这两个类的功能。值得注意的部分为查询的性能。其中较为重要的是容器的选择,在这里我选择了HashMap作为容器,key值为Person or Group的ID,Value为其对应的对象。这样便可以把查找的复杂度从O(n)降为O(1)。其余按照指导书展开即可,不在这里赘述。
-
相对而言较为复杂的是MyNetWork,这个类仅有一个实例化对象,可以理解为社交网络(终端),这里记录了用户的信息,用户的好友关系,群组的信息,可以访问其中的任一成员。该类中包含本次作业中最重要的两个查询功能,IsCircle以及QueryBlockSum。首先简单介绍一下二者的功能,isCircle是判断两个用户间是否可以通过中间关系相连(即图论中的联通),而queryBlockSum则是查询有多少个独立的非联通分支。二者按照基准策略展开可能后续会出现TLE的情况。借鉴图论中的知识,我们可以发现这里非常适合运用并查集的知识。可以开一个HashMap<Integer, Integer>来维护成员间的所属关系,这样每次在调用isCircle函数时仅需判断二者是否在一个分支上(是否有相同的根节点)即可。对于queryBlockSum功能,可以维护一个初始值为0的blockSum变量,当addPerson时blockSum++,addrelation时判断如果产生了非联通分支的合并则blockSum--,这样在queryBlockSum时便可以O(1)查找。
并查集实现见下:
在实现中考虑到查询性能问题,我采取了路径压缩 + 按秩合并的方式实现并查集。
由于路径压缩会改变树的高度,可以把秩近似更改为树的重量,这样更方便维护。
private HashMap<Integer, Integer> root; //记录关系的容器
private HashMap<Integer, Integer> weight; //记录分支重量的容器
///////////////查找结点所在分支(根节点)///////////////
public int find(int id) {
int rootId = id;
while (root.get(rootId) != rootId) {
rootId = root.get(rootId);
}
int ansId = id;
int parent;
while (ansId != rootId) {
parent = root.get(ansId);
root.put(ansId, rootId);
ansId = parent;
}
return ansId;
}
///////////////////////合并分支/////////////////////////
public void merge(int id1, int id2) {
int root1 = find(id1);
int root2 = find(id2);
if (root1 == root2) {
return;
}
else {
int sum = weight.get(root1) + weight.get(root2);
if (weight.get(root1) < weight.get(root2)) {
root.put(root1, root2);
weight.put(root2, sum);
} else {
root.put(root2, root1);
weight.put(root1, sum);
}
blockSum--;
}
} -
最后是异常处理的实现,这一部分中我采用了一个静态的Int型变量count(仅在程序启动时初始化一次为0)来记录某类异常出发的次数,采用HashMap<Integer, Integer>来记录该类下某PersonID(Key)触发该类异常的count(Value)。在顶层MainClass中调用Runner来捕获,在MyNetWork中检索并抛出异常。注意异常抛出的顺序,以及两id相等的判断。
(3)代码复杂度分析
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
code.MainClass.main(String[]) | 0 | 1 | 1 | 1 |
code.MyEqualGroupIdException.MyEqualGroupIdException(int) | 2 | 1 | 2 | 2 |
code.MyEqualGroupIdException.print() | 0 | 1 | 1 | 1 |
code.MyEqualPersonIdException.MyEqualPersonIdException(int) | 2 | 1 | 2 | 2 |
code.MyEqualPersonIdException.print() | 0 | 1 | 1 | 1 |
code.MyEqualRelationException.MyEqualRelationException(int, int) | 6 | 1 | 4 | 4 |
code.MyEqualRelationException.print() | 0 | 1 | 1 | 1 |
code.MyGroup.MyGroup(int) | 0 | 1 | 1 | 1 |
code.MyGroup.addPerson(Person) | 0 | 1 | 1 | 1 |
code.MyGroup.delPerson(Person) | 0 | 1 | 1 | 1 |
code.MyGroup.equals(Object) | 2 | 2 | 1 | 3 |
code.MyGroup.getAgeMean() | 2 | 2 | 2 | 3 |
code.MyGroup.getAgeVar() | 2 | 2 | 2 | 3 |
code.MyGroup.getId() | 0 | 1 | 1 | 1 |
code.MyGroup.getSize() | 0 | 1 | 1 | 1 |
code.MyGroup.getValueSum() | 6 | 1 | 4 | 4 |
code.MyGroup.hasPerson(Person) | 1 | 2 | 1 | 2 |
code.MyGroupIdNotFoundException.MyGroupIdNotFoundException(int) | 2 | 1 | 2 | 2 |
code.MyGroupIdNotFoundException.print() | 0 | 1 | 1 | 1 |
code.MyNetwork.MyNetwork() | 0 | 1 | 1 | 1 |
code.MyNetwork.addGroup(Group) | 2 | 2 | 2 | 2 |
code.MyNetwork.addPerson(Person) | 2 | 2 | 2 | 2 |
code.MyNetwork.addRelation(int, int, int) | 7 | 4 | 5 | 6 |
code.MyNetwork.addToGroup(int, int) | 6 | 4 | 5 | 5 |
code.MyNetwork.contains(int) | 1 | 2 | 1 | 2 |
code.MyNetwork.delFromGroup(int, int) | 4 | 4 | 4 | 4 |
code.MyNetwork.find(int) | 2 | 1 | 3 | 3 |
code.MyNetwork.getGroup(int) | 0 | 1 | 1 | 1 |
code.MyNetwork.getPerson(int) | 0 | 1 | 1 | 1 |
code.MyNetwork.hasPerson(int) | 1 | 2 | 1 | 2 |
code.MyNetwork.isCircle(int, int) | 6 | 3 | 3 | 4 |
code.MyNetwork.merge(int, int) | 5 | 2 | 3 | 3 |
code.MyNetwork.queryBlockSum() | 0 | 1 | 1 | 1 |
code.MyNetwork.queryPeopleSum() | 0 | 1 | 1 | 1 |
code.MyNetwork.queryValue(int, int) | 7 | 4 | 5 | 6 |
code.MyPerson.MyPerson(int, String, int) | 0 | 1 | 1 | 1 |
code.MyPerson.addAcquaintance(Person, int) | 0 | 1 | 1 | 1 |
code.MyPerson.compareTo(Person) | 0 | 1 | 1 | 1 |
code.MyPerson.equals(Object) | 2 | 2 | 1 | 3 |
code.MyPerson.getAge() | 0 | 1 | 1 | 1 |
code.MyPerson.getId() | 0 | 1 | 1 | 1 |
code.MyPerson.getName() | 0 | 1 | 1 | 1 |
code.MyPerson.isLinked(Person) | 2 | 3 | 2 | 3 |
code.MyPerson.queryValue(Person) | 1 | 2 | 1 | 2 |
code.MyPersonIdNotFoundException.MyPersonIdNotFoundException(int) | 2 | 1 | 2 | 2 |
code.MyPersonIdNotFoundException.print() | 0 | 1 | 1 | 1 |
code.MyRelationNotFoundException.MyRelationNotFoundException(int, int) | 4 | 1 | 3 | 3 |
code.MyRelationNotFoundException.print() | 0 | 1 | 1 | 1 |
Class | OCavg | OCmax | WMC | |
code.MainClass | 1 | 1 | 1 | |
code.MyEqualGroupIdException | 1.5 | 2 | 3 | |
code.MyEqualPersonIdException | 1.5 | 2 | 3 | |
code.MyEqualRelationException | 2.5 | 4 | 5 | |
code.MyGroup | 1.9 | 4 | 19 | |
code.MyGroupIdNotFoundException | 1.5 | 2 | 3 | |
code.MyNetwork | 2.44 | 5 | 39 | |
code.MyPerson | 1.44 | 3 | 13 | |
code.MyPersonIdNotFoundException | 1.5 | 2 | 3 | |
code.MyRelationNotFoundException | 2 | 3 | 4 |
本次作业由于按照JML规格展开,以及相对规模较小,整体复杂度并不算很高。
(4)Bug分析 && Hack策略
-
由于本次没有性能分,因此只要保证正确性便不会出锅。此外,需要加上并查集优化,以防被卡T。
-
Hack时也按照此策略来进行,如果qbs按照基准策略O(n^2)展开可以Hack到,在这里可以计算一下,一共1000条指令,n条addPerson,(1000-n)条qbs,通过求导可计算出add667个Person、qbs333次时得到最极限的数据。很遗憾我们房间都写了并查集,虽然有些人没优化到O(1),但是1000条数据量太小,并不能卡T,因此并没有bug。
-
此外,在完成本次作业时我发现,Group中的群体属性AgeMean、AgeVar、ValueSum等属性并没有被查询,但有些同学写的有些问题,但没办法Hack到。
2. 第十次作业
(1)作业要求
-
本次作业在上次的基础上添加了Message类,并进一步对NetWork和Person类进行增量开发,同时上次没有被查询的群体属性在本次也增添了相应的查询接口,整体而言工作量并不算很大,其中NetWork中加入了qlc查询指令,算是本次的一个难点(JML晦涩难懂)。
(2)UML类图 & 具体实现
-
UML类图如下:
本次新增了MyMessage类来实现官方包中的Message类,主要提供属性的查询,还增添了Relation类存储关系(图的边),以及Block类管理每个独立的连通分支,这两个类的功能在后面展开叙述。
-
具体实现:
-
本次增添了MyMessage类来管理消息,该类基于JML展开即可,仅提供查询方法,在MyNetWork和MyPerson类中实现对其的管控。本次的Message添加以这样的顺序进行,先通过addMessage加入NetWork中进行管控(即编辑功能),然后通过sendMessage来实现消息的发送,发送后信息产生socialValue,并且从NetWork中移除管控,Person中包含一个容纳最近消息的List来负责接收最新的消息。考虑到查询的效率,在NetWork中我使用HashMap来管理消息,在Person中使用ArrayList来管理消息。
-
本次还提供了Group中集体属性的查询,包括AgeMean,AgeVar,以及ValueSum,这里可以通过一些特殊变量的维护来将查询操作从O(N)、O(N^2)降低为O(1)。
-
AgeMean: 可以通过维护AgeSum来实现O(1)查找,每次用 AgeSum/n 即可;
-
AgeVar: 可以维护AgeSum、AgeVarSum来实现O(1)查找
$$
var= n ∑x 2 −2⋅(⌊ n ∑x ⌋)⋅∑x+n⋅(⌊ n ∑x ⌋) 2
$$
注意,不能使用D(X) = E(X^2) - E(X)^2 来计算,会有精度问题。
-
ValueSum,在每次addPerson与delPerson时,遍历组内的人,判断是否需要修改valueSum,此外在addRelation时查询两人是否有群组同时包含两人,更改该群组的ValueSum,每次qgvs时返回valueSum即可,可以实现O(1)复杂度的查询。
-
-
本次作业在NetWork作业中实现了qlc的查询,经过仔细研读,确定了实际上就是实现一颗最小生成树,如下为JML详细解析:
ensures \result ==
//////////////长度限定//////////////////////////
在最小生成树的实现方面,由于上次已经实现过并查集,所以在这里我选择了克鲁斯卡尔算法,首先找到生成树所在的分支,对其所有边
Relation
按权值从小到大排序,根据最小生成树的性质,该分支有n个点,仅需取n-1条边即可完成生成树,因次仅需选取出n-1条边即可结束算法。从小到大遍历边,每次查询并查集,判断两端点是否在同一分支上,如果不在则选取改边,重复直至去除n-1条边,并返回权值和。public int minTree() {
//////////////排序////////////////////////
Collections.sort(relations, new Comparator<Relation>() {
为了逻辑更加清晰以及方便操作,在这一部分我新增了,Relation类来管理关系(边),增加了Block类来维护一个分支中的所有人员和所有关系(边集):
在本次作业中并查集,我均使用了路径压缩 + 按秩合并的方式,以防维护块的时候经常需要合并导致TLE,此外使用非递归写法,以防爆栈。
(3)代码复杂度分析
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
code.Block.Block(int, int) | 0 | 1 | 1 | 1 |
code.Block.addRelation(Relation) | 0 | 1 | 1 | 1 |
code.Block.find(int) | 2 | 1 | 3 | 3 |
code.Block.getRelations() | 0 | 1 | 1 | 1 |
code.Block.merge(int, int) | 2 | 1 | 2 | 2 |
code.Block.mergeBlock(Block) | 1 | 1 | 2 | 2 |
code.Block.minTree() | 7 | 4 | 4 | 5 |
code.MainClass.main(String[]) | 0 | 1 | 1 | 1 |
code.MyEqualGroupIdException.MyEqualGroupIdException(int) | 2 | 1 | 2 | 2 |
code.MyEqualGroupIdException.print() | 0 | 1 | 1 | 1 |
code.MyEqualMessageIdException.MyEqualMessageIdException(int) | 2 | 1 | 2 | 2 |
code.MyEqualMessageIdException.print() | 0 | 1 | 1 | 1 |
code.MyEqualPersonIdException.MyEqualPersonIdException(int) | 2 | 1 | 2 | 2 |
code.MyEqualPersonIdException.print() | 0 | 1 | 1 | 1 |
code.MyEqualRelationException.MyEqualRelationException(int, int) | 6 | 1 | 4 | 4 |
code.MyEqualRelationException.print() | 0 | 1 | 1 | 1 |
code.MyGroup.MyGroup(int) | 0 | 1 | 1 | 1 |
code.MyGroup.addPerson(Person) | 3 | 1 | 3 | 3 |
code.MyGroup.addValueSum(int) | 0 | 1 | 1 | 1 |
code.MyGroup.delPerson(Person) | 3 | 1 | 3 | 3 |
code.MyGroup.equals(Object) | 2 | 2 | 1 | 3 |
code.MyGroup.getAgeMean() | 1 | 2 | 1 | 2 |
code.MyGroup.getAgeVar() | 1 | 2 | 1 | 2 |
code.MyGroup.getId() | 0 | 1 | 1 | 1 |
code.MyGroup.getPeople() | 0 | 1 | 1 | 1 |
code.MyGroup.getSize() | 0 | 1 | 1 | 1 |
code.MyGroup.getValueSum() | 0 | 1 | 1 | 1 |
code.MyGroup.hasPerson(Person) | 1 | 2 | 1 | 2 |
code.MyGroupIdNotFoundException.MyGroupIdNotFoundException(int) | 2 | 1 | 2 | 2 |
code.MyGroupIdNotFoundException.print() | 0 | 1 | 1 | 1 |
code.MyMessage.MyMessage(int, int, Person, Group) | 0 | 1 | 1 | 1 |
code.MyMessage.MyMessage(int, int, Person, Person) | 0 | 1 | 1 | 1 |
code.MyMessage.equals(Object) | 2 | 2 | 1 | 3 |
code.MyMessage.getGroup() | 0 | 1 | 1 | 1 |
code.MyMessage.getId() | 0 | 1 | 1 | 1 |
code.MyMessage.getPerson1() | 0 | 1 | 1 | 1 |
code.MyMessage.getPerson2() | 0 | 1 | 1 | 1 |
code.MyMessage.getSocialValue() | 0 | 1 | 1 | 1 |
code.MyMessage.getType() | 0 | 1 | 1 | 1 |
code.MyMessageIdNotFoundException.MyMessageIdNotFoundException(int) | 2 | 1 | 2 | 2 |
code.MyMessageIdNotFoundException.print() | 0 | 1 | 1 | 1 |
code.MyNetwork.MyNetwork() | 0 | 1 | 1 | 1 |
code.MyNetwork.addGroup(Group) | 2 | 2 | 2 | 2 |
code.MyNetwork.addMessage(Message) | 5 | 3 | 4 | 4 |
code.MyNetwork.addPerson(Person) | 2 | 2 | 2 | 2 |
code.MyNetwork.addRelation(int, int, int) | 13 | 4 | 8 | 9 |
code.MyNetwork.addToGroup(int, int) | 6 | 4 | 5 | 5 |
code.MyNetwork.contains(int) | 1 | 2 | 1 | 2 |
code.MyNetwork.containsMessage(int) | 1 | 2 | 1 | 2 |
code.MyNetwork.delFromGroup(int, int) | 4 | 4 | 4 | 4 |
code.MyNetwork.find(int) | 2 | 1 | 3 | 3 |
code.MyNetwork.getGroup(int) | 0 | 1 | 1 | 1 |
code.MyNetwork.getMessage(int) | 0 | 1 | 1 | 1 |
code.MyNetwork.getPerson(int) | 0 | 1 | 1 | 1 |
code.MyNetwork.hasPerson(int) | 1 | 2 | 1 | 2 |
code.MyNetwork.isCircle(int, int) | 6 | 3 | 3 | 4 |
code.MyNetwork.merge(int, int, int) | 5 | 2 | 3 | 3 |
code.MyNetwork.queryBlockSum() | 0 | 1 | 1 | 1 |
code.MyNetwork.queryGroupAgeVar(int) | 2 | 2 | 2 | 2 |
code.MyNetwork.queryGroupPeopleSum(int) | 2 | 2 | 2 | 2 |
code.MyNetwork.queryGroupValueSum(int) | 2 | 2 | 2 | 2 |
code.MyNetwork.queryLeastConnection(int) | 2 | 2 | 2 | 2 |
code.MyNetwork.queryPeopleSum() | 0 | 1 | 1 | 1 |
code.MyNetwork.queryReceivedMessages(int) | 2 | 2 | 2 | 2 |
code.MyNetwork.querySocialValue(int) | 2 | 2 | 2 | 2 |
code.MyNetwork.queryValue(int, int) | 7 | 4 | 5 | 6 |
code.MyNetwork.sendMessage(int) | 18 | 5 | 7 | 7 |
code.MyPerson.MyPerson(int, String, int) | 0 | 1 | 1 | 1 |
code.MyPerson.addAcquaintance(Person, int) | 0 | 1 | 1 | 1 |
code.MyPerson.addMoney(int) | 0 | 1 | 1 | 1 |
code.MyPerson.addSocialValue(int) | 0 | 1 | 1 | 1 |
code.MyPerson.compareTo(Person) | 0 | 1 | 1 | 1 |
code.MyPerson.equals(Object) | 2 | 2 | 1 | 3 |
code.MyPerson.getAge() | 0 | 1 | 1 | 1 |
code.MyPerson.getId() | 0 | 1 | 1 | 1 |
code.MyPerson.getMessages() | 0 | 1 | 1 | 1 |
code.MyPerson.getMoney() | 0 | 1 | 1 | 1 |
code.MyPerson.getName() | 0 | 1 | 1 | 1 |
code.MyPerson.getReceivedMessages() | 2 | 1 | 3 | 3 |
code.MyPerson.getSocialValue() | 0 | 1 | 1 | 1 |
code.MyPerson.isLinked(Person) | 2 | 3 | 2 | 3 |
code.MyPerson.queryValue(Person) | 1 | 2 | 1 | 2 |
code.MyPersonIdNotFoundException.MyPersonIdNotFoundException(int) | 2 | 1 | 2 | 2 |
code.MyPersonIdNotFoundException.print() | 0 | 1 | 1 | 1 |
code.MyRelationNotFoundException.MyRelationNotFoundException(int, int) | 4 | 1 | 3 | 3 |
code.MyRelationNotFoundException.print() | 0 | 1 | 1 | 1 |
code.Relation.Relation(int, int, int) | 0 | 1 | 1 | 1 |
code.Relation.getPersonId1() | 0 | 1 | 1 | 1 |
code.Relation.getPersonId2() | 0 | 1 | 1 | 1 |
code.Relation.getValue() | 0 | 1 | 1 | 1 |
compare(Relation, Relation) | 0 | n/a | n/a | n/a |
Class | OCavg | OCmax | WMC | |
code.Block | 2 | 5 | 16 | |
code.MainClass | 1 | 1 | 1 | |
code.MyEqualGroupIdException | 1.5 | 2 | 3 | |
code.MyEqualMessageIdException | 1.5 | 2 | 3 | |
code.MyEqualPersonIdException | 1.5 | 2 | 3 | |
code.MyEqualRelationException | 2.5 | 4 | 5 | |
code.MyGroup | 1.67 | 3 | 20 | |
code.MyGroupIdNotFoundException | 1.5 | 2 | 3 | |
code.MyMessage | 1.11 | 2 | 10 | |
code.MyMessageIdNotFoundException | 1.5 | 2 | 3 | |
code.MyNetwork | 2.5 | 6 | 65 | |
code.MyPerson | 1.33 | 3 | 20 | |
code.MyPersonIdNotFoundException | 1.5 | 2 | 3 | |
code.MyRelationNotFoundException | 2 | 3 | 4 | |
code.Relation | 1 | 1 | 4 |
由于JML规格扩展,本次代码复杂度相对上次有所提高,由于把Block,Relation等关系均提取出来,整体代码复杂度还相对比较低,两个复杂度较高的方法都是基于JML展开,由于循环以及多分支结构造成。
(4)Bug分析 && Hack策略
-
Hack策略主要针对的是完成作业时发现的一些问题:
-
atg操作没有注意到群组人数上限,达上限后向同一群组添加两次相同的人,bug程序会报epi,而正确程序会忽视请求输出两次OK
-
尝试卡一下朴素的Prim算法
-
并查集维护分支时未按秩合并
-
GroupValueSum官方的O(n^2)复杂度
-
AgeVar精度问题
3. 第十一次作业
(1)作业要求
-
本次作业在上次作业的基础上对Message类进行了细化,增添了MyNoticeMessage类、MyRedenvelopMessage类以及MyEmojiMessage类,并加入NetWork中进行管控。本次的难点为SendIndirectMessage方法,即实现最短路算法。这一部分实现较为简单需要注意的是这三个类均继承自Message,仅需增加私有属性即可,构造方法也可以通过super()方法来实现。
(2)UML类图 & 具体实现
-
UML类图:
-
新增的消息附属类:
这一部分实现较为简单需要注意的是这三个类均继承自Message,仅需增加私有属性即可,构造方法也可以通过super()方法来实现。并对addMessage以及sendMessage进行修改。在这一部分中NetWork中对EmojiHeat进行了管控,这一部分可以通过HashMap来实现,key为emojiId,value为Times。
-
SendIndirectMessage(最短路):
该方法是对SendMessage方法的拓展,SendMessage方法仅能在isLinked的两人间发送消息,而SendIndirectMessage则可以在isCircle
在这里我采用了Dijkstra算法,其核心思想是每次找出当前最短距离,并对其他边进行松弛,从而求出单源最短路。寻找当前最短距离时可以采用堆优化,这样可以把时间复杂度优化为O((∣V∣+∣E∣)log∣V∣) ,具体实现如下:
public int minDis(int person1Id, int person2Id) {
HashMap<Integer, Integer> book = new HashMap<>();
HashMap<Integer, Integer> dis = new HashMap<>();
for (Integer key : graph.keySet()) {
book.put(key, 0);
dis.put(key, 2147483647);
}
PriorityQueue<Relation> queue = new PriorityQueue<>();
queue.add(new Relation(person1Id, person1Id, 0));
dis.put(person1Id, 0);
while (!queue.isEmpty()) {
Relation relation = queue.poll();
int to = relation.getPersonId2();
if (dis.get(to) < relation.getValue()) {
continue;
}
if (book.get(to) == 1) {
continue;
}
book.put(to, 1);
for (int i = 0; i < graph.get(to).size(); i++) {
int next = graph.get(to).get(i).getPersonId2();
int weight = graph.get(to).get(i).getValue();
if (book.get(next) == 0 &&
dis.get(next) > dis.get(to) + weight) {
dis.put(next, dis.get(to) + weight);
queue.add(new Relation(person1Id, next, dis.get(next)));
}
}
}
return dis.get(person2Id);
}
(3)代码复杂度分析
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
code.Block.Block(int, int) | 0 | 1 | 1 | 1 |
code.Block.addRelation(Relation) | 4 | 1 | 3 | 3 |
code.Block.find(int) | 2 | 1 | 3 | 3 |
code.Block.merge(int, int) | 2 | 1 | 2 | 2 |
code.Block.mergeBlock(Block) | 1 | 1 | 2 | 2 |
code.Block.minDis(int, int) | 12 | 4 | 6 | 8 |
code.Block.minTree() | 7 | 4 | 4 | 5 |
code.MainClass.main(String[]) | 0 | 1 | 1 | 1 |
code.MyEmojiIdNotFoundException.MyEmojiIdNotFoundException(int) | 2 | 1 | 2 | 2 |
code.MyEmojiIdNotFoundException.print() | 0 | 1 | 1 | 1 |
code.MyEmojiMessage.MyEmojiMessage(int, int, Person, Group) | 0 | 1 | 1 | 1 |
code.MyEmojiMessage.MyEmojiMessage(int, int, Person, Person) | 0 | 1 | 1 | 1 |
code.MyEmojiMessage.getEmojiId() | 0 | 1 | 1 | 1 |
code.MyEqualEmojiIdException.MyEqualEmojiIdException(int) | 2 | 1 | 2 | 2 |
code.MyEqualEmojiIdException.print() | 0 | 1 | 1 | 1 |
code.MyEqualGroupIdException.MyEqualGroupIdException(int) | 2 | 1 | 2 | 2 |
code.MyEqualGroupIdException.print() | 0 | 1 | 1 | 1 |
code.MyEqualMessageIdException.MyEqualMessageIdException(int) | 2 | 1 | 2 | 2 |
code.MyEqualMessageIdException.print() | 0 | 1 | 1 | 1 |
code.MyEqualPersonIdException.MyEqualPersonIdException(int) | 2 | 1 | 2 | 2 |
code.MyEqualPersonIdException.print() | 0 | 1 | 1 | 1 |
code.MyEqualRelationException.MyEqualRelationException(int, int) | 6 | 1 | 4 | 4 |
code.MyEqualRelationException.print() | 0 | 1 | 1 | 1 |
code.MyGroup.MyGroup(int) | 0 | 1 | 1 | 1 |
code.MyGroup.addPerson(Person) | 3 | 1 | 3 | 3 |
code.MyGroup.addValueSum(int) | 0 | 1 | 1 | 1 |
code.MyGroup.delPerson(Person) | 3 | 1 | 3 | 3 |
code.MyGroup.equals(Object) | 2 | 2 | 1 | 3 |
code.MyGroup.getAgeMean() | 1 | 2 | 1 | 2 |
code.MyGroup.getAgeVar() | 1 | 2 | 1 | 2 |
code.MyGroup.getId() | 0 | 1 | 1 | 1 |
code.MyGroup.getPeople() | 0 | 1 | 1 | 1 |
code.MyGroup.getSize() | 0 | 1 | 1 | 1 |
code.MyGroup.getValueSum() | 0 | 1 | 1 | 1 |
code.MyGroup.hasPerson(Person) | 1 | 2 | 1 | 2 |
code.MyGroupIdNotFoundException.MyGroupIdNotFoundException(int) | 2 | 1 | 2 | 2 |
code.MyGroupIdNotFoundException.print() | 0 | 1 | 1 | 1 |
code.MyMessage.MyMessage(int, int, Person, Group) | 0 | 1 | 1 | 1 |
code.MyMessage.MyMessage(int, int, Person, Person) | 0 | 1 | 1 | 1 |
code.MyMessage.equals(Object) | 2 | 2 | 1 | 3 |
code.MyMessage.getGroup() | 0 | 1 | 1 | 1 |
code.MyMessage.getId() | 0 | 1 | 1 | 1 |
code.MyMessage.getPerson1() | 0 | 1 | 1 | 1 |
code.MyMessage.getPerson2() | 0 | 1 | 1 | 1 |
code.MyMessage.getSocialValue() | 0 | 1 | 1 | 1 |
code.MyMessage.getType() | 0 | 1 | 1 | 1 |
code.MyMessageIdNotFoundException.MyMessageIdNotFoundException(int) | 2 | 1 | 2 | 2 |
code.MyMessageIdNotFoundException.print() | 0 | 1 | 1 | 1 |
code.MyNetwork.MyNetwork() | 0 | 1 | 1 | 1 |
code.MyNetwork.addGroup(Group) | 2 | 2 | 2 | 2 |
code.MyNetwork.addMessage(Message) | 10 | 5 | 5 | 6 |
code.MyNetwork.addPerson(Person) | 2 | 2 | 2 | 2 |
code.MyNetwork.addRelation(int, int, int) | 13 | 4 | 8 | 9 |
code.MyNetwork.addToGroup(int, int) | 6 | 4 | 5 | 5 |
code.MyNetwork.clearNotices(int) | 7 | 2 | 4 | 4 |
code.MyNetwork.contains(int) | 1 | 2 | 1 | 2 |
code.MyNetwork.containsEmojiId(int) | 0 | 1 | 1 | 1 |
code.MyNetwork.containsMessage(int) | 1 | 2 | 1 | 2 |
code.MyNetwork.delFromGroup(int, int) | 4 | 4 | 4 | 4 |
code.MyNetwork.deleteColdEmoji(int) | 11 | 1 | 8 | 8 |
code.MyNetwork.find(int) | 2 | 1 | 3 | 3 |
code.MyNetwork.getGroup(int) | 0 | 1 | 1 | 1 |
code.MyNetwork.getMessage(int) | 0 | 1 | 1 | 1 |
code.MyNetwork.getPerson(int) | 0 | 1 | 1 | 1 |
code.MyNetwork.hasPerson(int) | 1 | 2 | 1 | 2 |
code.MyNetwork.isCircle(int, int) | 6 | 3 | 3 | 4 |
code.MyNetwork.merge(int, int, int) | 5 | 2 | 3 | 3 |
code.MyNetwork.queryBlockSum() | 0 | 1 | 1 | 1 |
code.MyNetwork.queryGroupAgeVar(int) | 2 | 2 | 2 | 2 |
code.MyNetwork.queryGroupPeopleSum(int) | 2 | 2 | 2 | 2 |
code.MyNetwork.queryGroupValueSum(int) | 2 | 2 | 2 | 2 |
code.MyNetwork.queryLeastConnection(int) | 2 | 2 | 2 | 2 |
code.MyNetwork.queryMoney(int) | 2 | 2 | 2 | 2 |
code.MyNetwork.queryPeopleSum() | 0 | 1 | 1 | 1 |
code.MyNetwork.queryPopularity(int) | 2 | 2 | 2 | 2 |
code.MyNetwork.queryReceivedMessages(int) | 2 | 2 | 2 | 2 |
code.MyNetwork.querySocialValue(int) | 2 | 2 | 2 | 2 |
code.MyNetwork.queryValue(int, int) | 7 | 4 | 5 | 6 |
code.MyNetwork.sendIndirectMessage(int) | 8 | 3 | 5 | 6 |
code.MyNetwork.sendMessage(int) | 33 | 5 | 12 | 12 |
code.MyNetwork.storeEmojiId(int) | 2 | 2 | 2 | 2 |
code.MyNoticeMessage.MyNoticeMessage(int, String, Person, Group) | 0 | 1 | 1 | 1 |
code.MyNoticeMessage.MyNoticeMessage(int, String, Person, Person) | 0 | 1 | 1 | 1 |
code.MyNoticeMessage.getString() | 0 | 1 | 1 | 1 |
code.MyPerson.MyPerson(int, String, int) | 0 | 1 | 1 | 1 |
code.MyPerson.addAcquaintance(Person, int) | 0 | 1 | 1 | 1 |
code.MyPerson.addMoney(int) | 0 | 1 | 1 | 1 |
code.MyPerson.addSocialValue(int) | 0 | 1 | 1 | 1 |
code.MyPerson.compareTo(Person) | 0 | 1 | 1 | 1 |
code.MyPerson.equals(Object) | 2 | 2 | 1 | 3 |
code.MyPerson.getAge() | 0 | 1 | 1 | 1 |
code.MyPerson.getId() | 0 | 1 | 1 | 1 |
code.MyPerson.getMessages() | 0 | 1 | 1 | 1 |
code.MyPerson.getMoney() | 0 | 1 | 1 | 1 |
code.MyPerson.getName() | 0 | 1 | 1 | 1 |
code.MyPerson.getReceivedMessages() | 2 | 1 | 3 | 3 |
code.MyPerson.getSocialValue() | 0 | 1 | 1 | 1 |
code.MyPerson.isLinked(Person) | 2 | 3 | 2 | 3 |
code.MyPerson.queryValue(Person) | 1 | 2 | 1 | 2 |
code.MyPersonIdNotFoundException.MyPersonIdNotFoundException(int) | 2 | 1 | 2 | 2 |
code.MyPersonIdNotFoundException.print() | 0 | 1 | 1 | 1 |
code.MyRedEnvelopeMessage.MyRedEnvelopeMessage(int, int, Person, Group) | 0 | 1 | 1 | 1 |
code.MyRedEnvelopeMessage.MyRedEnvelopeMessage(int, int, Person, Person) | 0 | 1 | 1 | 1 |
code.MyRedEnvelopeMessage.getMoney() | 0 | 1 | 1 | 1 |
code.MyRelationNotFoundException.MyRelationNotFoundException(int, int) | 4 | 1 | 3 | 3 |
code.MyRelationNotFoundException.print() | 0 | 1 | 1 | 1 |
code.Relation.Relation(int, int, int) | 0 | 1 | 1 | 1 |
code.Relation.compareTo(Relation) | 0 | 1 | 1 | 1 |
code.Relation.getPersonId1() | 0 | 1 | 1 | 1 |
code.Relation.getPersonId2() | 0 | 1 | 1 | 1 |
code.Relation.getValue() | 0 | 1 | 1 | 1 |
compare(Relation, Relation) | 0 | n/a | n/a | n/a |
Class | OCavg | OCmax | WMC | |
code.Block | 3 | 7 | 24 | |
code.MainClass | 1 | 1 | 1 | |
code.MyEmojiIdNotFoundException | 1.5 | 2 | 3 | |
code.MyEmojiMessage | 1 | 1 | 3 | |
code.MyEqualEmojiIdException | 1.5 | 2 | 3 | |
code.MyEqualGroupIdException | 1.5 | 2 | 3 | |
code.MyEqualMessageIdException | 1.5 | 2 | 3 | |
code.MyEqualPersonIdException | 1.5 | 2 | 3 | |
code.MyEqualRelationException | 2.5 | 4 | 5 | |
code.MyGroup | 1.67 | 3 | 20 | |
code.MyGroupIdNotFoundException | 1.5 | 2 | 3 | |
code.MyMessage | 1.11 | 2 | 10 | |
code.MyMessageIdNotFoundException | 1.5 | 2 | 3 | |
code.MyNetwork | 2.91 | 11 | 96 | |
code.MyNoticeMessage | 1 | 1 | 3 | |
code.MyPerson | 1.33 | 3 | 20 | |
code.MyPersonIdNotFoundException | 1.5 | 2 | 3 | |
code.MyRedEnvelopeMessage | 1 | 1 | 3 | |
code.MyRelationNotFoundException | 2 | 3 | 4 | |
code.Relation | 1 | 1 | 5 |
本次代码复杂度控制依旧良好,整体按照JML规格展开,并在可以优化的部分进行了优化,除了sendMessage等具有较多分支条件的方法外,整体复杂度良好。
(4)Bug分析 && Hack策略
本次强测互测环节依旧没出现过bug,顺利的苟过了第三单元,但在互测中也没有什么实质性的斩获,喜提成功Hack评测机一次。
大概是这样的数据,首先add了id为1的NoticeMessage,然后发送出来,这样messages里已经没有该id的消息,可以再次添加一条普通的id仍为1的消息,再次send此时接收者的消息列表中就有了两条id为1的不同类型消息,此时调用clearNotice,正常应该清除掉第一次发送的Notice保留下第二次的消息,但是在实际操作过程中如果我们调用了remove(message)方法的话,由于官方程序重写过equals方法,会认为所有id相同的message相等,因此,remove方法找到了第一个相同id的消息进行删除,导致删除了不该删除的普通消息,留下了应该删掉的notice消息。
解决办法就是改成remove(下标),这是我在公测结束前发现的神奇bug,感谢jbt同学的数据数据支持,本来想着看看有没有和我一样的冤大头,结果发现提交正确的输出官方说非法,错误的输出反倒通过了,然后就引发了血雨腥风……好在后面迅速修复了这个bug。(同房的xd都没犯这个错误)
三、NetWork拓展
-
新增
Advertiser:持续向外发送产品广告
Producer:产品生产商,通过Advertiser来销售产品
Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
Person:吃瓜群众,不发广告,不买东西,不卖东西
Advertiser、Producer、Customer均继承自Person类,维护自己的私有属性,同时为了满足广告和购买信息可以新增Advertisement和PurchaseMessage继承Message。
-
JML规格
添加广告
/*@ public normal_behavior
@ requires !(\exists int i; 0 <= i && i < messages.length; messages[i].equals(message)) &&
@ message.getType() == 2 && (message.getPerson1() instanceof Advertiser);
@ assignable messages;
@ ensures messages.length == \old(messages.length) + 1;
@ ensures (\exists int i; 0 <= i && i < messages.length; messages[i].equals(message));
@ ensures (\forall int i; 0 <= i && i < \old(messages.length);
@ (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i]))));
@ also
@ public exceptional_behavior
@ signals (EqualMessageIdException e)
@ (\exists int i; 0 <= i && i < messages.length; messages[i].equals(message));
@ signals (AdvertiserNotFoundException e)
@ !(\exists int i; 0 <= i && i < messages.length; messages[i].equals(message))
@ && !(messages[i].getPerson1() instanceof Advertiser);
@*/
public void addAdvertisement(Message message) throws EqualMessageIdException, AdvertiserNotFoundException;
添加产品
/* public normal_behavior
@ requires !(\exists int i; 0 <= i && i < products.length; products[i].equals(product));
@ assignable products;
@ ensures products.length == \old(products.length) + 1;
@ ensures (\exists int i; 0 <= i && i < products.length; products[i] == product);
@ ensures (\forall int i; 0 <= i && i < \old(products.length);
@ (\exists int j; 0 <= j && j < products.length; products[j] == (\old(products[i]))));
@ also
@ public exceptional_behavior
@ signals (EqualProductIdException e) (\exists int i; 0 <= i && i < products.length;
@ products[i].equals(product));
@*/
public void addProduct(/*@ non_null @*/Product product) throws EqualProductIdException;
查询销售额
/*@ public normal_behavior
@ requires (\exists int i; 0 <= i && i <= people.size;
@ people[i].equals(producer) && people[i] instanceof Producer)
@ ensures \result == producer.getMoney
@ also
@ public exceptional_behavior
@ signals (PersonIdNotFoundException e)
@ (\forall i; 0 <= i && i < people.size; !people[i].equals(producer))
@*/
public int querySaleVolume(Person producer) throws PersonIdNotFoundException;
四、反思感悟
本单元还算顺利,由于给出了JML规格因此整体工作量并没有很大,优化方面由于没有性能分,仅将O(N^2)复杂度的部分优化,以防T掉。验证方式采用了形式化验证(肉眼比对),加上随机数据(@jbt) & 对拍的方式来保障正确性。最大的困难可能就是解读形式化描述了吧,再次点名qlc。还有就是维护好代码的逻辑结构,由于较早的抽象出了Block(联通块?)这一个类来维护人员之间的关系,后面的最小生成树以及最短路算法均可在该类中实现,并不需要担心NetWork类过长的情况。整体对本单元的学习还算比较满意。