BUAA OO 第三单元总结
BUAA OO 第三单元总结
JML及数据构造:
JML的理解
在本单元的学习中,我们认识了规划化的JML语言,它以类形式化的语言描述了接口的规格,能够有效消除自然语言的二义性。利用JML语言对方法的前置、后置条件进行约束,规定好作用范围等,来有效规定所要实现的规格。
数据构造
本单元数据构造较为容易,在数据设计上我侧重于对于复杂度可能出现问题的方法进行测试,防止卡T。主要测试方法还是搭测评机和同学对拍,着重测试了qgvs、qlc,qci和sim等复杂度较高的指令。测试思路为先构造一个有一定规模的网络,接着进行查询,经过增删person后再次查询,如此反复即可。
架构设计
最终Uml类图:
架构实现:
本次作业最可能出现的问题是实现算法的复杂度的问题。
具体来说有以下几条指令:
qgvs
和qgav
:
对于这条指令,朴素做法是\(O(n^2)\),所以考虑将中间结果缓存处理,在每次从group中加入或删除person时对sum做更新操作,具体做法如下;
@Override
public void addPerson(Person person) {
for (Person p : people) {
if (p.isLinked(person)) {
sum += p.queryValue(person);
}
}
people.add(person);
peopleSet.add(person);
}
@Override
public int getValueSum() {
return sum;
}
@Override
public void delPerson(Person person) {
for (Person p : people) {
if (p.isLinked(person)) {
sum -= p.queryValue(person);
}
}
people.remove(person);
peopleSet.remove(person);
}
qci
查询两点的连通性,第一次作业中采用并查集的做法,后由于要计算最小生成树,就用HashMap维护了每个联通分支的情况,直接查询点所在的联通分支即可,在添加连边时,可能需要对两个联通分支进行合并操作:
private HashSet<Integer> blocks;
private HashMap<Integer,Integer> blockMap;
private HashMap<Integer,ArrayList<Integer>> blocksList;
@Override
public boolean isCircle(int id1, int id2) throws PersonIdNotFoundException {
if (contains(id1) && contains(id2)) {
return Objects.equals(blockMap.get(id1), blockMap.get(id2));
} else if (!contains(id1)) {
throw new MyPersonIdNotFoundException(id1);
} else {
throw new MyPersonIdNotFoundException(id2);
}
}
public void mergeBlock(int id1, int id2) {
int blockId1 = blockMap.get(id1);
int blockId2 = blockMap.get(id2);
ArrayList<Integer> list1 = blocksList.get(blockId1);
ArrayList<Integer> list2 = blocksList.get(blockId2);
for (Integer item : list2) {
list1.add(item);
blockMap.put(item, blockId1);
//System.out.println("item = " + item);
}
blocks.remove(blockId2);
blocksList.remove(blockId2);
blocksList.put(blockId1, list1);
}
qlc
查询最小生成树,由于维护了联通分支中的点,就采用了堆优化的kruskal算法,复杂度为\(O(nlog(n))\),实现如下:
public int queryLeastConnection(int id)
throws PersonIdNotFoundException {
int sum = 0;
if (contains(id)) {
MyPerson personHead = (MyPerson) getPerson(id);
HashSet<Integer> visited = new HashSet<>();
visited.add(id);
int blockId = blockMap.get(id);
ArrayList<Integer> blockList = blocksList.get(blockId);
PriorityQueue<Edge> edges = new PriorityQueue<>();
for (Person person : personHead.getAcquaintance()) {
edges.add(new Edge(id, person.getId(), personHead.queryValue(person)));
}
int len = blockList.size();
while (visited.size() < len && !edges.isEmpty()) {
Edge edge = edges.poll();
if (visited.contains(edge.getDst())) {
continue;
}
visited.add(edge.getDst());
Person person = getPerson(edge.getDst());
sum += edge.getValue();
for (Person person1 : ((MyPerson) person).getAcquaintance()) {
edges.add(new Edge(id, person1.getId(), person.queryValue(person1)));
}
}
return sum;
} else {
throw new MyPersonIdNotFoundException(id);
}
}
sim
考察最短路,采用了堆优化dijkstra,复杂度为\(O(nlog(n))\),实现如下:
public static int dijkstra(Network network, Person person1, Person person2,
HashMap<Integer, Integer> blockMap,
HashMap<Integer,ArrayList<Integer>> blocksList,
ArrayList<Person> people) {
MyPerson personHead = (MyPerson) person1;
int blockId = blockMap.get(personHead.getId());
ArrayList<Integer> blockList = blocksList.get(blockId);
PriorityQueue<Node> nodes = new PriorityQueue<>();
nodes.add(new Node(personHead.getId(), 0));
HashMap<Integer, Integer> visitMap = new HashMap<>();
HashSet<Integer> visited = new HashSet<>();
visited.add(personHead.getId());
for (int pid : blockList) {
visitMap.put(pid, Integer.MAX_VALUE);
}
visitMap.put(personHead.getId(), 0);
while (!nodes.isEmpty() && visited.size() != blockList.size()) {
Node node = nodes.poll();
int pid = node.getPid();
MyPerson personNew = (MyPerson) network.getPerson(pid);
visited.add(pid);
for (Person person : personNew.getAcquaintance()) {
if (visitMap.get(person.getId()) >
visitMap.get(pid) + network.getPerson(pid).queryValue(person)) {
visitMap.put(person.getId(),
visitMap.get(pid) + network.getPerson(pid).queryValue(person));
nodes.add(new Node(person.getId(), visitMap.get(person.getId())));
}
}
}
return visitMap.get(person2.getId());
}
性能和修复问题
第一次作业中只是简单根据JML实现了功能,并没有对性能做过多的考虑,于是被卡T了,集中于qgvs
上。后两次作业中修改了qgvs
的相关实现后没有出现性能问题。
Network拓展
- Advertiser:持续向外发送产品广告
- Producer:产品生产商,通过Advertiser来销售产品
- Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买
- Person:吃瓜群众,不发广告,不买东西,不卖东西
对于Person可以沿用之前的实现。Advertiser作为一个中转站,Customer向Advertiser发送消息,Advertiser再通知Producer即可。
实现如下:
/*@ public normal_behavior
@ requires !(\exists int i; 0 <= i && i < messages.length; messages[i].equals(message)) && (message.getPerson1() instanceof Advertiser);
@ assignable messages;
@ 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 messages.length == \old(messages.length) + 1;
@ also
@ public exceptional_behavior
@ signals (AdvertiseException e)
@ !(\exists int i; 0 <= i && i < messages.length; messages[i].equals(message))
@ || !(messages[i].getPerson1() instanceof Advertiser);
@*/
void postAdvertise(Message message) throws AdvertiseException;
/* @ public normal_behavior
@ requires !(\exists int i; 0 <= i && i < productList.length; productList[i].equals(product))
@ assignable productList
@ ensures (\forall int i; 0 <= i && i < \old(productList.length);
@ (\exists int j; 0 <= j && j < productList.length; productList[j].equals(\old(productList[i]))));
@ ensures (\exists int i; 0 <= i && i < productList.length; productList[i].equals(product));
@ ensures productList.length == \old(productList.length) + 1;
@ also
@ public exceptional_behavior
@ signals (ProductException e) (\exists int i; 0 <= i && i < productList.length; productList[i].equals(product))
@*/
void addProduct(Product product, Person advertiser) throws ProductException;
int querySalePath(Person producer);
/*@ public normal_behavior
@ assignable messages
@ requires contains(customerId) && contains(advertiserId) && contains(product.getProducer);
@ requires getPerson(advertiserId).containsProduct(product);
@ 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) && message.getPerson1.equals(getPerson(advertiserId)) && message.getPerson2.equals(product.getProducer);
@ ensures messages.length == \old(messages.length) + 1;
@ ensures !getPerson(advertiserId).containsProduct(product);
@ also
@ public exceptional_behavior
@ signals (PeronIdNotFoundException) !contains(customerId);
@ also
@ public exceptional_behavior
@ signals (PeronIdNotFoundException) !contains(advertiserId);
@ also
@ public exceptional_behavior
@ signals (PeronIdNotFoundException) !contains(product.getProducer);
@ also
@ public exceptional_behavior
@ signals (ProductIdNotFoundException) !getPerson(advertiserId).containsProduct(product);
@*/
void buyProduct(int customerId, int advertiserId, Product product);
心得体会
通过这个单元的学习,了解了规格和JML语言。使用JML语言能有效避免由于自然语言的二义性导致的程序逻辑问题。但是个人感觉JML语言由于追求逻辑上的完备导致十分冗长,实际并不能够很有效的被阅读和理解,例如本单元中最小生成树的JML描述,一时间是很难反应过来这描述的是什么。
在本单元中,代码任务中更多体现的是对于输入的各种情况的约束,需要很细心地完成代码实现。在本单元的任务中,也体会到了在不同的任务逻辑中设计合适的算法和数据结构的重要性。