第三单元总结
第三单元总结
一、测试数据构造
1、随机数据测试
使用程序随机生成数据。在一组数据的头部先添加一些诸如ap、am、ag等添加基本元素的指令,之后再按照设定的比例随机生成各个指令。生成好数据之后我选择的是和多个人的程序对拍来验证正确性。自动化评测能够找到比较明显的bug,并通过调高时间复杂度高的指令的比例来验证CPU时间是否会超过限制。
2、手工构造数据测试
在随机轰炸之后,基本可以保证正确性和性能。这时可以手工构造一些数据针对某一指令做功能或性能上的测试(在互测环节也可以使用)。例如针对ag,可以向group加入超过JML限制的人数;对于高复杂度的指令,随机生成的数据有可能不够强,在这一环节可以手工构造比较复杂的图,并在指令中安排多次查询来检测是否会超时。
二、架构设计
本次作业需要实现的类已经给出,在这个基础上根据自己的需求可以新增类;方法只需要阅读JML了解需求并实现即可。难点在于每次作业中图的实现。
第九次作业
本次作业中的qci指令要求查询两个点之间是否存在一条简单路径。
本来打算采用dfs的方法实现,后来看了助教的提示,使用了并查集的方法来实现,有效降低了时间复杂度。其中并查集的节点为Perosn,两个点之间的路径为Person的Relation。
并查集是一片森林,每一颗树里面的节点都是互相联通的,不在一棵树的节点必定不连通。因此要判断两个点是否联通,只需要判断两个节点的根节点是否相同。在本次作业中,我选择用HashMap来存储关系,value所对应的id是key值对应id的父节点。在加入person时,便在HashMap中加入一个key和value均为personId的元素;加入relation时只需将两棵树合并即可。这样便可以对并查集进行维护。
第十次作业
本次作业中需要完成的图论部分是最小生成树,我选用了Kruskal算法实现。首先取出id所在的连通块,初始时这个联通块的每个点都是一个划分,每次都取出value最小的关系,并将两个划分合并,直到连成一个划分。在这个过程中可以使用之前实现的并查集来实现。在加入person和relation时需要维护连通块的内容。
第十一次作业
本次作业需要实现最短路径,我使用Dijkstra算法:就是不断寻找和已知点集距离最小的点,那么这个点的离初始点之间的距离就可以被确定;重复该过程直到目标点与初始点之间的最短距离被找到。
三、性能问题和修复情况
开始时我仅仅对图部分的性能关注的比较多,但是还有另外一些地方会出现性能问题。在这几次作业中,有几个指令可以通过维护一个变量实现,而如果单纯“翻译”JML,容易在互测中被卡超时。例如qbs不通过一个变量储存的话,时间复杂度是O(n^2),改为变量存储则为O(1)(虽然需要一些步骤维护,但是总体复杂度小于前者),时间复杂度大大降低。这样的问题出现了多次,也就不再赘述。
在最后一次作业中,强测中的有个点出现了CTLE,查看了迪杰斯特拉算法的实现,发现没有什么问题。之后使用IDEA中的分析工具发现ArrayList的contains方法耗时很多,就想到改为HashMap/HashSet,查询速度快了很多。
四、扩展与JML规格
增加Advertiser、Producer、Customer三个继承自Person的类;PurchaseMessage 继承自Message;还有一些异常类。
Network增加int[] advertise,其中存放一个广告对应的产品id;int[] advertiserId,存放advertise对应的advertiser;int[] sale,存放对应的销售额。
发送广告
/*@ public normal_behavior @ requires contains(advertiserId) && (getPerson(advertiserId) instanceof Advertiser) @ assignable advertise, advertiser; @ ensures advertise.length == \old(advertise.length) + 1; @ ensures advertiser.length == \old(advertiser.length) + 1; @ ensures sale.length == \old(sale.length) + 1; @ ensures (\forall int i; 0 <= i && i < \old(advertise.length); (\exists int j; 0 <= j && j < advertise.length; advertise[j] == (\old(advertise[i])))); @ ensures (\forall int i; 0 <= i && i < \old(advertiser.length); (\exists int j; 0 <= j && j < advertiser.length; advertiser[j] == (\old(advertiser[i])))); @ ensures (\forall int i; 0 <= i && i < \old(sale.length); (\exists int j; 0 <= j && j < sale.length; sale[j] == (\old(sale[i])))); @ ensures (\exists int i; 0 <= i && i < advertise.length; advertise[i] == productId; advertiser[i] == advertiserId; sale[i] == 0); @ also @ public exceptional_behavior @ signals (PersonIdNotFoundException e) !contains(advertiserId); @ signals (PersonIsNotAdvertiserException e) !(getPerson(advertiserId) instanceof Advertiser) @*/ public void addAdvertise(int productId, int advertiserId) throws PersonIdNotFoundException, PersonIsNotAdvertiserException;
添加购买产品的消息
/*@ public normal_behavior @ requires contains(customerId) && (getPerson(customerId) instanceof Advertiser) @ assignable messages; @ ensures messages.length == \old(messages.length) + 1; @ ensures (\forall int i; 0 <= i && i < \old(messages.length);
(\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i])))); @ ensures (\exists int i; 0 <= i && i < messages.length; messages[i].equals(((Customer)getPerson(customerId)).purchase); @ also @ public exceptional_behavior @ signals (PersonIdNotFoundException e) !contains(customerId); @ signals (PersonIsNotAdvertiserException e) !(getPerson(customerId) instanceof Customer) @*/ public void addPurchaseMessage(int customerId) throws PersonIdNotFoundException, PersonIsNotCustomerException;
查询销量
/*@ public normal_behavior @ requires containsProduct(id); @ ensures (\exists int i; 0 <= i && i < advertise.length; advertise[i] == id && \result == sale[i]); @ also @ public exceptional_behavior @ signals (ProductIdNotFoundException e) !containsProduct(id); @*/ public /*@ pure @*/ int queryProductSale(int id) throws ProductIdNotFoundException;
五、本单元学习体会
这个单元除了第一次作业时理解JML规格花了比较多的时间,完成作业的时间相比前两个单元少了很多,难度相比之前也小了些,只需要根据规格描述完成即可。这样一来就有了更多时间构造评测数据,进行测试。
在这个单元的理论、作业、实验中,我获得了阅读JML、自己写JML的能力,相信在未来这些能力能被广泛使用。