OO_第三单元总结
如何利用JML规格来准备测试数据
在第二次作业互测中有位同学因为忽略了一些前置条件和后置条件,结果抛出空指针和除零异常了,因此可以发现JML规格是很严谨的,所以可以多琢磨JML规格去准备测试数据。
比如
/*@ ensures \result == (people.length == 0? 0:
@ ((\sum int i; 0 <= i && i < people.length; people[i].getAge()) / people.length));
@*/
public /*@ pure @*/ int getAgeMean();
可以构造people.length == 0
的测试数据。
比如
/*@ public normal_behavior
@ requires contains(id);
@ ensures \result == ……
@ also
@ public exceptional_behavior
@ signals (PersonIdNotFoundException e) !contains(id);
@*/
public /*@ pure @*/ int queryLeastConnection(int id) throws PersonIdNotFoundException;
可以构造!contains(id)
的测试数据。
此外,照着JML规格写很容易超时,但是照着JML规格写不容易出错,因此我觉得可以写两种版本,一个版本是照着JML规格的,另一个版本是为了避免超时做了优化的,写两个版本方便对拍。
分析图模型构建和维护策略
第一次作业
qci指令查询两个节点是否连通,可以使用并查集实现。在所有指令中只有ap和ar指令会影响qci指令的图模型。
我使用了HashMap<Integer, Integer> nodes
来存取每一个节点的父节点,key为节点id,value为父节点id。
在执行ap指令时,添加一个新节点nodes.put(id, id);
(初始状态,自己与自己是连通的)
在执行ar指令时,合并连通分量,维护图模型的连通性即可。
第二次作业
qlc指令查询某个节点的最小生成树,可以使用kruskal算法实现。
我使用了HashMap<Integer, Integer> minTrees
来存取每一个根节点的最小生成树。
qlc指令和qci指令类似,也是在执行ap指令时添加新节点,在执行ar指令时合并或更新最小生成树(合并时根节点减少了,需要remove)。但是可以发现ar指令是没有限制条数的,若每执行一条ar指令就更新最小生成树,那么肯定会超时,所以我做了个标记表示需要更新,当执行qlc指令时才更新最小生成树。
第三次作业
sim指令查询某个节点到另一个节点的最短路径,可以使用dijkstra算法实现。
可以使用PriorityQueue实现堆优化,确保了poll出来的一定是距离当前节点路径最短的节点。
分析性能问题
在互测当中发现被hack超时的都是复杂度为\(O(n^{2})\)的指令,可见复杂度为\(O(n^{2})\)的指令是需要去“特殊照顾”的,比如qbs,qgvs,qci等等。
qbs可以和qci指令一起计算,即在新建一个节点时block++,在合并一个连通分量时block--。
qgvs可以创建一个valueSum变量,在执行atg、ar、dfg指令时其值可能会发生变化,需要去维护。
qci指令使用了路径压缩,即寻找根节点return时,将经过的节点的父节点都更新为根节点,可以减少下一次的寻找次数。
qlc指令限制条数了,性能上可以偷懒点🤪。
sim指令使用了堆优化,优化了性能。
Network扩展
添加购买信息
/*@ public normal_behavior
@ requires !(\exists int i; 0 <= i && i < messages.length; messages[i].equals(message)) &&
@ (message instanceof PurchaseMessage) ==> (message.getPerson1() instanceof Customer &&
@ message.getPerson2() instanceof Producer) &&
@ (message.getType() == 0) ==> (message.getPerson1() != message.getPerson2());
@ 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(message));
@ ensures (message instanceof PurchaseMessage && message.getType() == 1) ==> (\not_assigned(messages));
@ signals (EqualMessageIdException e) (\exists int i; 0 <= i && i < messages.length;
@ messages[i].equals(message));
@ signals (PersonIdNotFoundException e) !(\exists int i; 0 <= i && i < messages.length;
@ messages[i].equals(message)) &&
@ (message instanceof PurchaseMessage) &&
@ !(message.getPerson1() instanceof Customer &&
@ message.getPerson2() instanceof Producer);
@ signals (EqualPersonIdException e) !(\exists int i; 0 <= i && i < messages.length;
@ messages[i].equals(message)) &&
@ ((message instanceof PurchaseMessage) ==>
@ (message.getPerson1() instanceof Customer &&
@ message.getPerson2() instanceof Producer)) &&
@ message.getType() == 0 && message.getPerson1() == message.getPerson2();
@*/
public void addMessage(Message message) throws
EqualMessageIdException, EmojiIdNotFoundException, EqualPersonIdException;
发送购买信息
/*@ public normal_behavior
@ requires containsMessage(id) && getMessage(id).getType() == 0 &&
@ getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2()) &&
@ getMessage(id).getPerson1() != getMessage(id).getPerson2();
@ assignable messages;
@ assignable getMessage(id).getPerson1().socialValue, getMessage(id).getPerson1().money;
@ assignable getMessage(id).getPerson2().messages, getMessage(id).getPerson2().socialValue, getMessage(id).getPerson2().money;
@ ensures !containsMessage(id) && messages.length == \old(messages.length) - 1 &&
@ (\forall int i; 0 <= i && i < \old(messages.length) && \old(messages[i].getId()) != id;
@ (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i]))));
@ ensures \old(getMessage(id)).getPerson1().getSocialValue() ==
@ \old(getMessage(id).getPerson1().getSocialValue()) + \old(getMessage(id)).getSocialValue() &&
@ \old(getMessage(id)).getPerson2().getSocialValue() ==
@ \old(getMessage(id).getPerson2().getSocialValue()) + \old(getMessage(id)).getSocialValue();
@ ensures (\old(getMessage(id)) instanceof PurchaseMessage) ==>
@ (\old(getMessage(id)).getPerson1.getMoney() ==
@ \old(getMessage(id).getPerson1.getMoney()) - \old(getMessage(id).getPerson2.getProducePrice(produceId)) &&
@ \old(getMessage(id)).getPerson2.getMoney() ==
@ \old(getMessage(id).getPerson2.getMoney()) + \old(getMessage(id).getPerson2.getProducePrice(produceId)));
@ ensures (!(\old(getMessage(id)) instanceof PurchaseMessage)) ==> (\not_assigned(people[*].money));
@ ensures (\forall int i; 0 <= i && i < \old(getMessage(id).getPerson2().getMessages().size());
@ \old(getMessage(id)).getPerson2().getMessages().get(i+1) == \old(getMessage(id).getPerson2().getMessages().get(i)));
@ ensures \old(getMessage(id)).getPerson2().getMessages().get(0).equals(\old(getMessage(id)));
@ ensures \old(getMessage(id)).getPerson2().getMessages().size() == \old(getMessage(id).getPerson2().getMessages().size()) + 1;
@ also
@ public exceptional_behavior
@ signals (MessageIdNotFoundException e) !containsMessage(id);
@ signals (RelationNotFoundException e) containsMessage(id) && getMessage(id).getType() == 0 &&
@ !(getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2()));
@*/
public void sendMessage(int id) throws
RelationNotFoundException, MessageIdNotFoundException, PersonIdNotFoundException;
查询某种商品的销售额
/*@ public normal_behavior
@ requires contains(id) && getPerson(id) instanceof Producer;
@ ensures \result == (containsProduct(productId)) ? getPerson(id).getProduct(productId).getSales : 0;
@ also
@ public exceptional_behavior
@ signals (PersonIdNotFoundException e) !contains(id);
@ signals (PersonIdNotFoundException e) !(getPerson(id) instanceof Producer);
@*/
public /*@ pure @*/ int queryProductSales(int id, int productId) throws PersonIdNotFoundException;
学习体会
一开始很懵,看着一堆JML规格不知道需要实现什么功能,感觉是边写边了解的,得先从简单的JML规格开始写起,在根据方法名猜一猜,后来才越来越熟练🤣。
这一单元的感想:JML规格的好处是严谨,坏处是难读懂难写出。