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列表中。

1.2 UML图:

2. 第二次作业

2.1 图模型构建和维护策略:

  • 构建(比上次作业增加的):
    • Network:比上次作业多了massage列表。
  • 维护(比上次作业增加的):
    • Network:
      • 动态增加message:
        • addMessage时将message加入。相应地增加了单人对单人发消息和单人群发消息的方法。
      • 维护一个记录所有边的EdgeManagement容器:
        • 新增了一个Edge类记录一条边的两顶点、权值。
        • addRelation时还要new出这条边并加入EdgeManagement容器。
        • 此容器用于记录所有边,方便kruskal算法以O(n)的时间复杂度遍历所有边
      • 求最小生成树:
        • 采用【并查集+Kruskal算法】:先找到出发结点所在的连通分支的所有边;再将连通分支所有边按权值排序;再用kruskal算法遍历已排序的连通分支所有边来求最小生成树,通过维护一个新的并查集记录最小生成树已有结点和边、每遍历到一条边用并查集判断此边的两端点是否已经连通(即第一次作业中isCircle方法),若已经连通则加上此边会形成环,应舍弃;若未联通则加上此边不会形成环,应当加入。
    • Group:
      • 动态维护ageSum、agePowerSum保证查询的时间复杂度为O(1):
        • 如果不维护ageSum、agePowerSum,查询年龄平均数、年龄方差平均数都需要双循环遍历计算一遍,时间复杂度为O(n^2),在当前指令条数和CPU时间限制下会超时。因此必须维护ageSum、agePowerSum,这样查询年龄平均数、年龄方差平均数只需要进行一个时间复杂度为O(1)的计算就可以了。

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,更新当前结点为取出来的队头元素结点;进行下一次循环。

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又给我当头一棒。

posted @ 2022-06-03 15:07  Siazxyyy  阅读(40)  评论(0编辑  收藏  举报