OO第三单元总结
OO第三单元总结
一、架构设计
1. 第一次作业
1.1 图模型构建和维护策略:
- 构建:
- Network是图:每个Person是图上的一个结点,保存在在people列表中;每个Relation是图上的一条边,边的权值和此边两顶点的Person分别保存在此边两顶点Person的value列表中。
- Group是群:类似微信群,本质上是一群人的一个容器。将群里每个人加入group的people列表中即可(Group和Network中的图没有直接联系,并不是说一个group是图中的一个连通分支)。
- 维护:
- NetWork:
- 动态增加结点、边:
- addPerson时将人加入people哈希图中。
- addRelation时,假设边为连接的Person为A、B,则把边的权值、B加入A的value列表中,把边的权值、A加入B的value列表中。
- 判断两人之间有无通路、查询连通分支个数:
- 采用【并查集算法】,对整个NetWork中所有的结点维护一个【查询某节点的“祖宗节点”的HashMap】,即HashMap的key值为某节点id、value值为该节点的祖宗节点的id。每次addRelation时,都将此边两顶点的Person进行union。
- 每次查询两结点间是否有通路(isCircle方法)时,都更新两人的祖宗节点,若祖宗节点一样则两人处于同一连通分支、两人之间有通路;若祖宗节点不一样则两人不处于同一连通分支、两人之间没有通路。
- 每次查询连通分支个数(queryBlockSum)时,都更新所有结点的祖宗节点,只需数有几个祖宗节点就可以。
- 动态增加结点、边:
- Group:
- 动态增加人:addPerson时将人加入group的people列表中。
- NetWork:
1.2 UML图:
2. 第二次作业
2.1 图模型构建和维护策略:
- 构建(比上次作业增加的):
- Network:比上次作业多了massage列表。
- 维护(比上次作业增加的):
- Network:
- 动态增加message:
- addMessage时将message加入。相应地增加了单人对单人发消息和单人群发消息的方法。
- 维护一个记录所有边的EdgeManagement容器:
- 新增了一个Edge类记录一条边的两顶点、权值。
- addRelation时还要new出这条边并加入EdgeManagement容器。
- 此容器用于记录所有边,方便kruskal算法以O(n)的时间复杂度遍历所有边。
- 求最小生成树:
- 采用【并查集+Kruskal算法】:先找到出发结点所在的连通分支的所有边;再将连通分支所有边按权值排序;再用kruskal算法遍历已排序的连通分支所有边来求最小生成树,通过维护一个新的并查集记录最小生成树已有结点和边、每遍历到一条边用并查集判断此边的两端点是否已经连通(即第一次作业中isCircle方法),若已经连通则加上此边会形成环,应舍弃;若未联通则加上此边不会形成环,应当加入。
- 动态增加message:
- Group:
- 动态维护ageSum、agePowerSum保证查询的时间复杂度为O(1):
- 如果不维护ageSum、agePowerSum,查询年龄平均数、年龄方差平均数都需要双循环遍历计算一遍,时间复杂度为O(n^2),在当前指令条数和CPU时间限制下会超时。因此必须维护ageSum、agePowerSum,这样查询年龄平均数、年龄方差平均数只需要进行一个时间复杂度为O(1)的计算就可以了。
- 动态维护ageSum、agePowerSum保证查询的时间复杂度为O(1):
- Network:
2.2 UML图:
3. 第三次作业
3.1 图模型构建和维护策略:
- 构建(比上次作业增加的):
- Network:比上次作业多了RedEnvelopMessage、NoticeMessag、 EmojiMessage三种Message的子类消息类型,每种message的send方法和add方法都要进行额外的处理。对于EmojiMessage需要创建一个HasMap记录emoji出现的次数(即题意中热度)。
- 维护(比上次作业增加的):
- Network:
- 求最短路径:
- 采用【堆优化的Dijkstra算法】:给每个人增加一个Path属性用于记录他当前的路劲长度;维护一个根据每个结点的Path值从小到大排列的PriorityQueue,这样Dijkstra每次循环直接取队头元素就为最小值(时间复杂度为O(1)),不用再遍历队列找最小值(时间复杂度为O(n));维护一个已访问节点的HashSet、一个正在访问结点的HashSet。
- 每次循环遍历当前结点的所有直接相邻的边,如果另一端结点已经访问过(即存在于“正在访问结点的HashSet”和PriorityQueue中)就更新它的当前Path值,如果另一端结点未被访问过就将它加入“正在访问结点的HashSet”并设置它的当前Path值、加入PriorityQueue;然后取队头元素,当前结点-队头元素即为从当前结点出发的最短路径;将当前结点加入已访问节点的HashSet,更新当前结点为取出来的队头元素结点;进行下一次循环。
- 求最短路径:
- Network:
3.2 UML图:
二、自测策略
本单元三次作业中,主要的自测方式是三个:一是重读JML规格并肉眼检查自己的方法,二是生成测试数据然后和同学对拍,三是和同学的测试数据互通有无。
- 重读JML规格肉眼检查自己的方法:虽然繁琐、无聊,但是很重要且很有用。重读时由于我写过一遍完整的代码后设计更完整了,能够找出以前设计不够完整和大删大改时埋下的bug。同时,还能及时发现一些可以优化性能的地方。
- 生成测试数据然后和同学对拍:我自己生成了一些随机、极端情况下数据和同学对拍。
- 和同学的测试数据互通有无:感谢我的好姐妹和好梦拓以及好姐妹的好姐妹和好梦拓,让我们拥有了一些有用的往届数据。
- 关于JUnit:因为JUnit需要自己编写单元测试,如果自己对JML理解错了那肯定测不出自己代码的问题,所以感觉在正确性的判断上不如和同学对拍来的方便准确,所以直接采取了和同学对拍,并没有用JUnit。
三、Bug分析
3.1 出现的性能问题
概括一下说其实就是O(n^2)查询。
第一次作业:
- 虽然isCircle用了并查集但是queryBlockSum还是用的JML中的土办法O(n^2)查询了…所以说还是要理解算法…
第二次作业:
- 虽然queryAgeMean和queryAgeVar记得动态维护一个变量进行O(1)查询了,但是queryGoupValue还是O(n^2)查询…倒也不是说优化的丢三落四,其实就是抱着“不会10sCPU时间还能超时吧”的侥幸心理所以没在意优化这件事。
第三次作业:
- 无性能问题。
3.2 出现的正确性问题
第三次作业因为没去了解PriorityQueue的原理就直接用了,导致我以为改变了Path值pq就自动更新顺序了、而实际上pq只在新加进来元素的时候才更新顺序,导致我没有及时更新pq队列,导致我计算最短路径有时会出现错误。
四、扩展如下内容的Network
假设出现了几种不同的Person
- Advertiser:持续向外发送产品广告
- Producer:产品生产商,通过Advertiser来销售产品
- Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
- Person:吃瓜群众,不发广告,不买东西,不卖东西
如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等 请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格(借鉴所总结的JML规格模式)
-
Network:
-
属性规格:
/*@ public instance model non_null Advertiser[] advertisers; @ public instance model non_null Producer[] producers; @ public instance model non_null Customer[] customers; @ public instance model non_null Person[] persons; @ public instance model non_null Advertise[] advertises; @*/
-
方法规格:
-
sendAdvertise:广告商向指定消费者发送广告
/*@ public normal_behavior @ requires containsAdvertise(advertiseId) && @ getAdvertise(advertiseId).getAdvertiser(). @ isLinked(getAdvertise(advertiseId).getCustomer()); @ assignable advertises; @ assignable getAdvertise(advertiseId).getCustomer().advertises @ ensures !containsAdvertise(advertiseId) && advertises.length == \old(advertises.length) - 1 && @ (\forall int i; 0 <= i && i < \old(advertises.length) && @ \old(advertises[i].getId()) != advertiseId; @ (\exists int j; 0 <= j && j < advertises.length; @ advertises[j].equals(\old(advertises[i])))); @ ensures (\forall int i; 0 <= i && @ i < \old(getAdvertise(advertiseId).getCustomer().getAdvertise().size()); @ \old(getAdvertise(advertiseId)).getCustomer().getAdvertise().get(i+1) == @ \old(getAdvertise(advertiseId).getCustomer().getAdvertise().get(i))); @ ensures \old(getAdvertise(advertiseId)).getCustomer().getAdvertises().get(0).equals( @ \old(getAdvertise(advertiseId))); @ ensures \old(getAdvertise(advertiseId)).getCustomer().getAdvertises().size() == @ \old(getAdvertise(advertiseId).getCustomer().getAdvertises().size()) + 1; @ also @ public exceptional_behavior @ signals (AdvertiseIdNotFoundException e) !containsAdvertise(advertiseId); @ signals (RelationNotFoundException e) containsMessage(id) && @ !getAdvertise(advertiseId).getAdvertiser().isLinked(getAdvertise(advertiseId).getCustomer()); public void sendAdvertise(int advertiseId, Customer customer) throws AdvertiseIdNotFoundException, RelationNotFoundException
-
queryProductValue:查询商品销售额
/*@ public normal_behavior @ requires contains(productId); @ ensures \result == getProduct(productId).getValue(); @ also @ public exceptional_behavior @ signals (ProductIdNotFoundException e) !contains(productId); @*/ public /*@ pure @*/ int queryProductValue(int productId) throws ProductIdNotFoundException
-
queryProductSalePath:查询商品销售路径
/*@ public normal_behavior @ requires containsProduct(productId) && !isCircle(producer.getId(), customer.getId()); @ ensures \result == -1; @ also @ public normal_behavior @ requires containsProduct(productId) && isCircle(producer.getId(), customer.getId()); @ ensures !containsProduct(productId) && products.length == \old(products.length) - 1 && @ (\forall int i; 0 <= i && i < \old(products.length) && @ \old(products[i].getId()) != productId; @ (\exists int j; 0 <= j && j < products.length; @ products[j].equals(\old(products[i])))); @ ensures (\exists Person[] pathM; @ pathM.length >= 2 && @ pathM[0].equals(producer) && @ pathM[pathM.length - 1].equals(customer) && @ (\forall int i; 1 <= i && i < pathM.length; pathM[i - 1].isLinked(pathM[i])); @ (\forall Person[] path; @ path.length >= 2 && @ path[0].equals(producer) && @ path[path.length - 1].equals(customer) && @ (\forall int i; 1 <= i && i < path.length; path[i - 1].isLinked(path[i])); @ (\sum int i; 1 <= i && i < path.length; path[i - 1].queryPathValue(path[i])) >= @ (\sum int i; 1 <= i && i < pathM.length; pathM[i - 1].queryPathValue(pathM[i]))) && @ \result == (\sum int i; 1 <= i && i < pathM.length; @ pathM[i - 1].queryPathValue(pathM[i]))); @ also @ public exceptional_behavior @ signals (ProductIdNotFoundException e) !containsProduct(productId); @*/ public /*@ pure @*/ int queryProductSalePath(int productId, Producer procucer, Customer customer) throws ProductIdNotFoundException;
-
buyProduct:消费者直接通过Advertiser给相应Producer发一个购买消息
-
-
-
Advertiser
-
属性规格
/*@ public instance model non_null Advertise[] advertises; @ public instance model int money; @*/
-
方法规格
- sendAdvertise:广告商向指定消费者发送广告
-
-
Producer
-
属性规格
/*@ public instance model non_null Product[] products; @ public instance model int money; @*/
-
方法规格
- sendProduct:生产商向指定消费者发送产品
-
-
Customer
-
属性规格
/*@ public instance model non_null Advertise[] advertises; @ public instance model non_null Product[] products; @ public instance model int money; @*/
-
方法规格
- receiveAdvertise:收到广告
- buyProduct:买到产品
-
-
Advertise
-
属性规格
/*@ public instance model int advertiseId; @ public instance model Advertiser advertiser; @ public instance model Customer customer; @ public instance model int value; @*/
-
方法规格
- getAdvertiser:获取本广告的广告商
- getCustomer:获取本广告的目标消费者
- getId:获取本广告Id
-
-
Product
-
属性规格
/*@ public instance model int productId; @ public instance model Producer producer; @ public instance model Customer customer; @ public instance model int value; @*/
-
方法规格
- getProducer:获取本产品的生产商
- getCustomer:获取本产品的目标消费者
- getId:获取本产品的Id
-
五、心得体会
第三单元真的很容易阴沟里翻船。第三次作业我和另一个同学对拍了不少数据,由于俩人错的一样硬是没找出来错误,不幸翻车。所以在用一些包装好的容器、方法的时候一定先搞清楚它的原理再用,否则很容易踩坑而不知,比如blockingQueue和Iterator。电梯单元的时候因为iterator导致线程安全问题互测被hack烂了,这次priorityQueue又给我当头一棒。