OO第三单元总结
实现规格的设计策略
- 类已经由课程组规定好,能设计的部分只有类的字段。
- 一般JML给出的都是疑似数组的形式,但是实际上又会出现大段的JML来描述一个数组和另一个数组对应的情况,其实这完全可以用一个HashMap来解决。
- 对于
contains..,has..这种名字其实稍微阅读JML确认一下,就知道只要调用容器的contains(key)就可以完成。 - 由简到难,先把所有pure方法写完。
- 剩下的方法,分为异常和正常表现两部分。个人倾向于:先用
if在前面把异常行为都写完。然后正常情况就可以直接写了。 - 异常类:所有的异常类其实是同一个模板。字段可以设计为:
- 一个静态字段统计出现总次数。
- 一个私有字段关联构造函数,每次保存最新的异常id,用于printf输出
测试策略
-
阅读JML:
重点比对signal的条件,以及ensures中有关于计算的语句。保证设计完全符合JML的要求才是最稳的。
-
随机数据对拍:
使用python制作了关于指令的随机输入。具体可以分几个部分:
- 首先是ap指令,数据随机。
- 然后是ar,ag指令,数据固定在之前ap的随机数集合中。
- 接下来是其他指令的组合。
接下来只要打包jar和同学愉快的对拍就好了。
容器选择经验
-
ArrayList:最贴近JML描述,易于实现,但是如果拘泥于此,性能会出大问题。
- 之前有提到过存在JML描述两个数组元素具有对应关系的情况,对于此,绝对不应该继续使用ArrayList。
- 即使不得不使用ArrayList,其实也可以额外保存一份HashMap作为cache,增加访问速度。
-
HashMap:提高性能首选。
- 由于作业的独特要求:任何人,组,消息,都有独一无二的id,这就已经为使用HashMap创造了有利条件。可以为他们都写一个cache增加访问速度。
- 根据需要还可以有HashMap和HashMap的嵌套,用于关系的cache。
-
PriorityQueue:可以使用优先队列对第三次作业中最短路算法做时间优化。
- 插入和取顶结点时的调整操作由java自己完成。
- 只需要编写相应的comparator即可,十分方便。
-
其他:自己实现了Relation类用于记录关系。用于优先队列优化的dijkstra算法。
public class Relation implements Comparable { private int to; private int value; ...
性能问题
三次作业里,几乎性能问题每次都集中出现在某几个函数上。
第一次作业:
-
queryBlockSum、isCircle:-
通过阅读JML我们可以知道这里是关于关系无向图,求连通分支的一些问题。
-
采用并查集解决:
-
初始化father数组,每词addPerson时,father为自己。
-
在每次addRelation的时候,进行father的归并。规定总是以更小的id为father。
private int father(int ori) { int f = ori; while (find.get(f) != f) { f = find.get(f); } return f; } -
最后father函数返回结果相同的人,属于同一个连通分支。
-
BlockSum只需要把father数组转化为set,然后查询元素个数即可得到最大连通分支数。
-
-
第二次作业:
-
getAgeMean、getAgeVar:-
在group类的内部设置这两个私有字段,每次addPerson,deletePerson时更新。更新实际用时为o(n),调用返回为o(1)。
-
其中对于方差的计算:通过对表达式的展开,可以发现其为年龄平方和、年龄和、年龄平均数的形式,就方便在add和delete中直接维护这三个数据,然后直接计算方差。
public void addPerson(Person person) { ... ageSum += person.getAge(); ageSqureSum += person.getAge() * person.getAge(); } public int getAgeVar() { ... return (ageSqureSum - 2 * mean * ageSum + size * mean * mean) / size; }
-
-
getValueSum:- 需要在NetWork中对于人的关系,人在哪些组进行缓存。
- 在每次addRelation的时候,通知两人所在组更新ValueSum。并对该Relation缓存。
- 在每次addToGroup的时候,对人所在的组进行缓存。
- 由此每个组的valueSum就在添加任何关系时自动更新,最后调用的时候只要直接返回值。更新实际用时为o(n),调用返回为o(1)。
第三次作业:
-
sendIndirectMessage:读了一遍JML,简称就是求最短路。 -
这里暂时没有想到在加人和关系的时候能直接算出来的方法。(估计不存在)所以要尽力优化每一次调用的时间。使用了优先队列优化的dijkstra算法。
-
自己实现了Relation类用于记录关系,其实现comparable接口,以便使用优先队列对路径长度排序。
... dj.offer(new Relation(id1, 0)); while (dj.isEmpty() != true && id1 != id2) { Relation r = dj.poll(); id1 = r.getTo(); if (dis.containsKey(id1) == true) { continue; } dis.put(id1,r.getValue()); Set<Integer> s = relations.get(id1).keySet(); for (Integer i:s) { dj.add(new Relation(i, relations.get(id1).get(i) + r.getValue())); } } ...
作业架构设计
主要分析自己实现的NetWork部分,其他的官方大都遵循官方架构。
第一次作业
- 类图:

- 度量分析:
| method | CogC | ev | iv | v |
|---|---|---|---|---|
| MyNetwork.union(int,int) | 3.0 | 2.0 | 2.0 | 3.0 |
| MyNetwork.queryValue(int,int) | 5.0 | 4.0 | 4.0 | 7.0 |
| MyNetwork.queryPeopleSum() | 0.0 | 1.0 | 1.0 | 1.0 |
| MyNetwork.queryNameRank(int) | 4.0 | 2.0 | 2.0 | 4.0 |
| MyNetwork.queryBlockSum() | 1.0 | 1.0 | 2.0 | 2.0 |
| MyNetwork.MyNetwork() | 0.0 | 1.0 | 1.0 | 1.0 |
| MyNetwork.isCircle(int,int) | 10.0 | 8.0 | 4.0 | 9.0 |
| MyNetwork.getPerson(int) | 3.0 | 3.0 | 2.0 | 3.0 |
| MyNetwork.father(int) | 1.0 | 1.0 | 2.0 | 2.0 |
| MyNetwork.contains(int) | 3.0 | 3.0 | 2.0 | 3.0 |
| MyNetwork.compareName(int,int) | 3.0 | 3.0 | 2.0 | 4.0 |
| MyNetwork.addRelation(int,int,int) | 11.0 | 8.0 | 6.0 | 11.0 |
| MyNetwork.addPerson(Person) | 1.0 | 2.0 | 2.0 | 2.0 |
| Total | 45.0 | 39.0 | 32.0 | 52.0 |
| Average | 3.4615384615384617 | 3.0 | 2.4615384615384617 | 4.0 |
- 图模型构建与维护策略:
- find描述了结点自身的id和他的父结点的id的联系,用于并查集操作。
- 具体为在每次addRelation时,会调用union函数,union中以两个结点的id调用father函数找到父节点,然后把两个结点连接到父节点上,修改find。
- 程序BUG分析:没有被测出bug
第二次作业
- 类图:

- 度量分析:
| method | CogC | ev | iv | v |
|---|---|---|---|---|
| MyNetwork.union(int,int) | 3.0 | 2.0 | 2.0 | 3.0 |
| MyNetwork.sendMessage(int) | 12.0 | 5.0 | 11.0 | 12.0 |
| MyNetwork.queryValue(int,int) | 5.0 | 4.0 | 4.0 | 7.0 |
| MyNetwork.querySocialValue(int) | 1.0 | 2.0 | 1.0 | 2.0 |
| MyNetwork.queryReceivedMessages(int) | 1.0 | 2.0 | 1.0 | 2.0 |
| MyNetwork.queryPeopleSum() | 0.0 | 1.0 | 1.0 | 1.0 |
| MyNetwork.queryNameRank(int) | 4.0 | 2.0 | 2.0 | 4.0 |
| MyNetwork.queryGroupValueSum(int) | 1.0 | 2.0 | 1.0 | 2.0 |
| MyNetwork.queryGroupSum() | 0.0 | 1.0 | 1.0 | 1.0 |
| MyNetwork.queryGroupPeopleSum(int) | 1.0 | 2.0 | 1.0 | 2.0 |
| MyNetwork.queryGroupAgeVar(int) | 1.0 | 2.0 | 1.0 | 2.0 |
| MyNetwork.queryGroupAgeMean(int) | 1.0 | 2.0 | 1.0 | 2.0 |
| MyNetwork.queryBlockSum() | 1.0 | 1.0 | 2.0 | 2.0 |
| MyNetwork.MyNetwork() | 0.0 | 1.0 | 1.0 | 1.0 |
| MyNetwork.isCircle(int,int) | 10.0 | 8.0 | 4.0 | 9.0 |
| MyNetwork.getPerson(int) | 0.0 | 1.0 | 1.0 | 1.0 |
| MyNetwork.getMessage(int) | 3.0 | 3.0 | 3.0 | 3.0 |
| MyNetwork.getGroup(int) | 1.0 | 2.0 | 2.0 | 2.0 |
| MyNetwork.father(int) | 1.0 | 1.0 | 2.0 | 2.0 |
| MyNetwork.delFromGroup(int,int) | 7.0 | 4.0 | 4.0 | 7.0 |
| MyNetwork.containsMessage(int) | 3.0 | 3.0 | 2.0 | 3.0 |
| MyNetwork.contains(int) | 1.0 | 2.0 | 1.0 | 2.0 |
| MyNetwork.compareName(int,int) | 3.0 | 3.0 | 2.0 | 4.0 |
| MyNetwork.addToGroup(int,int) | 12.0 | 4.0 | 6.0 | 9.0 |
| MyNetwork.addRelation(int,int,int) | 10.0 | 4.0 | 9.0 | 12.0 |
| MyNetwork.addPerson(Person) | 1.0 | 2.0 | 2.0 | 2.0 |
| MyNetwork.addMessage(Message) | 3.0 | 3.0 | 5.0 | 5.0 |
| MyNetwork.addGroup(Group) | 1.0 | 2.0 | 2.0 | 2.0 |
| Total | 87.0 | 71.0 | 75.0 | 106.0 |
| Average | 3.107142857142857 | 2.5357142857142856 | 2.6785714285714284 | 3.7857142857142856 |
-
图模型构建与维护策略:
- 新增peopleCache,在不改变之前并查集实现的基础上,增加了对person的访问速度。
- 使用peopleInGroup,在每一次addToGroup的时候,记录person被加入到哪一个group中。
- 使用relations,实质上是把关系当做三元组<from, to, value>存储,在addRelation的时候在NetWork中保存到Cache,并根据peopleInGroup通知先关组更新其value。
-
程序BUG分析:没有被测出bug
第三次作业
- 类图:

- 度量分析:
| method | CogC | ev | iv | v |
|---|---|---|---|---|
| MyNetwork.update(int,int,int) | 9.0 | 3.0 | 5.0 | 7.0 |
| MyNetwork.union(int,int) | 3.0 | 2.0 | 2.0 | 3.0 |
| MyNetwork.storeEmojiId(int) | 1.0 | 2.0 | 1.0 | 2.0 |
| MyNetwork.setMatrix(int,int,int) | 1.0 | 1.0 | 1.0 | 2.0 |
| MyNetwork.sendMessage(int) | 28.0 | 5.0 | 18.0 | 19.0 |
| MyNetwork.sendIndirectMessage(int) | 14.0 | 5.0 | 10.0 | 14.0 |
| MyNetwork.queryValue(int,int) | 5.0 | 4.0 | 4.0 | 7.0 |
| MyNetwork.querySocialValue(int) | 1.0 | 2.0 | 1.0 | 2.0 |
| MyNetwork.queryReceivedMessages(int) | 1.0 | 2.0 | 1.0 | 2.0 |
| MyNetwork.queryPopularity(int) | 1.0 | 2.0 | 1.0 | 2.0 |
| MyNetwork.queryPeopleSum() | 0.0 | 1.0 | 1.0 | 1.0 |
| MyNetwork.queryNameRank(int) | 4.0 | 2.0 | 2.0 | 4.0 |
| MyNetwork.queryMoney(int) | 1.0 | 2.0 | 1.0 | 2.0 |
| MyNetwork.queryGroupValueSum(int) | 1.0 | 2.0 | 1.0 | 2.0 |
| MyNetwork.queryGroupSum() | 0.0 | 1.0 | 1.0 | 1.0 |
| MyNetwork.queryGroupPeopleSum(int) | 1.0 | 2.0 | 1.0 | 2.0 |
| MyNetwork.queryGroupAgeVar(int) | 1.0 | 2.0 | 1.0 | 2.0 |
| MyNetwork.queryGroupAgeMean(int) | 1.0 | 2.0 | 1.0 | 2.0 |
| MyNetwork.queryBlockSum() | 1.0 | 1.0 | 2.0 | 2.0 |
| MyNetwork.MyNetwork() | 0.0 | 1.0 | 1.0 | 1.0 |
| MyNetwork.isCircle(int,int) | 4.0 | 4.0 | 2.0 | 5.0 |
| MyNetwork.getPerson(int) | 0.0 | 1.0 | 1.0 | 1.0 |
| MyNetwork.getMessage(int) | 0.0 | 1.0 | 1.0 | 1.0 |
| MyNetwork.getMatrix(int,int) | 1.0 | 1.0 | 1.0 | 2.0 |
| MyNetwork.getGroup(int) | 1.0 | 2.0 | 2.0 | 2.0 |
| MyNetwork.father(int) | 1.0 | 1.0 | 2.0 | 2.0 |
| MyNetwork.delFromGroup(int,int) | 7.0 | 4.0 | 4.0 | 7.0 |
| MyNetwork.deleteColdEmoji(int) | 6.0 | 1.0 | 4.0 | 4.0 |
| MyNetwork.containsMessage(int) | 0.0 | 1.0 | 1.0 | 1.0 |
| MyNetwork.containsEmojiId(int) | 0.0 | 1.0 | 1.0 | 1.0 |
| MyNetwork.contains(int) | 1.0 | 2.0 | 1.0 | 2.0 |
| MyNetwork.compareName(int,int) | 3.0 | 3.0 | 2.0 | 4.0 |
| MyNetwork.addToGroup(int,int) | 12.0 | 4.0 | 6.0 | 9.0 |
| MyNetwork.addRelation(int,int,int) | 10.0 | 4.0 | 9.0 | 12.0 |
| MyNetwork.addPerson(Person) | 2.0 | 2.0 | 3.0 | 3.0 |
| MyNetwork.addMessage(Message) | 6.0 | 4.0 | 9.0 | 9.0 |
| MyNetwork.addGroup(Group) | 1.0 | 2.0 | 2.0 | 2.0 |
| Total | 129.0 | 82.0 | 107.0 | 146.0 |
| Average | 3.4864864864864864 | 2.2162162162162162 | 2.891891891891892 | 3.945945945945946 |
-
图模型构建与维护策略:
- 相比于之前的策略,只是封装了对于缓存的Relation读写的函数,可以直接调用
setMatrix和getMatrix。(其余的部分本来想写在加入关系的时候直接求出最短路,但是没能找出算法) - 最短路的算法部分除了利用这两个读取cache,和之前的没有太多关联。
- 相比于之前的策略,只是封装了对于缓存的Relation读写的函数,可以直接调用
-
程序BUG分析
- 在
sendMessage里面往群里发红包的时候,JML的条件里漏看了一句,导致直接给自己也发了一份钱,被hack了。
@(\forall Person p;\old(getMessage(id)).getGroup().hasPerson(p) && p != \old(getMessage(id)).getPerson1(); @p.getMoney() == \old(p.getMoney()) + i)); - 在

浙公网安备 33010602011771号