第三单元总结
第三单元总结
一、自动化测试
本单元推荐使用Junit 单元测试来对自己的程序进行测试,但事实上配置比较麻烦,而且跟着推荐的两篇博客配置时,也遇到了一点问题,第一篇博客要求修改测试用例模板,将模板中生成的package的包名需去掉test,事实上我修改之后就出现错误,无法识别测试文件,搞了好长时间,最后发现不修改反而可以识别测试文件。
在了解了Junit单元测试后,发现Junit单元测试并不能很好满足我希望的本地测试要求,似乎难以对大量数据的答案的正确性进行检测,加上相对于Junit自动化测试,我还是更熟悉传统的对拍测试方法,考虑到效率等因素,我还是选择了传统的测试模式。
第一单元的正确性检查可以使用python库,第二单元我利用了类似有限状态机的方法。但是本单元的正确型检查比较麻烦,我选择了和小伙伴对拍。
查看JML规格可发现,对于生成的数据并没有严格的显示,如果数据不满足要求的话总会触发异常,而且触发异常也是一种“正确性检查”,所以这里对测试数据没有什么限制。我们可以生成大量的随机数据。
生成数据方面完全随机,在大量的数据投入下,双方都发现了错误,主要还是漏看JML规则,或者是错误理解JML规则的问题。
本单元强测互测均无bug。
二、架构设计——图模型构建和维护策略
本单元架构设计方面,主要还是根据JML规格完善方法。
在图模型构建方面,最主要的还是人之间的关系网络图。关系的网络图是带权的无向图,每个Person都保存了自己相邻的Person节点以及相应的权值。此外并没有特殊构建图模型。仅仅是在向社会网络中添加人和关系的时候维护相关量就可以了。
本单元主要是JML的学习和一些图算法的了解,涉及最小生成树算法,最短路径算法以及并查集。同时为了不超时,需要尽量压缩算法复杂度,消除O(n^2)复杂的算法,复杂度不超过O(nlog(n))。
本单元作业中,queryBlockSum指令官方推荐并查集算法。实际上我们只需在加人和加关系的时候,维护一下连通分量集和连通分量的数目就可以了,而且本单元的三次作业均不必再次扩展。可以做到O(1)的复杂度。
三、按照作业分析代码实现出现的性能问题和修复情况
-
queryLeastConnection需要使用最小生成树算法,由于可能超时,需要加入堆优化,可以使用java现有的优先队列。
-
queryGroupValueSum可以在改变Group里的人时,修改相关的ValueSum。
-
queryGroupAgeVar可以在加人,删人或者加关系的时候维护相关变量,在查询时做到O(1)的复杂度;
对于queryGroupAgeVar,有公式
\(\sum (\mathrm{age}_i - \overline{\mathrm{age}})^2 = \sum \mathrm{age}_i^2 - 2 * \overline{\mathrm{age}} \sum \mathrm{age}_i+ n * \overline{\mathrm{age}}^2\)
因此我们可以在每次加人或者加关系的时候维护\(\sum \mathrm{age}_i^2\), \(\sum \mathrm{age}\), 而且由于计算结果是取整的,因此这样算在精度上不会出现错误。
-
sendIndirectMessage指令涉及到最短路径算法,同样为了避免超时,需要进行堆优化。
-
如果需要在集合中间删减元素的话,可以使用LinkedList替代ArrayList,效率更高。
-
此外,比较容易忽视的一个点就是第三次作业中有关表情包信息操作,注意的是这里的一个EmojiId可能对应多个不同的Id(Message)。需要区别EmojiId和Id;
这些优化相当于将一个方法的复杂度平摊到众多的方法里面。
如果不进行相关查询的话,速度可能会慢,但是进行大量的高复杂度的查询命令的话,速度会大大提升。在课程组10000指令的规模下,相比未优化前,可以减少程序最大运行CPU时间,保证不超时。
(也许不进行一些优化也能过强测,但是很可能在互测遇见一些极其***钻的数据,导致超时。)
四、bug分析
主要是和小伙伴对拍的时候发现的错误
- 第一次没有注意到EmojiId可能对应多个不同的Id(Message)。
- queryGroupAgeVar 没有除人数,主要是漏看了JML,凭这个方法的名字主观判断了
- 细节问题,比如有的指令要删信息,但是一些相似的指令不需要删,就导致了主观性的错误。
- 没有先判断异常,导致出错。
- 最多的问题就是在优化里出错了,在优化里要格外注意,往往需要在多个方法里对一个变量进行维护,比较分散,容易忽略,导致出错。
五、对Network进行扩展,并给出相应的JML规格
设计的类如下:
新增的方法和部分JML规格
//public instance model non_null Product[] product;
/*@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < people.length;
@ people[i].getId() == id && people[i] instanceof Producer);
@ assignable product;
@ assignable getProducer(id).product;
@ ensures product.length == \old(product.length) + 1;
@ ensures (\forall int i; 0 <= i && i < \old(product.length);
@ (\exists int j; 0 <= j && j < product.length; product[j] ==
@ (\old(product[i]))));
@ ensures (\exists int i; 0 <= i && i < product.length; product[i] ==
@ product);
@ ensures getProducer(id).product.length ==
@ \old(getProducer(id).product.length) + 1;
@ ensures (\forall int i; 0 <= i && i < \old(getProducer(id).product.length);
@ (\exists int j; 0 <= j && j <getProducer(id).product.length;
@ getProducer(id).product[j] == (\old(getProducer(id).product[i])));
@ ensures (\exists int i; 0 <= i && i < getProducer(id).product.length;
@ getProducer(id).product[i] == product);
@ also
@ public normal_behavior
@ requires !(\exists int i; 0 <= i && i < people.length;
@ people[i].getId() == id && people[i] instanceof Producer);
@ assignable \nothing;
@*/
public void createProduct(Product product,int id);
public void advertiseProduct(Product product,int producerId,int advertiserId);
public void watchingAdvertisements(int customerId);
public void buyProduct(int customerId,int productId);
/*
@public normal_behavior
@requires (exists int i;0 <=i && i<product.length ;
@ product[i].getid == id );
@ensures (exists int i;0 <=i && i<product.length ;
@ product[i].getid == id && \result ==
@ product[i].getSalesPath );
@also
@requires !(exists int i;0 <=i && i<product.length ;
@ product[i].getid == id );
@ensures \result == null;
*/
public String querySalePath(int id);
/*
@public normal_behavior
@requires (exists int i;0 <=i && i<product.length ;
@ product[i].getid == id );
@ensures (exists int i;0 <=i && i<product.length ;
@ product[i].getid == id && \result ==
@ product[i].getSalesVolume );
@also
@requires !(exists int i;0 <=i && i<product.length ;
@ product[i].getid == id );
@ensures \result == null;
*/
public int querySalesVolume(int id)
六、本单元学习体会
本单元主要学习了一些基础的JML语法,并且复习了一些基础的图算法。
但是阅读JML的时候,对于众多的(),{},并不好掌握,我使用了VS Code准化为.v文件进行阅读,可以把匹配的()和{}用不同的颜色标出来,便于阅读,此外,对于每次作业的迭代,可以使用VS Code的文件对比功能,找到需要进行扩增的点。
JML语言十分准确,保证了课程组传递给我们的信息的准确性,但是一堆的\exists和\forall着实不容易阅读,对于一个简单的向一个容器中加一个元素,都要分四五句着实有点麻烦,可能程序的规格本身就不那么容易地准确表达吧。