分析在本单元自测过程中如何利用JML规格来准备测试数据
先说结论,压根没测,依然不妨碍三次作业中测一次过强测全满分。
既然作业要求写这一部分,那我就来说一下不测的理由。
首先,第三单元完全就是看JML写代码,我只需要保证代码语义和JML语义相同就可以了,而要保证这一点对大部分函数基本上没有难度,真正有点难度的是为了在减小时间复杂度而进行优化的同时保证代码语义不变。而这点在理解JML的“实际意义”后,可以理论上进行证明,也就是可以证明代码语义和JML语义是等价的。由了这一点的保证,我就有足够的自信不去大量测试。但是,依然要进行一些小规模的测试,是为了检测是否出现手残、打错字、看错标识符等低级错误,这部分任务就可以交给中测。
其次,我也没有Junit进行测试,原因也很简单,Junit根本就一个稍微便捷一点的插件,本身根本不能提供任何测试样例和测试模板等。所以Junit本质上有和没有就没有区别。
梳理本单元的架构设计,分析自己的图模型构建和维护策略
先上类图
第三单元架构相比前两个单元来说很简单,以此实现各个接口即可。唯一有所不同的点是我又加入了Edge类,和ConnectedComponent类。Edge类本质上就是个C语言结构体,包含三个字段,边的两个节点id,以及边的权值。而ConnectedComponent类则存储了该连通分量中所有的点和边,以及点到边的映射关系,并且缓存了该连通分量的最小生成树的权值。
第一次作业中没有使用Edge类和ConnectedComponent类,直接在NetworkImpl类中构建了并查集,从第二次作业开始加入了这两个类,NetworkImpl中也只需要维护连通分量即可,查询方法直接调用ConnectedComponent类中的最小生成树和最短路径方法即可。
总体而言架构很简单,同时也很清晰。而且由于第三单元完全是被JML牵着鼻子走,指导书上也没有写整个程序是要干什么的,因而也就没有考虑架构的必要的了,每次作业在上一次的基础上增加一些功能,适当改变一些数据维护的形式就可以了。
按照作业分析代码实现出现的性能问题和修复情况
首先我没有出现任何性能问题,所以下面就来说一下我所采取的优化方案。我的优化只以不TLE为目标,总所周知,没有复用价值的程序在出现性能问题前不需要考虑性能问题。
第一次作业
第一次作业可能会爆TLE的只有isCircle方法和queryBlockSum方法。采用路径压缩的并查集就可以解决问题,相信大家基本上都不是采用暴力深搜或者广搜的方法。
第二次作业
第二次作业出现了最小生成树的算法,我采用了并查集优化的Kruskal算法,并将结果保存在成员变量中,这样在连通分量不加边的时候没有必要再算一遍。
第三次作业
第三次作业出现了最短路径算法,我采用了堆优化的Dijkstra算法,因为最短路径值和输入参数(也就是起点和终点)有关,在一个连通分量有很多点的时候缓存这些最短路劲值要耗费很长时间,因而没有采取缓存的方法。及时没有缓存,粗略得算一下极限情况下应该也不会导致TLE。
然而,除此之外,由于第三次作业的数据放宽,前两次作业的问题暴露了出来。Group接口下的getValueSum方法如果暴力O(n2),理论上稍微极限一点的数据就能卡掉,所以我将该值缓存了下来,每次在该组中加、减人以及添加关系的时候更新该值,如此查询就是O(1)的复杂,维护该值的时间复杂度为O(n),在题目要求下不会爆TLE。同时我还维护了ageSum和ageSquareSum这两个变量,用于加速getAgeMean和getAgeVar的查询。需要注意的是getAgeVar要注意精度问题,不能在忽略向下取整的情况下进行数学化简(虽然不知道强行化简强测会不会爆精度)。
请针对下页ppt内容对Network进行扩展,并给出相应的JML规格
显然这些新出现的Person要继承原有的Person接口并增设一些成员变量
Advertiser
需要维护一个集合用于存储提出委托的产品生产商
Producer
需要维护一个集合用于存储生产的产品,对于委托的广告商可以不存储。产品对象需要单独写一个类并保存附加信息。另外还需要一个变量来记录销售额。
Customer
需要维护一个集合用于存储购买的商品,还需要一个属性用于表示消费者偏好。
运行规则
Advertiser只能向同一连通分量上的Person或者Customer发广告。Producer也只能向同一连通分量上的Advertiser发出委托。Customer也只能向同一连通分量上的Advertiser所要商品。商品的销售路径是Customer到Producer的路径中经过Advertiser的最短的一条,如有多条随机选一条。
发广告
// in Advertiser
/*@ public instance model non_null Integer[] producers;
@*/
/*@ public normal_behavior
@ requires (\exist int i; 0 <= i && i < people.length; people[i].getId() == id && people[i] instanceof Advertiser) && getPerson(id).producers.length > 0;
@ assignable people[*].messages
@ ensures (\forall int i; 0 <= i && i < people.length;(\forall int j;0 <= j && j < \old(people[i].messages.length)(\exsit int k;0 <= k && k < people[i].messages.length;\old(people[i].messages)[j] == people[i].messages[k])));
@ ensures (\forall int i; 0 <= i && i < people.length; (isCircle(id,i) && id != i) ==> (people[i].messages.length == \old(people[i].messages.length));
@ also
@ public exceptional_behavior
@ signals (PersonIdNotFoundException e) (\forall int i; 0 <= i && i < people.length; people[i].getId() != id || !(people[i] instanceof Advertiser));
@ also
@ public exceptional_behavior
@ signals (NoAdvertisementException e) (\exist int i; 0 <= i && i < people.length; people[i].getId() == id && people[i] instanceof Advertiser) && getPerson(id).producers.length == 0;
*/
void sendAdvertisement(int id) throws PersonIdNotFoundException,NoAdvertisementException;
查询销售额
// in Producer
/*@ public instance model Integer sales;
@*/
/*@ public normal_behavior
@ ensures \result == sales;
@ also
@ public exceptional_behavior
@ signals (PersonIdNotFoundException e) (\forall int i; 0 <= i && i < people.length; people[i].getId() != id || !(people[i] instanceof Producer));
*/
int /*@ pure @*/ getSales(int id)throws PersonIdNotFoundException;
委托发广告
// in Advertiser
/*@ public instance model non_null Integer[] producers;
@*/
// in Producer
/*@ public instance model non_null Product[] products;
@*/
/*@ public normal_behavior
@ requires (\exist int i; 0 <= i && i < people.length; people[i].getId() == id1 && people[i] instanceof Producer && ((Producer)people[i]).products.length > 0) && (\exist int i; 0 <= i && i < people.length; people[i].getId() == id2 && people[i] instanceof Advertiser);
@ assignable ((Advertiser)getPerson(id1)).producers
@ ensures (\forall int i; 0 <= i && i < \old(((Advertiser)getPerson(id1)).producers.length);(\exist int j;0 <= j && j < ((Advertiser)getPerson(id1)).producers.length; ((Advertiser)getPerson(id1)).producers[j] == \old(((Advertiser)getPerson(id1)).producers)[i]));
@ ensures ((Advertiser)getPerson(id1)).producers.length = \old(((Advertiser)getPerson(id1)).producers.length) + 1;
@ ensures (\exist int i;0 <= i && i < ((Advertiser)getPerson(id1)).producers.length; ((Advertiser)getPerson(id1)).producers[i] == id2);
@ also
@ public exceptional_behavior
@ signals (PersonIdNotFoundException e) (\forall int i; 0 <= i && i < people.length; people[i].getId() != id1 || !(people[i] instanceof Producer)) || (\forall int i; 0 <= i && i < people.length; people[i].getId() != id2 || !(people[i] instanceof Advertiser));
@ also
@ public exceptional_behavior
@ signals (NoProductException e) (\exist int i; 0 <= i && i < people.length; people[i].getId() == id2 && people[i] instanceof Advertiser) && (\exist int i; 0 <= i && i < people.length; people[i].getId() == id1 && people[i] instanceof Producer && ((Producer)people[i]).products.length == 0);
*/
void entrustAdvertiser(int id1,int id2) throws PersonIdNotFoundException,NoProductException;
本单元学习体会
本单元主要是是学习了JML语言,以及一些契约式编程的思想。针对JML语言我只能说可读性极差,稍微复杂一点的条件语句就括号满天飞。ensures或者requires语句既不像纯数学语言一样简洁也不像编程语言一样可以通过定义程式进行复用来简化代码,同样的思想(比如对一个简单的addXxxx类型的方法的规范描述)要在n个方法上写n遍,写起来费劲,看起来更费劲。做个比喻,将方法的规范描述看作一个程序,那JML的方法规格就是个main函数战士,一main到底。不知道最新版的JML是否还是如此,至少我们目前使用的JML我是再也不想看第二眼。针对这一问题的一个可行的办法就是在对每个规格都额外添加一个可能不严谨但容易理解的自然语言版本(当然要求说人话),在理解方法的意图后再去看JML描述可以显著提生效率。而对于契约式编程的确是一种不错的思想,再没有真正接触到这个名词前我就产生了这样的想法,但是一直也没有尝试过,或许不久的将来就能做到在对输入数据有一定约束的情况下严格证明程序的正确性(虽然也不知道这是否已被证明为不可能)。