BUAA-OO-Unit3总结
BUAA-OO-Unit3总结
本单元的主要内容是契约式编程,只需要根据官方提供的jml来实现对应的接口来完成接口中定义的函数,而不需要花费精力在架构的设计上。
jml语言是利用前置条件、后置条件、不变式等语法描述Java程序的数据、方法、类的规格。jml只关注方法的执行效果和对其他产生的影响而不需要关注方法本身是如何实现的。
测试数据
本单元的测试数据生成比较简单,我主要采用可两种方法进行数据生成的。
首先采用随机数据,利用随机数据主要测试异常类的计数和输出等问题(因为随机数据真的太随机了,导致生成的数据中异常指令比正常的查询指令还要多)。而且,随机数据的覆盖率要求比较低,对某些边界条件覆盖不到,如group人数不能超过1111。
其次,是针对性的数据,针对每条指令进行单独的检查,即生成大量的同种类型的指令针对边界条件进行检查。同时还能检查部分很耗时的指令的超时情况(利用linux系统的time指令获得程序的执行时间)。
对于Junit,我尝试了一下后就放弃了,虽然Junit可以针对每个函数的逻辑进行测试,但我觉得使用Junit的效率太低了,而且不容易覆盖到边界情况。
图模型构建与维护
图存储
我新建了类MyEdge来存储边的信息,新建了类MyConnection来存储图,MyConnection中用ArrayList来存储图中的边。
最小生成树
使用脏位useful来判断保存的leastConnection是否有效。同时,使用Kruskal算法来计算最小生成树,并且在每次计算后会删除掉最小生成树之外的边,这样下次加边后计算的时候就会减少很多的计算量。
public int calLeastConnection() {
if (useful) {
return leastConnection;
} else if (edges.isEmpty()) {
return 0;
} else {
useful = true;
leastConnection = 0;
Collections.sort(edges);
ArrayList<HashSet<Person>> blocks = new ArrayList<>();
int i = 0;
while (i < edges.size()) {
MyEdge edge = edges.get(i);
HashSet<Person> block1 = null;
HashSet<Person> block2 = null;
for (HashSet<Person> block : blocks) {
if (block.contains(edge.getPerson1())) {
block1 = block;
}
if (block.contains(edge.getPerson2())) {
block2 = block;
}
}
if (block1 == null && block2 == null) {
HashSet<Person> block = new HashSet<>();
block.add(edge.getPerson1());
block.add(edge.getPerson2());
blocks.add(block);
leastConnection += edge.getValue();
} else if (block1 == null || block2 == null) {
if (block1 == null) {
block2.add(edge.getPerson1());
} else {
block1.add(edge.getPerson2());
}
leastConnection += edge.getValue();
} else {
if (!block1.equals(block2)) {
block1.addAll(block2);
blocks.remove(block2);
leastConnection += edge.getValue();
} else {
edges.remove(edge);
i--;
}
}
if (blocks.size() == 1 && blocks.get(0).size() == people.size()) {
break;
}
i++;
}
}
return leastConnection;
}
最短路径
使用的是最普通的Dijkstra算法
public int calLeastPath(Person person1, Person person2) {
HashMap<Integer, Integer> s = new HashMap<>();
HashMap<Integer, Integer> u = new HashMap<>();
s.put(person1.getId(), 0);
MyPerson curPerson = (MyPerson) person1;
int curValue = 0;
while (!s.containsKey(person2.getId())) {
for (Map.Entry<Person, Integer> entry : curPerson.getAcquaintance().entrySet()) {
if (!s.containsKey(entry.getKey().getId())) {
int value = curValue + entry.getValue();
if (u.containsKey(entry.getKey().getId())) {
value = Math.min(value,u.get(entry.getKey().getId()));
}
u.put(entry.getKey().getId(), value);
}
}
curValue = Integer.MAX_VALUE;
for (Map.Entry<Integer, Integer> entry : u.entrySet()) {
if (entry.getValue() < curValue) {
curValue = entry.getValue();
curPerson = (MyPerson) people.get(entry.getKey());
}
}
u.remove(curPerson.getId());
s.put(curPerson.getId(), curValue);
}
return s.get(person2.getId());
}
作业出现的性能问题及修复情况
本单元作业中并没有怎么出现性能方面的问。qbs使用的并查集,O(n)的复杂度。qlc使用的Kruskal算法,并且会在每次计算完最小生成树后把没用的边删除,只保留最小生成树的边,这样在接下来的加边和查询时可以保证有更好的性能。sim使用的是最普通的Dijkstra算法,性能还是可以的,并不会超时。
第九次作业
说起来你可能不信,我使用了并查集结果还是超时了。因为不知道我在写的时候怎么想的没有把遍历的过程删掉,而只是修改了返回值,使用了并查集但好像又没有使用。
第十次作业
意外的获得了几个WA,原因是我在group里面的求平均值和方差的时候将计算结果保存了下来然后设置脏位来记录结果的有效性,然后在删人的时候没有直接调用group中的删除方法,导致结果没有更新,更神奇的是我室友提醒我删人的时候记得更新脏位,我特意查看了一下,确实更新了。这波是更新了但又没完全更新。
第十一次作业
这次并没有出现意外。
Network扩展
假设出现了几种不同的Person
- Advertiser:持续向外发送产品广告
- Producer:产品生产商,通过Advertiser来销售产品
- Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
- Person:吃瓜群众,不发广告,不买东西,不卖东西
如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等 请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格。
1、新建Advertiser、Producer、Customer类,继承Person类;新建AdvertiseMessage、BuyMessage类,继承Message类。
2、增加sendAdvertiseMessage、buy、querySales等方法。
sendAdvertiseMessage()
/*@ public normal_behavior
@ requires containsMessage(messageId) && (getMessage(messageId) instanceof Advertisement);
@ assignable messages;
@ assignable people[*].messages;
@ ensures !containsMessage(messageIdmessageId) && messages.length == \old(messages.length) - 1 &&
@ (\forall int i; 0 <= i && i < \old(messages.length) && \old(messages[i].getId()) != messageId;
@ (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i]))));
@ ensures (\forall int i; 0 <= i && i < people.length; person[i].likeType(\old(((Advertisement)getMessage(messageId))).getType) ==>
@ ((\forall int j; 0 <= j && j < \old(person[i].getMessages().size());
@ person[i].getMessages().get(j+1) == \old(person[i].getMessages().get(j))) &&
@ (person[i].getMessages().get(0).equals(\old(getMessage(messageId)))) &&
@ (person[i].getMessages().size() == \old(person[i].getMessages().size()) + 1)));
@ also
@ public exceptional_behavior
@ signals (MessageIdNotFoundException e) !containsMessage(messageId);
@ signals (MessageNotAdverException e) containsMessage(messageId) && !(getMessage(messageId) instanceof Advertisement);
@*/
public void sendAdvertiseMessage(int messageId) throws MessageIdNotFoundException, MessageNotAdverException;
buy()
/*@ public normal_behavior
@ requires containsMessage(messageId) && (getMessage(messageId) instanceof BuyMessage);
@ requires (getMessage(messageId).getPerson1() instanceof Customer) && (getMessage(messageId).getPerson2() instanceof Advertiser);
@ assignable messages, getMessage(messageId).getPerson1().money;
@ assignable getMessage(messageId).getPerson2().messages, getMessage(messageId).getPerson2().money;
@ ensures !containsMessage(messageId) && 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(messageId)).getPerson2().getMessages().size() == \old(getMessage(messageId).getPerson2().getMessages().size()) + 1;
@ ensures (\old(getMessage(messageId)).getPerson1().getMoney() ==
@ \old(getMessage(messageId).getPerson1().getMoney()) - ((BuyMessage)\old(getMessage(messageId))).getMoney() &&
@ \old(getMessage(messageId)).getPerson2().getMoney() ==
@ \old(getMessage(messageId).getPerson2().getMoney()) + ((BuyMessage)\old(getMessage(messageId))).getMoney());
@ also
@ public exceptional_behavior
@ signals (MessageIdNotFoundException e) !containsMessage(messageId);
@ signals (MessageNotBuyMessageException e) containsMessage(messageId) && !(getMessage(messageId) instanceof BuyMessage);
@*/
public void bug(int messageId) throws MessageIdNotFoundException, MessageNotBuyMessageException;
querySales()
/*@ public normal_behavior
@ requires containsProductId(productId);
@ ensures \result == productList(id).getSalesAmount();
@ also
@ public exceptional_behavior
@ signals (ProductNotFoundException e) !containsProduct(productId);
@*/
public int querySaleAmount(int productId) throws ProductNotFoundException;
学习体会
第三单元的总体难度并不大,毕竟整体的架构已经设计完成了,不过还是有一些坑点的,就比如group的1111限制,就有很多人踩坑了(有些人不看jml),我旁边的很多人就中招了(不过是在大家分享数据自测的时候)。
虽然根据jml完成函数时不需要考虑很多,只需要根据规格限制完成函数就好了,但是轮到自己去写jml时才发现这玩意真的不好写啊,需要仔细分析方法的前置条件、后置条件、不变式、还有针对每一个变式的限制,需要用数学语言进行描述。当面对复杂或变式较多的方法时,jml就显得很臃肿了,如求最小生成树的那个方法,那复杂的一坨我看了半个小时才明白是让我求最小生成树。