BUAA-OO-UNIT3-JML
JML规格及测试:
JML规格:
JML规格提供了一种对于数据的约束,通过更加形式化的语言来对于前置条件、后置条件进行约束,从而保证程序在正确执行后能得到正确的结果。
总体来说,JML规格相较于平时使用的自然语言来说更加复杂,这在作业中也有很明显的体现,例如最短路、最小生成树的描述,让人看着感到不适。
但这样也有效的避免了二义性的出现,同时也使得实现的过程中的逻辑更加清晰。
测试:
首先是介绍了一种新的测试手段JUnit。它需要你实现一个testcase子类,通过断言的方式来进行验证。
其次还是通过自己手写评测机的方式,进行对拍。我在测试的过程中主要还是通过对拍的方式进行。
JML规格提供了对于数据的约束,通过这些约束,可以进行数据的构造,也可以进行评测机的编写。
数据主要还是根据规格进行make,通过随机数据进行全面覆盖的测试,再通过构造corner case的方式进行边界测试,最后对于时间复杂度进行测试以及优化。
架构设计:
这三次的作业是实现一个人际网络关系,主要有Network,Group,Person这几个主要的类需要实现以及增量开发。
NetWork作为最顶层的类,内部包含了Group和Person。Group可以理解为群聊,消息的发出可以是P2P也可以是P2G,通过不同消息的类来实现。
除了JML规格需要实现正确性,还需要引入一些图论算法或者是预处理对于询问进行加速。
例如并查集、kruskal实现最小生成树、堆优化的dijkstra算法等。
在增量开发的过程中,会遇到一个类的长度大于500行的问题,这就需要我们进行解耦,专门抽离出一部分内容单独实现,我是通过新开了一个algorithm类,将所有需要使用的算法放入其中。
具体实现如下:
最小生成树:
public int mst(int id) { int rt = 0; int totalNode = 0; ArrayList<Edge> edges = new ArrayList<>(); ArrayList<Person> people1 = new ArrayList<>(); MyPerson fa = find(id); for (Person person : people) { if (!find(person.getId()).equals(fa)) { continue; } totalNode++; people1.add(person); ArrayList<Person> node = ((MyPerson) (person)).getAcquaintance(); for (Person p : node) { if (find(p.getId()).equals(fa)) { edges.add(new Edge(person.getId(), p.getId(), person.queryValue(p))); } } } for (Person person : people1) { ((MyPerson) (person)).setFa(person.getId()); } edges.sort(Comparator.comparingInt(Edge::getValue)); int totalEdge = 0; for (Edge edge : edges) { MyPerson fa1 = find(edge.getNode1()); MyPerson fa2 = find(edge.getNode2()); if (!fa1.equals(fa2)) { rt += edge.getValue(); fa1.setFa(fa2.getId()); totalEdge++; } if (totalEdge == totalNode - 1) { break; } } return rt; }
并查集:
private MyPerson find(int id) { MyPerson temp = ((MyPerson)getPerson(id)); if (id == temp.getFa()) { return temp; } int fa = temp.getFa(); MyPerson person = find(fa); temp.setFa(person.getId()); return person; }
dijkstra:
HashMap<Integer, Integer> ans = new HashMap<>(); PriorityQueue<Node> pq = new PriorityQueue<>(); pq.add(new Node(getMessage(id).getPerson1(), 0)); ans.put(getMessage(id).getPerson1().getId(), 0); Person end = getMessage(id).getPerson2(); while (!pq.isEmpty()) { Node temp = pq.poll(); if (temp.getDis() != ans.get(temp.getPerson().getId())) { continue; } if (temp.getPerson().equals(end)) { break; } ArrayList<Person> person = ((MyPerson)temp.getPerson()).getAcquaintance(); ArrayList<Integer> val = ((MyPerson)temp.getPerson()).getValue(); int size = person.size(); for (int i = 0; i < size; i++) { if (!ans.containsKey(person.get(i).getId()) || temp.getDis() + val.get(i) < ans.get(person.get(i).getId())) { ans.put(person.get(i).getId(), temp.getDis() + val.get(i)); pq.add(new Node(person.get(i), temp.getDis() + val.get(i))); } } }
通过这样的形式可以很大程度上使得network的实现更加清晰,阅读代码和debug也相对更加轻松。
问题以及修复:
在作业中主要是遇到的性能问题,一部分原因是由于做本地测试的时候评测环境比较好,速度较快,从而忽略了一些写法上的优化。
第一次作业因为QBS(Query Block Sum)操作的复杂度过高导致TLE,后面改成了在加入或者删除一个人或者一条边的时候,考虑对答案的改变。
第二次作业和第三次作业主要问题都是JML阅读不仔细,第二次是QVS(Query Value Sum)想当然的认为一条边的贡献只算一次,导致答案只有正确答案的一半。
第三次作业是对于红包消息的发送,是发送到除自己以外的所有人,我发送到了group内部包括自己的所有人,这导致QM的时候会出现错误。
但由于评测机是根据自己错误理解写的,跟别人进行对拍的时候主要也是集中于需要算法实现的几个方法,因此导致最后错误了几个点。
第三次作业由于dijkstra算法写的不是很好,虽然在本地能够很好的完成,但在评测的时候出现了TLE的情况。
最后发现在最开始的时候初始化HashMap,就将HashMap填入了1000+个元素,导致取用的时候复杂度高。
后来改成了在取用的时候先判断是否存在,不存在就应该为INF,然后修改之后再加入HashMap,这样减少了HashMap取值的复杂度,在最终评测的时候能优化1倍以上的时间。
NetWork拓展:
Advertiser、Producer、Customer可以在Person的基础上继承。
Person吃瓜群众不接受任何购买信息,可以直接忽略。
Producer在生产了物品之后,将信息发送给Advertiser,Advertiser再将这些信息发送到需要的Customer。
对于Producer或者通过Advertiser的销售额统计:
/*@public normal_behavior @requires contains(id) && getPerson(id) instanceof Producer; @ensures \result == getPerson(id).getTotalSales(); @also @public exceptional_behavior @signals (PersonIdNotFoundException e) !contains(id); */ public int querySales(int id);
对于Advertiser发送的广告进行统计:
/* @ public normal_behavior @ requires (\exists int i; 0 <= i && i < people.length; people[i].getId() == id && people[i] instanceof Advertiser && (\forall int j; 0 <= j && j < people.length; people[j].getId() == id && people[j] instanceof Customer && (\forall int k; 0 <= k && k < people[j].ads.length; people[j].ads[k].getId() != people[i].getAdId()))) ; @ assignable people[*]; @ ensures (\exists int i; 0 <= i && i < people.length; people[i].getId() == id && people[i] instanceof Advertiser && (\forall int j; 0 <= j && j < people.length; people[j].getId() == id && people[j] instanceof Customer && (\exists int k; 0 <= k && k < people[j].ads.length; people[j].ads[k].getId() == people[i].getAdId()))) ; @ also @ public exceptional_behavior @ signals (PersonIdNotFoundException e) !(\exists int i; 0 <= i && i < people.length; people[i].getId() == id && people[i] instanceof Producer); @ also @ public exceptional_behavior @ signals (AdIdNotFoundException e) (\exists int i; 0 <= i && i < people.length; people[i].getId() == id && people[i] instanceof Advertiser && (\forall int j; 0 <= j && j < people.length; people[j].getId() == id && people[j] instanceof Customer && (\exists int k; 0 <= k && k < people[j].ads.length; people[j].ads[k].getId() == people[i].getAdId()))) ; */ public sendAd(int id) throws PersonIdNotFoundException, AdIdNotFoundException;
Producer生产一个新产品:
/*@ public normal_behavior @ requires contains(id1) && getPerson(id1) instanceof Producer && containsMessage(id2) @&& getMessage(id2) instanceof ProduceMessage && getPerson(id1) == getMessage(id2).getPerson1(); @assignable messages; @assignable getMessage(id2).getPerson2().getProduct(); @ensures !containsMessage(id2) && 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 (\forall int i; 0 <= i && i < \old(getMessage(id2).getPerson2().getProducts().size()); @ \old(getMessage(id2)).getPerson2().getProducts().get(i+1) == \old(getMessage(id2).getPerson2().getProducts().get(i))); @ ensures \old(getMessage(id2)).getPerson2().getProducts().get(0) == \old(getMessage(id2)); @ ensures \old(getMessage(id2)).getPerson2().getProducts().size() == \old(getMessage(id2).getPerson2().getProducts().size()) + 1; @also @public exceptional_behavior @ signals (MessageIdNotFoundException e) !containsMessage(id2); @signals (PersonIdNotFoundException e) !contains(id1); @*/ public void produce(int id1, int id2) throws MessageIdNotFoundException, PersonIdNotFoundException
心得体会:
总体来说这个单元的作业难度不高,根据JML规格就可以写出对应的实现。
出现的最多的问题就是JML太多太杂,在阅读中不仔细就会出现问题,导致强测、互测中失分。
还需要更加适应形式化语言的复杂与严谨