第三单元博客总结
一、测试部分
测试数据的准备
对于这次的作业,我们可以根据JML规格的描述来构造测试数据,对每一个方法做出专门的测试。任何一个方法的JML规格都是由requires
,assignable
,ensures
三部分组成,对于有不同情况需要有不同操作的方法,会有多个requires(also)
,assignable
,ensures
。当我们构造测试程序时,要充分考虑这三个部分。
首先是requires(also)
部分,这部分是方法正确执行后面内容的前提条件,是方法能够保证正确性的数据范围。对一个或者多个requires(also)
部分,我们的测试数据应该保证充分覆盖,要求每一个requires(also)
都要有对应的3-5
条数据。
其次是ensures
最后是assignable
,这部分在我们检查正确性的时候需要格外注意。因为我们在编写程序的时候,这一行小字常常被粗心的同学遗忘和忽略,很容易在编写程序的时候违反assignable
的规定。
这里我们拿storeEmojiId
函数来举例子。
/*@ public normal_behavior
@ requires !(\exists int i; 0 <= i && i < emojiIdList.length; emojiIdList[i] == id);
@ assignable emojiIdList, emojiHeatList;
@ ensures (\exists int i; 0 <= i && i < emojiIdList.length; emojiIdList[i] == id && emojiHeatList[i] == 0);
@ ensures emojiIdList.length == \old(emojiIdList.length) + 1 &&
@ emojiHeatList.length == \old(emojiHeatList.length) + 1;
@ ensures (\forall int i; 0 <= i && i < \old(emojiIdList.length);
@ (\exists int j; 0 <= j && j < emojiIdList.length; emojiIdList[j] == \old(emojiIdList[i]) &&
@ emojiHeatList[j] == \old(emojiHeatList[i])));
@ also
@ public exceptional_behavior
@ signals (EqualEmojiIdException e) (\exists int i; 0 <= i && i < emojiIdList.length;
@ emojiIdList[i] == id);
@*/
首先是requires(also)
部分,我们可以看到requires(also)
部分将情况分为了两种一种是emojiIdList
没有该元素,一种是emojiIdList
中有该元素。在构造数据的时候就应该按照其中有该元素和没有该元素两种。再看ensures
部分,第一个和第二个ensures
是表示将该元素插入emojiIdList
和emojiHeatList
之中,第三个ensures
则是表示原有的元素和emojiHeatList
对应关系不变。因此在构造数据的时候,我们不仅要构造将该元素插入空emojiIdList
的数据,也要构造里面有一些别的元素然后再插入元素的数据以便检查能否满足这三个ensures
。最后是assignable
这部分我们要保证不去改变除了emojiIdList
和emojiHeatList
之外的数据。
测试工具
使用了这么多次大佬的测试工具以后,我终于第一次编写出了属于我自己的测试工具>_<
。
首先是测试数据,测试数据我分为两个部分:
-
根据
JML
规格自己手搓的测试数据。 -
根据随机数随机生成的大量数据。
在正确性检查这部分,我同样也是分为两个部分。手搓数据那一部分,由于数据较有针对性并且数据量很小,我自己根据逻辑写出正确结果进行对比。对于随机数生成的大量数据,由于这次作业比较容易对拍,我找到几个同学的代码一起对拍。
根据不同情况生成指令代码如下(以ar
为例):
int id1(){
return (rand()%count);
}
int i = id1();
if(rand()%3!=0){
fprintf(out,"ar %d %d %d\n",id1(),id1(),age());
} else{
fprintf(out,"ar %d %d %d\n",i,i,age());
}
二、架构设计
首先是图节点存储。由于在这次作业中,图节点都是使用同一的id
来查询,使用hashmap()
来存取最为合适。但是有一些时候我们还需要知道节点顺序或者需要遍历,ArrayList()
也有优势之处。因此我是用hashmap()
存取为主,ArrayList()
存取为辅的方式存取数据(虽然这样做比较浪费空间,维护起来也略微麻烦)。
private HashMap<Integer, Person> mapOfPeople;
private ArrayList<Person> people;
三、性能问题和修复情况
在性能问题这方面,我觉得我还是比较有发言权的>_<
,三次作业无一不产生了性能问题。
首先,是第一次作业,我以为本单元作业只需要将JML
规格照葫芦画瓢翻译成JAVA
就行了,于是我的第一次作业所有的数据都是ArrayList
,所有的查询都是一重套一重循环。于是第一次作业我就成功TLE
了。
后来我将所有需要查询的数据采用HashMap
存取,实现了O(1)
查询。并且对于循环中多次使用的节点,采用零时变量存下来,避免一次又一次的查询。
public int getAgeVar() {
int i;
int sum = 0;
int ageMean = getAgeMean();
for (i = 0; 0 <= i && i < people.size(); i++) {
sum = sum + (people.get(i).getAge() - ageMean)
* (people.get(i).getAge() - ageMean);
}
return (people.size() == 0 ? 0 : (sum / people.size()));
}
在第一次作业中,还有qci
也是比较难的一个点。由于我傻乎乎的沿着图一点点的查询,于是又TLE
。对于这个问题,我才用并查集的方式,将联通的所有节点放在一个容器中。这样,我们只需要查询两个节点是否在一个容器里就可以知道他们是否是连起来的了。
第二次作业是qgvs
。我没有将每一个类的valueSum
动态维护,而是在查询的时候二重循环计算数值。导致在一些抗压性测试下无法通过。于是,我将程序中便于维护,经常查询的量全部都动态维护起来,提高了性能。
private int valueSum;
public void addPerson(Person person) {
//require
if (!hasPerson(person)) {
people.add(person);
int i;
for (i = 0; i < people.size(); i++) {
if (person.isLinked(people.get(i))) {
valueSum = valueSum + 2 * person.queryValue(people.get(i));
}
}
} else {
System.out.println("input is not meet addPerson's require");
}
}
public int getValueSum() {
return valueSum;
}
第三次是sim
。对于dijkstra
算法,我们需要找到权值最小的节点进行下一次操作,然而,我在找权值最小的节点时居然使用的是排序……
waitQueue.sort((a, b) -> {
return minLink.get(a) - minLink.get(b);
});
于是我把这个排序改掉,变成遍历,我的朴素dijkstra
算法复杂度从O(n^2logn)
变成O(n^2)
后通过了测试。
public int findMiniWay(HashMap<Integer, Person> personHashMap, Message handleMessage) {
handleMessage.getPerson2().getMessages().add(0, handleMessage);
HashMap<Integer, Integer> minLink = new HashMap<>();
ArrayList<Integer> waitQueue = new ArrayList<>();
waitQueue.add(handleMessage.getPerson1().getId());
minLink.put(handleMessage.getPerson1().getId(), 0);
ArrayList<Person> peopleNext = ((MyPerson) handleMessage.getPerson1()).getAcquaintance();
ArrayList<Integer> valueNext = ((MyPerson) handleMessage.getPerson1()).getValue();
for (int i = 0; i < peopleNext.size(); i++) {
Person person = peopleNext.get(i);
if (!waitQueue.contains(person.getId())) {
waitQueue.add(person.getId());
}
if (minLink.containsKey(person.getId())) {
if (minLink.get(person.getId()) > valueNext.get(i)) {
minLink.put(person.getId(), valueNext.get(i));
}
} else { minLink.put(person.getId(), valueNext.get(i)); }
}
int k = 1;
while (waitQueue.size() > k) {
if (waitQueue.get(k - 1) == handleMessage.getPerson2().getId()) {
break; }
int min = 0;
int mini = 0;
for (int i = k; i < waitQueue.size(); i++) {
if (i == k) {
min = minLink.get(waitQueue.get(i));
mini = k;
} else if (min > minLink.get(waitQueue.get(i))) {
min = minLink.get(waitQueue.get(i));
mini = i; } }
if (mini != k) {
int t = waitQueue.get(mini);
waitQueue.remove(waitQueue.get(mini));