北航2022面向对象第三单元:JML规格
作者:互联网
北航2022面向对象第三单元:JML规格
内容概括
- 测试方法
- 架构设计
- 性能优化
- Network拓展
- 总结和体会
1. 测试方法
因为本单元的JML语法是清楚且严格的,对于一些特别简单的方法就没必要做测试了。但是对于核心和复杂的方法,可以翻译成自然语言进行测试,同时对also
区分开的分支全面覆盖。特别注意要一句句翻译,不然容易漏看条件。
比如将Person添加到Group中这个方法。
public void addToGroup(int id1, int id2) throws GroupIdNotFoundException,
PersonIdNotFoundException, EqualPersonIdException;
异常情况就有三种,要注意判断抛出的异常是什么,传入的id是什么。正常情况,要注意一组的人数有上限,如果不认真看容易看漏。
对于复杂的JML,可以翻译成自然语言进行理解。
比如
/*
求一个值最小的subgroup,一共有偶数个人,而且
(\min Person[] subgroup; subgroup.length % 2 == 0 &&
0-1,2-3,4-5...这些人有联系,而且
(\forall int i; 0 <= i && i < subgroup.length / 2; subgroup[i * 2].isLinked(subgroup[i * 2 + 1])) &&
所有和id这个人在一个圈内的人,
(\forall int i; 0 <= i && i < people.length; isCircle(id, people[i].getId()) <==>
也在这个subgroup里面,而且
(\exists int j; 0 <= j && j < subgroup.length; subgroup[j].equals(people[i]))) &&
所有和id这个人在一个圈内的人,
(\forall int i; 0 <= i && i < people.length; isCircle(id, people[i].getId()) <==>
存在一个connection数组
(\exists Person[] connection;
connection第一个人是id对应的人,最后一个是圈内的这个人。而且对于connection中的c[j],c[j+1],在subgroup中有s[k],s[k+1]分别对应。
(\forall int j; 0 <= j && j < connection.length - 1;
(\exists int k; 0 <= k && k < subgroup.length / 2; subgroup[k * 2].equals(connection[j]) && subgroup[k * 2 + 1].equals(connection[j + 1]))); connection[0].equals(getPerson(id)) && connection[connection.length - 1].equals(people[i])));
对subgroup求值,把0-1,2-3,4-5...的连接的权值加起来。让这样的值最小。
(\sum int i; 0 <= i && i < subgroup.length / 2; subgroup[i * 2].queryValue(subgroup[i * 2 + 1])));
*/
public /*@ pure @*/ int queryLeastConnection(int id) throws PersonIdNotFoundException;
通过自然语言稍加分析,可以知道是求id这个人所在圈子的最小生成树。通过构造不同的连接,计算最小生成树的权值之和,就可以测试这个方法了。
2. 架构设计
主要的方法涉及到并查集、最小生成树、单源最短路径
- 并查集
主要是维护一个parent
映射,通过Person能快速找到父节点。 - 最小生成树
采用Prim算法,简单易行。但是并查集的懒修改不太方便找到所有孩子节点。因此又添加了children
映射,对于每一个父节点,查找所有孩子节点的集合。在修改图结构的时候,都移动父亲节点、合并孩子集合。这相当于原来并查集查询时才做的合并,在加边的时候就合并了。 - 单源最短路径
采用Dijkstra算法。由于能快速找到父节点的孩子集合,并且所有的边都是已知的,相当于查询时已经有了一个邻接表结构的图。直接应用算法即可
3.性能优化
本单元作业逻辑没有大的问题,出的问题主要是性能优化问题,导致超时。
-
并查集
并查集中,采用懒修改。只有查询的时候才合并父节点,修改的时候,直接接在并查集末尾即可。对于有限条命令来说,这样的效率是最高的,不会有任何多余操作。如果每次修改都压缩路径,修改很多次,但是不查询,就会浪费时间。 -
边权值修改即更新
之前偷懒,直接根据JML方法查询点集里的边权值和,需要两次迭代,速度很慢。之后改成了每次修改图就修改边权和,查询的时候不再计算直接获得结果。代码变复杂了,但是效率提升明显。需要注意什么操作会影响到图结构,有时其他类的方法也会影响该类的方法结果,需要考虑的情况更多,需要更全面。 -
Dijkstra算法
原来没有仔细思考,没做堆优化,性能很差。大的完全图边的数量远大于点的数量。因此在给定有限的指令条数以后,构造较大的图都是稀疏的,堆优化有明显效果。// 堆优化后的算法 private int getMinPath(Person p1, Person p2) { if (p1.equals(p2)) { return 0; } Set<Person> set = children.get(parents.get(p1)); Set<Person> tempSet = new HashSet<>(set); Map<Person, Long> dist = new HashMap<>(); PriorityQueue<Map.Entry<Person, Long>> queue = new PriorityQueue<>((o1, o2) -> o1.getValue().compareTo(o2.getValue())); dist.put(p1, 0L); tempSet.remove(p1); Person now = p1; while (!now.equals(p2)) { for (Person p: ((MyPerson) now).getAcqs()) { if (tempSet.contains(p)) { long newDist = dist.get(now) + now.queryValue(p); Map.Entry<Person, Long> entry = new AbstractMap.SimpleEntry<>(p, newDist); // 插入:Log(N)时间复杂度 queue.add(entry); } } // 取出距离最小的点:Log(N)时间复杂度 Map.Entry<Person, Long> minEntry = queue.poll(); assert minEntry != null; dist.put(minEntry.getKey(), minEntry.getValue()); now = minEntry.getKey(); tempSet.remove(now); } long ret = dist.get(p2); return (int) ret; }
-
选择合适容器
注意到接收信息的时候,每次都添加在数组开头,查询也是从前往后查。因此使用LinkedList
双向链表,相比于ArrayList
能更快在头部添加。
4. Network拓展
-
区分不同的
person
,可以采用不同的接口实现类,通过instance of
区分。 -
添加一个
Message
的实现类AdMessage
表示广告,在广告中加一个adType
属性,表示广告类型。
Advertisement可以是发给个人,也可以发给群组。 -
每种
Person
可以添加一个AdMessage[] allAds
数组,表示收到的广告,但是根据种类处理不同。 -
每个人有特定感兴趣的产品,可以添加一个
Advertisement.adType[] favorAds
数组,表示感兴趣的广告类型。 -
给
Network
增加AdType.class[] goodsTypes
数组,以及int[] goodsTypesCount
数组,表示广告有多少种类,每种销售了多少个产品。 -
添加一个
Message
的实现类PurchaseMessage
,表示购买产品,让Advertiser
给Producer
发送信息。 -
PurchaseMessage
中含有AdType
属性,表示购买产品的类型。
发送广告
可以并入发送信息里面。给特定的人发广告或者群发广告。
/*@
@ requires containsMessage(id) && getMessage(id).getType() == 0 &&
@ getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2()) &&
@ getMessage(id).getPerson1() != getMessage(id).getPerson2();
@ .......
@ ensures (\old(getMessage(id)) instanceof AdMessage) ==>
@ (\old(getMessage(id)).getPerson2().allAds.length =
@ \old(getMessage(id).getPerson2().allAds.length) + 1) &&
@ (\forall int i; i >= 0 && i < \old(getMessage(id).getPerson2().allAds.length);
@ (\exists int j; j >= 0 && j < getMessage(id).getPerson2().allAds.length;
@ getMessage(id).getPerson2().allAds[j] == \old(getMessage(id).getPerson2().allAds[i])) &&
@ (\exists int i; i >= 0 && i < getMessage(id).getPerson2().allAds.length;
@ getMessage(id).getPerson2().allAds[i].equals(\old(getMessage(id))));
@ .......
@ .......
@ requires containsMessage(id) && getMessage(id).getType() == 1 &&
@ getMessage(id).getGroup().hasPerson(getMessage(id).getPerson1());
@ .......
@ ensures (\old(getMessage(id)) instanceof AdMessage) ==>
@ (\forall Person p; \old(getMessage(id)).getGroup().hasPerson(p) &&
@ p != \old(getMessage(id)).getPerson1();
@ (p.allAds.length == \old(p.allAds.length) + 1) &&
@ (\exists int i; i >= 0 && i < p.allAds.length;
@ p.allAds[i].equals(\old(getMessage(id)))) &&
@ (\forall int i; i >= 0 && i < \old(p.allAds.length);
@ (\exists int j; j >= 0 && j < p.allAds.length;
@ p.allAds[j].equals(\old(p.allAds[i])))));
@ ......
@*/
public void sendMessage(int id) throws
RelationNotFoundException, MessageIdNotFoundException, PersonIdNotFoundException;
购买产品
发送一条购买商品的信息
/*@
@ requires containsMessage(id) &&
@ getMessage(id) instanceof PurchaseMessage &&
@ getMessage(id).getType() == 0 &&
@ getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2()) &&
@ getMessage(id).getPerson1() != getMessage(id).getPerson2() &&
@ getMessage(id).getPersion1() instanceof Advertiser &&
@ getMessage(id).getPersion2() instanceof Producer;
@ assignable messages, goodsTypes, goodsTypesCount;
@ assignable getMessage(id).getPerson1().socialValue;
@ assignable getMessage(id).getPerson2().messages, getMessage(id).getPerson2().socialValue;
@ 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 (\forall int i; i >= 0 && i < \old(goodsTypes.length);
@ (\exists int j; j >= 0 && j < goodsTypes.length; goodsTypes[j]==\old(goodsTypes[i]) &&
@ (!(goodsTypes[j] instanceof AdType) ==> goodsTypesCount[j] == \old(goodsTypesCount[j]));
@ ensures goodsTypes.length == goodsTypesCount.length;
@ ensures !(\exists int i; i >= 0 && i < \old(goodsTypes.length); \old(goodsTypes[i]) != AdType) ==>
(goodsTypes.length == \old(goodsTypes.length) + 1) &&
@ (\exists int i; i >= 0 && i < goodsTypes.length;
@ goodsTypes[i] == AdType && goodsTypesCount[i] == 1);
@ ensures (\exists int i; i >= 0 && i < goodsTypes.length;
@ (goodsTypes[i] instanceof AdType) ==> goodsTypesCount[i] == \old(goodsTypesCount[i]) + 1);
@ 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) &&
@ getMessage(id) instanceof PurchaseMessage &&
@ getMessage(id).getType() == 0 &&
@ getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2()) &&
@ getMessage(id).getPerson1() != getMessage(id).getPerson2() &&
@ getMessage(id).getPersion1() instanceof Advertiser &&
@ getMessage(id).getPersion2() instanceof Producer);
@*/
public void buyGoods(int msgId) throws MessageIdNotFoundException;
查询产品销量
输入产品种类,查看该产品销售了多少个
/*@
@ public normal_behavior
@ requires !(exists int i; i >= 0 && i < goodsTypes.length; goodsTypes[i] == adType);
@ ensure \result == 0;
@ also
@ public normal_behavior
@ requires (exists int i; i >= 0 && i < goodsTypes.length; goodsTypes[i] == adType);
@ ensure (exists int i; i >= 0 && i < goodsTypes.length; goodsTypes[i] == adType &&
@ \result == goodsTypesCount[i]);
@*/
public /*@ pure @*/ int getSales(AdType adType);
5.总结和体会
本单元主要学习了JML的简单用法。我认为JML和接口的内在思想是相通的,都是规定了任务分工。JML是更好地进行分工而约定的一套接口。每个类、每个方法都只需要考虑有限的几种情况,而不用纠结于比如一个String是否为null,是否为"",是否为" \t "等细小而繁杂的问题,再处理业务逻辑。这样的规定极大减少了重复判断的开销,而且方便了内部的灵活实现,使得检查的方法致力于检查,具体实现方法致力于业务逻辑。至于具体实现则不作限制,只要能满足给定的功能就行。这就很类似于面向接口编程。只规定一套输入输出,具体实现不作要求,通过规范和合理分工,使得不同人、不同时间开发的程序也能很快地对接起来。
写JML程序最大的感受就是省心。只要考虑给定范围内的处理,实现方法也不作要求,凡是不满足require
的输入一概不管。深刻体会了合理分工可以带来高效。
标签:JML,int,北航,length,getMessage,2022,&&,goodsTypes,id 来源: https://www.cnblogs.com/mtr329/p/16343208.html