OO 第三单元总结
OO 第三单元总结
一、实现规格采取的设计策略
1、仔细阅读JML内容
本单元主要的考察点在于阅读理解提供的JML并根据自己的理解完成程序代码。JML语言表述的方法实现往往十分冗长。大量的requires字句和ensures字句使得一个本不复杂的方法看起来很复杂,大量的()嵌套使得表达式的层次难以理顺,所以需要我们耐心地一步步分析方法的规格。
2、根据需求选择恰当的容器
虽然在JML中将很多变量规格声明为int[],Person[]等数组的形式,但是在我们的实际操作中还要根据需求选择合适的容器,如ArrayList,HashMap等。(并且根据本人的实际经历这些最后十有八九都要被优化为HashMap…)
3、注意关注性能问题
官方提供的规格虽然完善,但是不会考虑具体实现时是否会出现性能的问题。所以一味地去“翻译”JML表达式而不根据自己的理解进行优化,很容易出现TLE的bug。(比如我…)例如Group中的getAgeMean()和getAgeVar():
/*@ ensures \result == (people.length == 0? 0 : @ ((\sum int i; 0 <= i && i < people.length; people[i].getAge()) / people.length)); @*/ public /*@pure@*/ int getAgeMean(); /*@ ensures \result == (people.length == 0? 0 : ((\sum int i; 0 <= i && i < people.length; @ (people[i].getAge() - getAgeMean()) * (people[i].getAge() - getAgeMean())) / @ people.length)); @*/ public /*@pure@*/ int getAgeVar();
如果真的在getAgeMean() 中遍历people得到result,那么调用getAgeVar()时就会出现一个二重循环,使得消耗的时间大大增加。
4、利用方法名帮助理解
因为JML的表达式总是表述的很复杂,这时候根据方法的名字就可以很快理解该方法的作用。如方法名叫做addPerson(),那么该方法往往是简单的向某一数组中加入一个person。
二、基于JML规格来设计测试的方法和策略
1、正确性测试
测试一些需要抛出异常的状况有没有正确抛出异常。重点测几个实现比较复杂的函数,看看是否结果正确。(然而强测还是很凄惨,因为自己造的数据不够强,又没有评测机)
2、性能测试
测试诸如qbs,qgam,qgav,sim等需要进行遍历或递归等算法的指令,容易出现CLE的问题。主要是分析是否能够优化自己的算法。
三、分析容器的选择和使用的经验
对于数组的实现我基本都是使用的HashMap(一开始我还试图用ArrayList,但最后都改成了HashMap),偶尔使用ArrayList也是为了它有序的特性。因为HashMap在查找元素时只需要调用get()既可,而放在ArrayList中就需要遍历整个list。除此之外,HashMap键值对的存储方式也使得数据的存储和调用方便了许多。
四、针对本单元容易出现的性能问题,总结分析原因如果自己作业没有出现,分析自己的设计为何可以避免
1、第一次作业
第一次作业主要是qbs的问题。
我对于isCirle()的实现有很大问题。当时我只是想到用递归的方法查找,就想不出什么更好的方法了。后来bug修复的时候知道了并查集的方法,顿时惊为天人。让我意识到解决性能问题的一大方法就是在每次ap,ar等操作时把当前的状态进行保存,在需要的时候通过一次遍历甚至一次调用就能解决所有问题。
除此之外,我在实现qbs时,只是向JML表达式里面写的那样简单粗暴的循环调用isCircle(),却没有考虑过其中的性能问题。但实际上我们增加一个成员变量qbs,在每一次ap,ar时都可以根据判断实时增减qbs的值,需要时直接返回既可。这样就大大减少了性能开销。
2、第二次作业
一是getAgeMean()和getAgeVar()的问题。当时写的时候我没有注意到getAgeVar()中每一次循环都要调用getAgeMean(),如果getAgeMean()中再循环计算就是一个二重循环了。然后就TLE了。
对此我的解决方法是增加一个成员变量ageSum,初始值为0,每次增加或删除person时修改ageSum的值,在调用getAgeMean()时只需返回ageSum除以people.size()的值既可。这样getAgeVar()中只有一重循环了。
二是qgvs的问题。我完成作业时是考虑到了如果遍历可能会超时的问题。然后我又想到第一次作业的解决方法,就试图每次记录下当前的getValueSum的值。然而我又忘记了person的value值是可以改变的,而且people中的person还可能被删除,结果就导致强测大片大片的WA。
3、第三次作业
主要是针对sim的问题。以下是我根据迪杰斯特拉算法的一部分代码实现:
for (Person head : mark.keySet()) { HashMap<Person,Integer> list = wait.get(head.getId()); int value = mark.get(head); for (Person p : list.keySet()) { int listValue = list.get(p); if (min > value + listValue) { min = value + listValue; nextPerson = p; } } } mark.put(nextPerson,min); if (person2.equals(nextPerson)) { break; }
然而最后还是有两个点TLE了。我个人觉得如果在初始存放路径表时能够按照大小顺序排序的话,上述代码可以简化为一重循环,每个点可以直接取出相连点中路径最小的点。(然而我还没试过)
五、梳理自己的作业架构设计,特别是图模型构建与维护策略
整体架构设计都是按照JML规格完成的。
对于每个类:
1、MyPerson
private int id; private String name; private int age; private HashMap<Person,Integer> acquaintance; private int socialValue; private ArrayList<Message> messages; private int money = 0;
其中acquaintance中的key值是有relation的人,value值是熟人对应的value值。
2、MyNetwork
private HashMap<Integer,Person> people; private HashMap<Integer,Integer> relation = new HashMap<>(); private HashMap<Integer,HashMap<Person,Integer>> searchRelation = new HashMap<>(); private HashMap<Integer,Group> groups = new HashMap<>(); private HashMap<Integer,Message> messages = new HashMap<>(); private int qbs = 0; private HashMap<Integer,Integer> emojiIdList = new HashMap<>();
relation中key值代表一个person,value值代表person的父节点。addperson时同时在relation中添加一个value值为自身的person的id。addRelation时判断是否需要更改父节点:
if (relation.get(id1).equals(relation.get(id2))) { return; } int father1 = find(id1,id1); int father2 = find(id2,id2); if (father1 != father2) { relation.put(father1,father2); qbs--; }
使用时通过调用find()判断是否拥有同一根节点,同时简化路径。以下为find()的实现:
public int find(int personId, int id) { if (relation.get(id).equals(id)) { return id; } else { relation.put(personId,find(id,relation.get(id))); return relation.get(personId); } }
searchRelation中存放每一个person对应的acquaintance。用于辅助计算最短加权路径。迪杰斯特拉算法的实现在上一部分的第三次作业中已经提过,此处不再赘述。
qbs用于保存当前状态下的qbs值。初始为0。每次addPerson时加一,每次将父节点为自身的点(即根节点)设置为别的节点时,qbs减一。
3、MyGroup
private int id; private HashMap<Person, Integer> people = new HashMap<>(); private int ageMean = 0;
people保存了每个person和对应的age值。
ageMean保存当前状态下people的age之和。
4、MyMessage
private int id; private int socialValue; private int type; private Person person1; private Person person2; private Group group;
都是JML中要求的规格。
5、各种异常
private static int times = 0; private static HashMap<Integer, Integer> map = new HashMap<>(); private int id1; private int id2;
times记录每个异常的触发次数。
map记录每个id抛出该异常的次数,不存在表示抛出次数为0。
id1,id2记录本次抛出异常的id。可能只有一个属性id。

浙公网安备 33010602011771号