BUAA_OO_Unit3 总结
BUAA_OO_Unit3 总结
目录
- 分析在本单元自测过程中如何利用JML规格来准备测试数据
- 梳理本单元的架构设计,分析自己的图模型构建和维护策略
- 按照作业分析代码实现出现的性能问题和修复情况
- 对Network进行扩展,并给出相应的JML规格
- 本单元学习体会
一、测试思路
- 在测试方面,主要依靠测评机进行对拍。
- 在构造数据方面:根据不同的前置条件情况构造数据,覆盖所有可能出现的数据类型。
二、架构设计
本次作业架构主要按照JML规格来进行设计,涉及到的图论算法有并查集、最小生成树、最短路径
图模型构建
- 并查集
在第一次作业中需要查询两个结点是否连通,使用dfs算法复杂度高达O(n^2),时间效率不高。为了降低复杂度,我们可以使用并查集来处理判断结点是否连通的问题。但传统的并查集在判断两个结点是否连通的过程中,需要递归查询结点的根节点,通过判断根节点是否相同来判断这两个是否连通,如果集合中的结点形成一条链,会降低代码的运行效率。我们可以通过路径压缩——同一集合中的结点指向同一个根节点,可以进一步降低查询的复杂度。
维护策略:
public static void kUnion(int id1, int id2,HashMap<Integer,Integer> trees, HashMap<Integer,ArrayList<Integer>> gathers) { int parentId1 = trees.get(id1); int parentId2 = trees.get(id2); if (id1 != id2 && parentId1 != parentId2) { ArrayList<Integer> gather1 = gathers.get(parentId1); ArrayList<Integer> gather2 = gathers.get(parentId2); if (gather1.size() < gather2.size()) { trees.put(id1, parentId2); gather2.add(id1); for (int i : gather1) { trees.put(i, parentId2); if (!gather2.contains(i)) { gather2.add(i); } } gathers.put(parentId2, gather2); gathers.remove(parentId1); } else { trees.put(id2, parentId1); gather1.add(id2); for (int i : gather2) { trees.put(i, parentId1); if (!gather1.contains(i)) { gather1.add(i); } } gathers.put(parentId1, gather1); gathers.remove(parentId2); } } }
其中,trees储存了所有的结点及其对应的根节点信息;groups储存了所有的根节点及该根节点对应的子节点的集合。
- 最小生成树——kruskal算法
为了实现hw10中的qlr(求person所在连通图的最小生成树)指令,使用了kruskal算法。
新建了MyEdge类,包含属性startId(startId == person1.getId())、endId(endId == person2.getId())、weight (weight == person1.queryValue(person2))。
在求person所在连通图的最小生成树时,先获取树中的所有结点。查询结点间的连通关系,并或得所有的边, 再通过kruskal算法获得最小生成树。
核心代码如下:
for (int i = 0; i < size; i++) { int id1 = nodes.get(i); Person person1 = getPerson(id1); for (Person person : ((MyPerson) person1).getAcquaintance()) { if (person1.isLinked(person)) { MyEdge edge = new MyEdge(id1, person.getId(), person1.queryValue(person)); edges.add(edge); } } }
- 最短路径——dijkstra算法
为了实现hw11中的sim(求连通图中两点间的最短路径)指令,使用了Dijkstra算法;
三、问题分析
- 性能问题
- hw9:isCircle直接使用了dfs实现;
- hw10:qgvs使用for循环嵌套;
- hw11:最小生成树算法中使用常规Dijkstra算法,导致超时。
- 修复情况
-
hw9:使用并查集,queryBlockSum通过查询有几个不连通的集合实现;
- hw10:MyGroup中增加valueSum属性,通过查询valueSum实现queryGroupValueSum,将复杂度降为了O(1);
- hw11:采用Dijkstra堆优化算法;
-
四、对Network进行扩展,并给出相应的JML规格
题目:
假设出现了几种不同的Person
- Advertiser:持续向外发送产品广告
- Producer:产品生产商,通过Advertiser来销售产品
- Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
- Person:吃瓜群众,不发广告,不买东西,不卖东西
如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等 请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格(借鉴所总结的JML规格模式)
- 对Network扩展
- 新建Advertiser、Producer、Customer类,都继承Person类
- Customer类增加属性preference表示自己偏好的产品, product[]表示购买的产品
- Producer类增加属性type表示生产产品的类型
- 新建 AdvertiseMessage 、CustomMessage类 ,都继承Message类
- AdvertiseMessage表示Advertiser发送的产品广告,包含属性:product, advertiser(发送广告的人)
- CustomMessage表示Customer直接通过Advertiser给相应Producer发送的购买消息,包含属性:customer(发送购买消息的人),advertiser(直接接收消息的advertiser)
- 新建Product类,包含属性:type(产品类型), value...
- 选择3个核心业务功能的接口方法撰写JML规格
- 发送产品广告
/*@ public normal_behavior @ requires containsMessage(id) && (getMessage(id) instanceof Advertisement); @ assignable messages; @ assignable people[*].messages;
@ ensures !containsMeaage(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 (\forall int i; 0 <= i && i < people.length && !getMessage(id).getPerson1().isLinked(people[i]); @ people[i].getMessages().equals(\old(people[i].getMessages())); @ ensures (\forall int i; 0 <= i && i < people.length && getMessage(id).getAdvertiser().isLinked(people[i]); @ (\forall int j; 0 <= j && j < \old(people[i].getMessages().size()); @ people[i].getMessages().get(j+1) == \old(people[i].getMessages().get(j))) @ ensures people[i].getMessages().get(0).equals(\old(getMessage(id))) @ ensures people[i].getMessages().size() == \old(people[i].getMessages().size()) + 1); @ also @ public exceptional_behavior @ signals (MessageIdNotFoundException e) !containsMessage(id); @ signals (NotAdvertisementException e) !(getMessage(id) instanceof Advertisement); @*/ public void sendAdvertisement(int id) throws MessageIdNotFoundException, NotAdvertisementException; - 生产产品
/*@ public normal_behavior @ requires contains(producerId) && (getPerson(producerId) instanceof Producer) @ assignable products[]; @ ensures (\exists int i; i<product.length && i>=0; products[i] == product); @ ensures getProductCount(productId) == @ \old(getProductCount(productId)) + 1; @ also @ public exceptional_behavior @ signals (PersonIdNotFoundException e) !contains(producerId); @ signals (NotProducerException e) !(getPerson(producerId) instanceof Producer); @ @*/ public void produceProduct(int producerId, Produce product) throws PersonIdNotFoundException, NotProducerException;
- 发送产品广告
-
- 购买产品
/*public normal_behavior @ requires product.contains(product) && contains(coustomerId) && getPerson(customerId) instanceof Customer @ assignable products[], getPerson(customerId).money, getPerson(customerId).products; @ ensures products.length == \old(products.length) - 1; @ ensures (\forall int i; i>=0 && i<=\old(products.length)) && product[i]!=product; (\exist int j; j>=0 && j<=products.length; product[j].equals(product[i])) @ ensures getPerson(customerId).getmoney == \old(getPerson(customerId)) - product.getValue(); @ ensures getPerson(customerId).getProduct().length == \old(getPerson(customerId).getProduct().length)+1; @ ensures (\forall int i; i>=0&&i<=\old(getPerson(customerId).getProduct().length)&&\old(getPerson(customerId).getProduct().get(i)); (\exist int j; j>=0 && j<=getPerson(customerId).getProduct().length; getPerson(customerId).getProduct().get(j).equals(\old(getPerson(customerId).getProduct().get(i))) @ ensures (\exist int j; j>=0 && j<=getPerson(customerId).getProduct().length; getPerson(customerId).getProduct().get(i).equals(product); @public exceptional_behavior @ signals (NotProductException e) !product.contains(product); @ signals (NotCustomerException e) !contains(coustomerId) || !(getPerson(producerId) instanceof Producer); */ public void buyProduct(int coustomerId, Product product) throws NotProductException, NotCustomerException
- 购买产品
五、学习体会
本单元的难度小了很多,但更需要严谨仔细地阅读JML规格,充分理解代码的前置条件和后置条件。
遇到复杂的前置条件和后置条件时,需要将其提炼化简,理解它想要我们干什么。在提炼化简的过程中需要注意正确性。
为了提高代码的效率,我们编程时没有必要完全按照JML规格的描述进行,可以进行一定的优化,但这需要在充分理解JML规格。
此外,在学习过程中,我们需要细心,在本单元的练习中,我们需要阅读大量的UML代码,在阅读过程中一定要沉下心,仔细阅读,不可想当然,写完代码后最好对照JML规格检查一遍。