BUAA_OO 第三单元总结——JML
BUAA_OO 第三单元总结——JML
第三次作业的目的是建立一个社交通信网络,通过JML
帮助大家理解并且掌握JML
有关内容。说一说一,这单元为了让我们认识到JML
,图论的内容还是相当多的,如果我们按照正常JML
的规格去写代码的话,最后的结果一定是超市的,这个也是学习JML
的一部分,理解起来JML
还是相当费劲的。当然最后因为偷懒导致有的地方还是超时了。
从JML角度构造测试数据
在测试方面,我在第一次作业的时候尝试去junit
去对于每个方法分别进行测试,其实测试方法很简单,主要根据对应的方法设置对应的网络图,然后进行简单的测试,然后就翻车了。
翻车的原因很简单,抛出异常错误的时候没有写换行符,然后强测直接寄了。当然我也不知道为什么弱测没有测出来,而且也不知道自己为什么看到没换行还没有反映出了点什么,总之真的有点崩溃。
当然这个zz错误暂且不提,在后来的测试中,我逐渐发现了junit
这个测试方法具有相当大的局限性,那就是测试样例内容构造的难度,当具有大量数据的时候,该方法就无法测出一些比如说数据越界或者并查集错误等问题,因此并不适用,最后我还是回到了数据生成器+对拍的方法。
但是最后的数据生成器还是需要与JML联系起来,那就是分部进行方法测试。这样的测试的原因或者好处便是在于数据生成的不确定性,无论是添加人还是关系,由于数据生成的不确定性,因此覆盖率很难达到,所以需要的方法是限定范围内大量构造数据,这里变我用了c++的方法构造。
范围限定使用了随机数的方法
mt19937 mt(time(0));
uniform_int_distribution<int> fop(1,2),op(1,9),V(1,1000),val(1,500),type(0,1);
首先大量生成基础网络
while(t--) {
int nowfop=fop(mt);
switch(nowfop) {
case 1: {
ap();
break;
}
case 2: {
ar();
break;
}
case 3: {
ag();
break;
}
case 4: {
atg();
break;
}
}
}
然后在对于每个方法单独随机生成数据
为什么不测所有的方法:因为如果想保证构造方法时随机生成的数据能连接到网络,就需要大量的数据生成,如果每个方法都这样,就有点太卡了。而另一个好处是,这样测试能把错误精确到定位到具体方法上。
JML
的另一个好处在于能看出方法的复杂度,方便进行优化。
例如Group
的getValueSum
方法:
/*@ ensures \result == (\sum int i; 0 <= i && i < people.length;
@ (\sum int j; 0 <= j && j < people.length &&
@ people[i].isLinked(people[j]); people[i].queryValue(people[j])));
@*/
public /*@ pure @*/ int getValueSum();
我们能从JML
中直观的读取到他所要表示的内容,但是在另一方面,我们又不能用它所写的内容直接进行表示,这就是JML
语言魅力之处:它能直观地帮助我们获取代码的写法,但是有需要我们开动脑筋,将方法构造最优化。
图模型构建和维护策略
其实无论是怎么优化,优化的方法都是把O(N^2)乃至更高复杂度的算法优化为O(n)甚至O(1)的算法
第九次作业
并查集的使用:qci
在isCircle
函数中,为了降低复杂度,实现了一个并查集,并查集的维护在addRelation
方法中
而我看大多数人用的都是通过find
的递归方法来进行查询和处理,这样做的方法确实很简单。我最开始也是这么写的,但是后来我在查Kruskal
算法的时候发现其实还可以不用find方法,即把每次更新就把所有相连的节点指向同一个节点,因为需要遍历,这样的时间复杂度确实会是上升,但是还是O(n),而因此最后使用了这个方法。
看着也挺方便点
容器的选择:hashMap
的使用
按照我的想,如何把复杂度降下去,那就是用hashmap
,它的查询时间复杂度是(logN),因此极大地降低了查找的时间,因为有好多的方法需要遍历然后查询,这样如果是数组遍历无疑时间复杂度会上升。
第十次作业
Kruska
l算法:qlc
这个算法其实没啥好说的,我是自己生成了一个tree
类来进行点和边的组合,然后其中调用了compareTo
先进行了排序,然后就是正常的计算了。
阴险狡诈的qgvs
这个互测可把我给淦烂了,之前我知道这是O(N^2)复杂度,但是在测试之前我确实没有想到什么好的方法去降低,互测时候想起来了。。。
如何降低人与人之间的关系价值,把他降低复杂度,最好的方法当然还是在这个Group内就把每个人与这个组里面的每个人的关系价值先计算出来,这里用HashMap
表示这里面的影响因素在于三点:
- Group增加新人
- Group删除新人
- 添加关系
addRelation
因此只要在三个地方注意更新就好了。然后就通过遍历HashMap
就可以得到最后的结果。
public int getValueSum() {
int sum = 0;
for (Person person : people) {
sum += values.get(person);
}
return sum;
}
第十一次作业
Dijkstra算法
在第十一次作业中,有一个间接的发送消息,会进行最短路径搜寻的工作,因此需要用Dijkstra算法进行搜索,但是这里面存在的问题是:正常的Dijkstra算法时间复杂度是O(N^2),因此需要使用堆优化。
java
的堆优化还是比较方便的,这个PriorityQueue
类是真的神奇,他会直接对队里面的数据进行排序,形成小顶堆,因此时间复杂度就降低为O(nlogn)了,它不用我们再额外新建堆优化的容器,因此实现起来也要轻松许多。
维护过程和bug修复
其实对于本单元的设计,维护过程就是一个bug修复的过程,我所有的错误都在时间复杂度上(除了那个特别nt的),因此维护过程也就是我一步步对于JML
的理解更加深刻的过程,如何为维护各种方法,理解JML
的语言的实际算法的不同,这就是一个逐步了解JML
并学习的过程。
Network扩展
出现了几种不同的Person
- Advertiser:持续向外发送产品广告
- Producer:产品生产商,通过Advertiser来销售产品
- Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
- Person:吃瓜群众,不发广告,不买东西,不卖东西
首先,由于这三种新的身份仍然是Person
,因此扩展直接定义三个类去继承Person
类
- Advertiser发送到广告可以看作是一个Message,本身带有Message
- Customer有一个偏好的属性preference
- Message增加两个信息,
AdvertiseMessage
(type = 2)和SaleMessage
(type = 3)分别代表广告信息和购买信息
NetWork
增加的方法为:containsAdvertiseMessage
,getAdvertiseMessage
、addAdvertiseMessage
,sendAdvertiseMessage
,containsSaleMessage
、getSaleMessage
、addSaleMessage
,sendSaleMessage
是否有广告信息
//@ ensures \result == (\exists int i; 0 <= i && i < messages.length && message.getType() == 3; messages[i].getId() == id);
public /*@ pure @*/ boolean containsAdvertiseMessage(int id);
是否有销售信息
//@ ensures \result == (\exists int i; 0 <= i && i < messages.length && message.getType() == 4; messages[i].getId() == id);
public /*@ pure @*/ boolean containsSaleMessage(int id);
获得广告信息
/*@ public normal_behavior
@ requires containsAdvertiseMessage(id);
@ ensures (\exists int i; 0 <= i && i < messages.length; messages[i].getId() == id &&
@ \result == messages[i]);
@ public normal_behavior
@ requires !containsAdvertiseMessage(id);
@ ensures \result == null;
@*/
public /*@ pure @*/ Message getAdvertiseMessage(int id);
获得销售信息
/*@ public normal_behavior
@ requires containsSaleMessage(id);
@ ensures (\exists int i; 0 <= i && i < messages.length; messages[i].getId() == id &&
@ \result == messages[i]);
@ public normal_behavior
@ requires !containsSaleMessage(id);
@ ensures \result == null;
@*/
public /*@ pure @*/ Message getAdvertiseMessage(int id);
发送广告
/*@ public normal_behavior
@ requires containsAdvertiseMessage(id);
@ assignable advertiseMessages,productMessages;
@ ensures !containsAdvertiseMessage(id) && advertiseMessages.length == \old(advertiseMessages.length) - 1 &&
@ (\forall int i; 0 <= i && i < \old(advertiseMessages.length) && \old(advertiseMessages[i].getId()) != id;
@ ensures productMessages.length == \old(productMessages.length)+1;
@ (\forall int i; 0 <= i && i < \old(productMessages.length);
@ (\exists int j; 0 <= j && j < productMessages.length; productMessages[j].getId() == \old(productMessages[i].getId());
@ also
@ public exception_behavior
@ signalonly (AdvertiseMessageNotFound e) !containsAdvertiseMessage(id);
public void sendAdvertiseMessage(int id);
学习体会
这次课程虽然看起来相对于前面的课程任务较小,但是实现难度还有存在的,而且,由于我自己的疏忽或者说知识漏洞,错误的地方比之前还有多少。对于我而言,JML
规格无疑仍然是一个不小的挑战,一方面JML
的写法复杂度与实际有出入需要化简,另一方面对于想最短路径等图JML
的理解仍然是一个不小的挑战。从JML
撰写的角度,客观并且全面的评价实现的功能,并且用一种约束化的语言来描述,是一个很困难的事情,也是一个很规范的问题。回顾这一单元的内容,我的同学们无疑给了我很大的帮助,让我能想起来我已经忘记的图论。(忘记之后写算法真是一件折磨人的事情啊)。