OO 第三单元总结

OO 第三单元总结

一、总述

  • 本单元主要学习了如何正确解读JML规格,并根据给出的JML规格编写规范的代码,并实现异常处理功能。本单元的背景是模拟社交网络,包括维护NetWork(社交网络)、Group(群组)、Person(用户)、Message(消息)等类的信息,并且了解最小生成树、最短路等图算法。本单元没有性能分数,算是降低了一些压力,不用搞平衡树之类的优化。

二、作业分析

1. 第九次作业

(1)作业要求

  • 本次作业主要目的为熟悉基本的JML规格描述,理解入门级JML规格并完成代码实现。本次作业的背景是模拟简单的社交网络,依照JML规格实现具体模拟和查询功能,并学习异常处理的相关知识。

(2)UML类图 & 具体实现

  • 本次作业UML类图如下:

     

     

  • 本次作业主要包括MyPerson、MyGroup、MyNetwork三个类(实现官方包接口),还有六个异常处理类。

  • 首先是MyPerson以及MyGroup类,这两个类相对而言比较简单,仅需维护其内部属性,并提供修改以及查询属性的功能。可以把Person认为是一个个社交软件的用户,Group认为是群组,这样Person中的acquaintance可以认为是好友列表,value可以认为是亲密度;Group中的集体属性为群组信息,其中的People容器容纳的便是群组中的所有成员。这样来理解可以帮助我们更好地来实现这两个类的功能。值得注意的部分为查询的性能。其中较为重要的是容器的选择,在这里我选择了HashMap作为容器,key值为Person or GroupIDValue为其对应的对象。这样便可以把查找的复杂度从O(n)降为O(1)。其余按照指导书展开即可,不在这里赘述。

  • 相对而言较为复杂的是MyNetWork,这个类仅有一个实例化对象,可以理解为社交网络(终端),这里记录了用户的信息,用户的好友关系,群组的信息,可以访问其中的任一成员。该类中包含本次作业中最重要的两个查询功能,IsCircle以及QueryBlockSum。首先简单介绍一下二者的功能,isCircle是判断两个用户间是否可以通过中间关系相连(即图论中的联通),而queryBlockSum则是查询有多少个独立的非联通分支。二者按照基准策略展开可能后续会出现TLE的情况。借鉴图论中的知识,我们可以发现这里非常适合运用并查集的知识。可以开一个HashMap<Integer, Integer>来维护成员间的所属关系,这样每次在调用isCircle函数时仅需判断二者是否在一个分支上(是否有相同的根节点)即可。对于queryBlockSum功能,可以维护一个初始值为0的blockSum变量,当addPersonblockSum++addrelation时判断如果产生了非联通分支的合并则blockSum--,这样在queryBlockSum时便可以O(1)查找。

    并查集实现见下:

    在实现中考虑到查询性能问题,我采取了路径压缩 + 按秩合并的方式实现并查集。

    由于路径压缩会改变树的高度,可以把秩近似更改为树的重量,这样更方便维护。

    private HashMap<Integer, Integer> root;         //记录关系的容器
    private HashMap<Integer, Integer> weight; //记录分支重量的容器

    ///////////////查找结点所在分支(根节点)///////////////
    public int find(int id) {
           int rootId = id;
           while (root.get(rootId) != rootId) {
               rootId = root.get(rootId);
          }
           int ansId = id;
           int parent;
           while (ansId != rootId) {
               parent = root.get(ansId);
               root.put(ansId, rootId);
               ansId = parent;
          }
           return ansId;
    }
    ///////////////////////合并分支/////////////////////////
    public void merge(int id1, int id2) {
           int root1 = find(id1);
           int root2 = find(id2);
           if (root1 == root2) {
               return;
          }
           else {
               int sum = weight.get(root1) + weight.get(root2);
               if (weight.get(root1) < weight.get(root2)) {
                   root.put(root1, root2);
                   weight.put(root2, sum);
              } else {
                   root.put(root2, root1);
                   weight.put(root1, sum);
              }
               blockSum--;
          }
      }
  • 最后是异常处理的实现,这一部分中我采用了一个静态的Int型变量count(仅在程序启动时初始化一次为0)来记录某类异常出发的次数,采用HashMap<Integer, Integer>来记录该类下某PersonID(Key)触发该类异常的count(Value)。在顶层MainClass中调用Runner来捕获,在MyNetWork中检索并抛出异常。注意异常抛出的顺序,以及两id相等的判断。

(3)代码复杂度分析

MethodCogCev(G)iv(G)v(G)
code.MainClass.main(String[]) 0 1 1 1
code.MyEqualGroupIdException.MyEqualGroupIdException(int) 2 1 2 2
code.MyEqualGroupIdException.print() 0 1 1 1
code.MyEqualPersonIdException.MyEqualPersonIdException(int) 2 1 2 2
code.MyEqualPersonIdException.print() 0 1 1 1
code.MyEqualRelationException.MyEqualRelationException(int, int) 6 1 4 4
code.MyEqualRelationException.print() 0 1 1 1
code.MyGroup.MyGroup(int) 0 1 1 1
code.MyGroup.addPerson(Person) 0 1 1 1
code.MyGroup.delPerson(Person) 0 1 1 1
code.MyGroup.equals(Object) 2 2 1 3
code.MyGroup.getAgeMean() 2 2 2 3
code.MyGroup.getAgeVar() 2 2 2 3
code.MyGroup.getId() 0 1 1 1
code.MyGroup.getSize() 0 1 1 1
code.MyGroup.getValueSum() 6 1 4 4
code.MyGroup.hasPerson(Person) 1 2 1 2
code.MyGroupIdNotFoundException.MyGroupIdNotFoundException(int) 2 1 2 2
code.MyGroupIdNotFoundException.print() 0 1 1 1
code.MyNetwork.MyNetwork() 0 1 1 1
code.MyNetwork.addGroup(Group) 2 2 2 2
code.MyNetwork.addPerson(Person) 2 2 2 2
code.MyNetwork.addRelation(int, int, int) 7 4 5 6
code.MyNetwork.addToGroup(int, int) 6 4 5 5
code.MyNetwork.contains(int) 1 2 1 2
code.MyNetwork.delFromGroup(int, int) 4 4 4 4
code.MyNetwork.find(int) 2 1 3 3
code.MyNetwork.getGroup(int) 0 1 1 1
code.MyNetwork.getPerson(int) 0 1 1 1
code.MyNetwork.hasPerson(int) 1 2 1 2
code.MyNetwork.isCircle(int, int) 6 3 3 4
code.MyNetwork.merge(int, int) 5 2 3 3
code.MyNetwork.queryBlockSum() 0 1 1 1
code.MyNetwork.queryPeopleSum() 0 1 1 1
code.MyNetwork.queryValue(int, int) 7 4 5 6
code.MyPerson.MyPerson(int, String, int) 0 1 1 1
code.MyPerson.addAcquaintance(Person, int) 0 1 1 1
code.MyPerson.compareTo(Person) 0 1 1 1
code.MyPerson.equals(Object) 2 2 1 3
code.MyPerson.getAge() 0 1 1 1
code.MyPerson.getId() 0 1 1 1
code.MyPerson.getName() 0 1 1 1
code.MyPerson.isLinked(Person) 2 3 2 3
code.MyPerson.queryValue(Person) 1 2 1 2
code.MyPersonIdNotFoundException.MyPersonIdNotFoundException(int) 2 1 2 2
code.MyPersonIdNotFoundException.print() 0 1 1 1
code.MyRelationNotFoundException.MyRelationNotFoundException(int, int) 4 1 3 3
code.MyRelationNotFoundException.print() 0 1 1 1
         
Class OCavg OCmax WMC  
code.MainClass 1 1 1  
code.MyEqualGroupIdException 1.5 2 3  
code.MyEqualPersonIdException 1.5 2 3  
code.MyEqualRelationException 2.5 4 5  
code.MyGroup 1.9 4 19  
code.MyGroupIdNotFoundException 1.5 2 3  
code.MyNetwork 2.44 5 39  
code.MyPerson 1.44 3 13  
code.MyPersonIdNotFoundException 1.5 2 3  
code.MyRelationNotFoundException 2 3 4  

本次作业由于按照JML规格展开,以及相对规模较小,整体复杂度并不算很高。

(4)Bug分析 && Hack策略

  • 由于本次没有性能分,因此只要保证正确性便不会出锅。此外,需要加上并查集优化,以防被卡T。

  • Hack时也按照此策略来进行,如果qbs按照基准策略O(n^2)展开可以Hack到,在这里可以计算一下,一共1000条指令,naddPerson(1000-n)qbs,通过求导可计算出add667个Person、qbs333次时得到最极限的数据。很遗憾我们房间都写了并查集,虽然有些人没优化到O(1),但是1000条数据量太小,并不能卡T,因此并没有bug。

  • 此外,在完成本次作业时我发现,Group中的群体属性AgeMean、AgeVar、ValueSum等属性并没有被查询,但有些同学写的有些问题,但没办法Hack到。

 

2. 第十次作业

(1)作业要求

  • 本次作业在上次的基础上添加了Message类,并进一步对NetWorkPerson类进行增量开发,同时上次没有被查询的群体属性在本次也增添了相应的查询接口,整体而言工作量并不算很大,其中NetWork中加入了qlc查询指令,算是本次的一个难点(JML晦涩难懂)。

(2)UML类图 & 具体实现

  • UML类图如下:

    本次新增了MyMessage类来实现官方包中的Message类,主要提供属性的查询,还增添了Relation类存储关系(图的边),以及Block类管理每个独立的连通分支,这两个类的功能在后面展开叙述。

  

 

 

  • 具体实现:

  • 本次增添了MyMessage类来管理消息,该类基于JML展开即可,仅提供查询方法,在MyNetWorkMyPerson类中实现对其的管控。本次的Message添加以这样的顺序进行,先通过addMessage加入NetWork中进行管控(即编辑功能),然后通过sendMessage来实现消息的发送,发送后信息产生socialValue,并且从NetWork中移除管控,Person中包含一个容纳最近消息的List来负责接收最新的消息。考虑到查询的效率,在NetWork中我使用HashMap来管理消息,在Person中使用ArrayList来管理消息。

  • 本次还提供了Group中集体属性的查询,包括AgeMeanAgeVar,以及ValueSum,这里可以通过一些特殊变量的维护来将查询操作从O(N)O(N^2)降低为O(1)

    • AgeMean: 可以通过维护AgeSum来实现O(1)查找,每次用 AgeSum/n 即可;

    • AgeVar: 可以维护AgeSum、AgeVarSum来实现O(1)查找

      $$
      var= n ∑x 2 −2⋅(⌊ n ∑x ​ ⌋)⋅∑x+n⋅(⌊ n ∑x ​ ⌋) 2 ​
      $$

      注意,不能使用D(X) = E(X^2) - E(X)^2 来计算,会有精度问题。

    • ValueSum,在每次addPerson与delPerson时,遍历组内的人,判断是否需要修改valueSum,此外在addRelation时查询两人是否有群组同时包含两人,更改该群组的ValueSum,每次qgvs时返回valueSum即可,可以实现O(1)复杂度的查询。

  • 本次作业在NetWork作业中实现了qlc的查询,经过仔细研读,确定了实际上就是实现一颗最小生成树,如下为JML详细解析:

    ensures \result ==
         //////////////长度限定//////////////////////////
         @ (\min Person[] subgroup; subgroup.length % 2 == 0 &&
         /////////////subgroup中存的是边(的两个端点),因此端点相连
     @ (\forall int i; 0 <= i && i < subgroup.length / 2; subgroup[i * 2].isLinked(subgroup[i * 2 + 1])) &&
         ///////////// 关于操作的都是与该PersonID联通的描述 ——> 在一个分支上
         @ (\forall int i; 0 <= i && i < people.length; isCircle(id, people[i].getId()) <==>
         @ (\exists int j; 0 <= j && j < subgroup.length; subgroup[j].equals(people[i]))) &&
         @ (\forall int i; 0 <= i && i < people.length; isCircle(id, people[i].getId()) <==>
         ///////////// 块内的点均联通且有路径 //////////////////////
         //// 这一部分很迷语,结合前面size % 2 == 0的描述,推断出subGroup中存储的是边的两个端点,即端点可以重复出现
         @ (\exists Person[] connection;
         @ (\forall int j; 0 <= j && j < connection.length - 1;
         @ (\exists int k; 0 <= k && k < subgroup.length / 2; subgroup[k * 2].equals(connection[j]) &&
         @ subgroup[k * 2 + 1].equals(connection[j + 1])));
         @ connection[0].equals(getPerson(id)) && connection[connection.length - 1].equals(people[i])));
         ////////////////////// 求最小权值和 //////////////////////
         @ (\sum int i; 0 <= i && i < subgroup.length / 2; subgroup[i * 2].queryValue(subgroup[i * 2 + 1])));

    在最小生成树的实现方面,由于上次已经实现过并查集,所以在这里我选择了克鲁斯卡尔算法,首先找到生成树所在的分支,对其所有边Relation按权值从小到大排序,根据最小生成树的性质,该分支有n个点,仅需取n-1条边即可完成生成树,因次仅需选取出n-1条边即可结束算法。从小到大遍历边,每次查询并查集,判断两端点是否在同一分支上,如果不在则选取改边,重复直至去除n-1条边,并返回权值和。

    public int minTree() {
      //////////////排序////////////////////////
           Collections.sort(relations, new Comparator<Relation>() {
               @Override
               public int compare(Relation o1, Relation o2) {
                   return o1.getValue() - o2.getValue();
              }
          });
      //////////////初始化并查集,以及按秩合并的容器////////////
           root = new HashMap<>();
           weight = new HashMap<>();
           for (Integer id : blockPeople) {
               root.put(id, id);
               weight.put(id, 1);
          }
           int count = 0;
           int sum = 0;
      /////////////////生成树///////////////////////////
           for (int i = 0; i < relations.size(); i++) {
               Relation relation = relations.get(i);
               int root1 = find(relation.getPersonId1());
               int root2 = find(relation.getPersonId2());
               if (root1 != root2) {
                   merge(root1, root2);
                   count++;
                   sum += relation.getValue();
                   if (count == size - 1) {
                       break;
                  }
              }
          }
           return sum;
      }

    为了逻辑更加清晰以及方便操作,在这一部分我新增了,Relation类来管理关系(边),增加了Block类来维护一个分支中的所有人员和所有关系(边集):

     

     

     

    在本次作业中并查集,我均使用了路径压缩 + 按秩合并的方式,以防维护块的时候经常需要合并导致TLE,此外使用非递归写法,以防爆栈。

(3)代码复杂度分析

MethodCogCev(G)iv(G)v(G)
code.Block.Block(int, int) 0 1 1 1
code.Block.addRelation(Relation) 0 1 1 1
code.Block.find(int) 2 1 3 3
code.Block.getRelations() 0 1 1 1
code.Block.merge(int, int) 2 1 2 2
code.Block.mergeBlock(Block) 1 1 2 2
code.Block.minTree() 7 4 4 5
code.MainClass.main(String[]) 0 1 1 1
code.MyEqualGroupIdException.MyEqualGroupIdException(int) 2 1 2 2
code.MyEqualGroupIdException.print() 0 1 1 1
code.MyEqualMessageIdException.MyEqualMessageIdException(int) 2 1 2 2
code.MyEqualMessageIdException.print() 0 1 1 1
code.MyEqualPersonIdException.MyEqualPersonIdException(int) 2 1 2 2
code.MyEqualPersonIdException.print() 0 1 1 1
code.MyEqualRelationException.MyEqualRelationException(int, int) 6 1 4 4
code.MyEqualRelationException.print() 0 1 1 1
code.MyGroup.MyGroup(int) 0 1 1 1
code.MyGroup.addPerson(Person) 3 1 3 3
code.MyGroup.addValueSum(int) 0 1 1 1
code.MyGroup.delPerson(Person) 3 1 3 3
code.MyGroup.equals(Object) 2 2 1 3
code.MyGroup.getAgeMean() 1 2 1 2
code.MyGroup.getAgeVar() 1 2 1 2
code.MyGroup.getId() 0 1 1 1
code.MyGroup.getPeople() 0 1 1 1
code.MyGroup.getSize() 0 1 1 1
code.MyGroup.getValueSum() 0 1 1 1
code.MyGroup.hasPerson(Person) 1 2 1 2
code.MyGroupIdNotFoundException.MyGroupIdNotFoundException(int) 2 1 2 2
code.MyGroupIdNotFoundException.print() 0 1 1 1
code.MyMessage.MyMessage(int, int, Person, Group) 0 1 1 1
code.MyMessage.MyMessage(int, int, Person, Person) 0 1 1 1
code.MyMessage.equals(Object) 2 2 1 3
code.MyMessage.getGroup() 0 1 1 1
code.MyMessage.getId() 0 1 1 1
code.MyMessage.getPerson1() 0 1 1 1
code.MyMessage.getPerson2() 0 1 1 1
code.MyMessage.getSocialValue() 0 1 1 1
code.MyMessage.getType() 0 1 1 1
code.MyMessageIdNotFoundException.MyMessageIdNotFoundException(int) 2 1 2 2
code.MyMessageIdNotFoundException.print() 0 1 1 1
code.MyNetwork.MyNetwork() 0 1 1 1
code.MyNetwork.addGroup(Group) 2 2 2 2
code.MyNetwork.addMessage(Message) 5 3 4 4
code.MyNetwork.addPerson(Person) 2 2 2 2
code.MyNetwork.addRelation(int, int, int) 13 4 8 9
code.MyNetwork.addToGroup(int, int) 6 4 5 5
code.MyNetwork.contains(int) 1 2 1 2
code.MyNetwork.containsMessage(int) 1 2 1 2
code.MyNetwork.delFromGroup(int, int) 4 4 4 4
code.MyNetwork.find(int) 2 1 3 3
code.MyNetwork.getGroup(int) 0 1 1 1
code.MyNetwork.getMessage(int) 0 1 1 1
code.MyNetwork.getPerson(int) 0 1 1 1
code.MyNetwork.hasPerson(int) 1 2 1 2
code.MyNetwork.isCircle(int, int) 6 3 3 4
code.MyNetwork.merge(int, int, int) 5 2 3 3
code.MyNetwork.queryBlockSum() 0 1 1 1
code.MyNetwork.queryGroupAgeVar(int) 2 2 2 2
code.MyNetwork.queryGroupPeopleSum(int) 2 2 2 2
code.MyNetwork.queryGroupValueSum(int) 2 2 2 2
code.MyNetwork.queryLeastConnection(int) 2 2 2 2
code.MyNetwork.queryPeopleSum() 0 1 1 1
code.MyNetwork.queryReceivedMessages(int) 2 2 2 2
code.MyNetwork.querySocialValue(int) 2 2 2 2
code.MyNetwork.queryValue(int, int) 7 4 5 6
code.MyNetwork.sendMessage(int) 18 5 7 7
code.MyPerson.MyPerson(int, String, int) 0 1 1 1
code.MyPerson.addAcquaintance(Person, int) 0 1 1 1
code.MyPerson.addMoney(int) 0 1 1 1
code.MyPerson.addSocialValue(int) 0 1 1 1
code.MyPerson.compareTo(Person) 0 1 1 1
code.MyPerson.equals(Object) 2 2 1 3
code.MyPerson.getAge() 0 1 1 1
code.MyPerson.getId() 0 1 1 1
code.MyPerson.getMessages() 0 1 1 1
code.MyPerson.getMoney() 0 1 1 1
code.MyPerson.getName() 0 1 1 1
code.MyPerson.getReceivedMessages() 2 1 3 3
code.MyPerson.getSocialValue() 0 1 1 1
code.MyPerson.isLinked(Person) 2 3 2 3
code.MyPerson.queryValue(Person) 1 2 1 2
code.MyPersonIdNotFoundException.MyPersonIdNotFoundException(int) 2 1 2 2
code.MyPersonIdNotFoundException.print() 0 1 1 1
code.MyRelationNotFoundException.MyRelationNotFoundException(int, int) 4 1 3 3
code.MyRelationNotFoundException.print() 0 1 1 1
code.Relation.Relation(int, int, int) 0 1 1 1
code.Relation.getPersonId1() 0 1 1 1
code.Relation.getPersonId2() 0 1 1 1
code.Relation.getValue() 0 1 1 1
compare(Relation, Relation) 0 n/a n/a n/a
         
Class OCavg OCmax WMC  
code.Block 2 5 16  
code.MainClass 1 1 1  
code.MyEqualGroupIdException 1.5 2 3  
code.MyEqualMessageIdException 1.5 2 3  
code.MyEqualPersonIdException 1.5 2 3  
code.MyEqualRelationException 2.5 4 5  
code.MyGroup 1.67 3 20  
code.MyGroupIdNotFoundException 1.5 2 3  
code.MyMessage 1.11 2 10  
code.MyMessageIdNotFoundException 1.5 2 3  
code.MyNetwork 2.5 6 65  
code.MyPerson 1.33 3 20  
code.MyPersonIdNotFoundException 1.5 2 3  
code.MyRelationNotFoundException 2 3 4  
code.Relation 1 1 4  

由于JML规格扩展,本次代码复杂度相对上次有所提高,由于把BlockRelation等关系均提取出来,整体代码复杂度还相对比较低,两个复杂度较高的方法都是基于JML展开,由于循环以及多分支结构造成。

(4)Bug分析 && Hack策略

  • Hack策略主要针对的是完成作业时发现的一些问题:

  • atg操作没有注意到群组人数上限,达上限后向同一群组添加两次相同的人,bug程序会报epi,而正确程序会忽视请求输出两次OK

  • 尝试卡一下朴素的Prim算法

  • 并查集维护分支时未按秩合并

  • GroupValueSum官方的O(n^2)复杂度

  • AgeVar精度问题

 

3. 第十一次作业

(1)作业要求

  • 本次作业在上次作业的基础上对Message类进行了细化,增添了MyNoticeMessage类、MyRedenvelopMessage类以及MyEmojiMessage类,并加入NetWork中进行管控。本次的难点为SendIndirectMessage方法,即实现最短路算法。这一部分实现较为简单需要注意的是这三个类均继承自Message,仅需增加私有属性即可,构造方法也可以通过super()方法来实现。

 

(2)UML类图 & 具体实现

  • UML类图:

     

     

  • 新增的消息附属类:

    这一部分实现较为简单需要注意的是这三个类均继承自Message,仅需增加私有属性即可,构造方法也可以通过super()方法来实现。并对addMessage以及sendMessage进行修改。在这一部分中NetWork中对EmojiHeat进行了管控,这一部分可以通过HashMap来实现,keyemojiIdvalueTimes

  • SendIndirectMessage(最短路):

    该方法是对SendMessage方法的拓展,SendMessage方法仅能在isLinked的两人间发送消息,而SendIndirectMessage则可以在isCircle的两人间发送消息。由于上次作业已经维护了联通块Block,因此该部分最短路仅需在Block中实现即可,效果按照sendMessage展开,并返回二者间的最短距离。

    在这里我采用了Dijkstra算法,其核心思想是每次找出当前最短距离,并对其他边进行松弛,从而求出单源最短路。寻找当前最短距离时可以采用堆优化,这样可以把时间复杂度优化为O((∣V∣+∣E∣)log∣V∣) ,具体实现如下:

    public int minDis(int person1Id, int person2Id) {
           HashMap<Integer, Integer> book = new HashMap<>();
           HashMap<Integer, Integer> dis = new HashMap<>();
           for (Integer key : graph.keySet()) {
               book.put(key, 0);
               dis.put(key, 2147483647);
          }
           PriorityQueue<Relation> queue = new PriorityQueue<>();
           queue.add(new Relation(person1Id, person1Id, 0));
           dis.put(person1Id, 0);

           while (!queue.isEmpty()) {
               Relation relation = queue.poll();
               int to = relation.getPersonId2();
               if (dis.get(to) < relation.getValue()) {
                   continue;
              }
               if (book.get(to) == 1) {
                   continue;
              }
               book.put(to, 1);
               for (int i = 0; i < graph.get(to).size(); i++) {
                   int next = graph.get(to).get(i).getPersonId2();
                   int weight = graph.get(to).get(i).getValue();
                   if (book.get(next) == 0 &&
                           dis.get(next) > dis.get(to) + weight) {
                       dis.put(next, dis.get(to) + weight);
                       queue.add(new Relation(person1Id, next, dis.get(next)));
                  }
              }
          }

           return dis.get(person2Id);
      }

(3)代码复杂度分析

MethodCogCev(G)iv(G)v(G)
code.Block.Block(int, int) 0 1 1 1
code.Block.addRelation(Relation) 4 1 3 3
code.Block.find(int) 2 1 3 3
code.Block.merge(int, int) 2 1 2 2
code.Block.mergeBlock(Block) 1 1 2 2
code.Block.minDis(int, int) 12 4 6 8
code.Block.minTree() 7 4 4 5
code.MainClass.main(String[]) 0 1 1 1
code.MyEmojiIdNotFoundException.MyEmojiIdNotFoundException(int) 2 1 2 2
code.MyEmojiIdNotFoundException.print() 0 1 1 1
code.MyEmojiMessage.MyEmojiMessage(int, int, Person, Group) 0 1 1 1
code.MyEmojiMessage.MyEmojiMessage(int, int, Person, Person) 0 1 1 1
code.MyEmojiMessage.getEmojiId() 0 1 1 1
code.MyEqualEmojiIdException.MyEqualEmojiIdException(int) 2 1 2 2
code.MyEqualEmojiIdException.print() 0 1 1 1
code.MyEqualGroupIdException.MyEqualGroupIdException(int) 2 1 2 2
code.MyEqualGroupIdException.print() 0 1 1 1
code.MyEqualMessageIdException.MyEqualMessageIdException(int) 2 1 2 2
code.MyEqualMessageIdException.print() 0 1 1 1
code.MyEqualPersonIdException.MyEqualPersonIdException(int) 2 1 2 2
code.MyEqualPersonIdException.print() 0 1 1 1
code.MyEqualRelationException.MyEqualRelationException(int, int) 6 1 4 4
code.MyEqualRelationException.print() 0 1 1 1
code.MyGroup.MyGroup(int) 0 1 1 1
code.MyGroup.addPerson(Person) 3 1 3 3
code.MyGroup.addValueSum(int) 0 1 1 1
code.MyGroup.delPerson(Person) 3 1 3 3
code.MyGroup.equals(Object) 2 2 1 3
code.MyGroup.getAgeMean() 1 2 1 2
code.MyGroup.getAgeVar() 1 2 1 2
code.MyGroup.getId() 0 1 1 1
code.MyGroup.getPeople() 0 1 1 1
code.MyGroup.getSize() 0 1 1 1
code.MyGroup.getValueSum() 0 1 1 1
code.MyGroup.hasPerson(Person) 1 2 1 2
code.MyGroupIdNotFoundException.MyGroupIdNotFoundException(int) 2 1 2 2
code.MyGroupIdNotFoundException.print() 0 1 1 1
code.MyMessage.MyMessage(int, int, Person, Group) 0 1 1 1
code.MyMessage.MyMessage(int, int, Person, Person) 0 1 1 1
code.MyMessage.equals(Object) 2 2 1 3
code.MyMessage.getGroup() 0 1 1 1
code.MyMessage.getId() 0 1 1 1
code.MyMessage.getPerson1() 0 1 1 1
code.MyMessage.getPerson2() 0 1 1 1
code.MyMessage.getSocialValue() 0 1 1 1
code.MyMessage.getType() 0 1 1 1
code.MyMessageIdNotFoundException.MyMessageIdNotFoundException(int) 2 1 2 2
code.MyMessageIdNotFoundException.print() 0 1 1 1
code.MyNetwork.MyNetwork() 0 1 1 1
code.MyNetwork.addGroup(Group) 2 2 2 2
code.MyNetwork.addMessage(Message) 10 5 5 6
code.MyNetwork.addPerson(Person) 2 2 2 2
code.MyNetwork.addRelation(int, int, int) 13 4 8 9
code.MyNetwork.addToGroup(int, int) 6 4 5 5
code.MyNetwork.clearNotices(int) 7 2 4 4
code.MyNetwork.contains(int) 1 2 1 2
code.MyNetwork.containsEmojiId(int) 0 1 1 1
code.MyNetwork.containsMessage(int) 1 2 1 2
code.MyNetwork.delFromGroup(int, int) 4 4 4 4
code.MyNetwork.deleteColdEmoji(int) 11 1 8 8
code.MyNetwork.find(int) 2 1 3 3
code.MyNetwork.getGroup(int) 0 1 1 1
code.MyNetwork.getMessage(int) 0 1 1 1
code.MyNetwork.getPerson(int) 0 1 1 1
code.MyNetwork.hasPerson(int) 1 2 1 2
code.MyNetwork.isCircle(int, int) 6 3 3 4
code.MyNetwork.merge(int, int, int) 5 2 3 3
code.MyNetwork.queryBlockSum() 0 1 1 1
code.MyNetwork.queryGroupAgeVar(int) 2 2 2 2
code.MyNetwork.queryGroupPeopleSum(int) 2 2 2 2
code.MyNetwork.queryGroupValueSum(int) 2 2 2 2
code.MyNetwork.queryLeastConnection(int) 2 2 2 2
code.MyNetwork.queryMoney(int) 2 2 2 2
code.MyNetwork.queryPeopleSum() 0 1 1 1
code.MyNetwork.queryPopularity(int) 2 2 2 2
code.MyNetwork.queryReceivedMessages(int) 2 2 2 2
code.MyNetwork.querySocialValue(int) 2 2 2 2
code.MyNetwork.queryValue(int, int) 7 4 5 6
code.MyNetwork.sendIndirectMessage(int) 8 3 5 6
code.MyNetwork.sendMessage(int) 33 5 12 12
code.MyNetwork.storeEmojiId(int) 2 2 2 2
code.MyNoticeMessage.MyNoticeMessage(int, String, Person, Group) 0 1 1 1
code.MyNoticeMessage.MyNoticeMessage(int, String, Person, Person) 0 1 1 1
code.MyNoticeMessage.getString() 0 1 1 1
code.MyPerson.MyPerson(int, String, int) 0 1 1 1
code.MyPerson.addAcquaintance(Person, int) 0 1 1 1
code.MyPerson.addMoney(int) 0 1 1 1
code.MyPerson.addSocialValue(int) 0 1 1 1
code.MyPerson.compareTo(Person) 0 1 1 1
code.MyPerson.equals(Object) 2 2 1 3
code.MyPerson.getAge() 0 1 1 1
code.MyPerson.getId() 0 1 1 1
code.MyPerson.getMessages() 0 1 1 1
code.MyPerson.getMoney() 0 1 1 1
code.MyPerson.getName() 0 1 1 1
code.MyPerson.getReceivedMessages() 2 1 3 3
code.MyPerson.getSocialValue() 0 1 1 1
code.MyPerson.isLinked(Person) 2 3 2 3
code.MyPerson.queryValue(Person) 1 2 1 2
code.MyPersonIdNotFoundException.MyPersonIdNotFoundException(int) 2 1 2 2
code.MyPersonIdNotFoundException.print() 0 1 1 1
code.MyRedEnvelopeMessage.MyRedEnvelopeMessage(int, int, Person, Group) 0 1 1 1
code.MyRedEnvelopeMessage.MyRedEnvelopeMessage(int, int, Person, Person) 0 1 1 1
code.MyRedEnvelopeMessage.getMoney() 0 1 1 1
code.MyRelationNotFoundException.MyRelationNotFoundException(int, int) 4 1 3 3
code.MyRelationNotFoundException.print() 0 1 1 1
code.Relation.Relation(int, int, int) 0 1 1 1
code.Relation.compareTo(Relation) 0 1 1 1
code.Relation.getPersonId1() 0 1 1 1
code.Relation.getPersonId2() 0 1 1 1
code.Relation.getValue() 0 1 1 1
compare(Relation, Relation) 0 n/a n/a n/a
         
Class OCavg OCmax WMC  
code.Block 3 7 24  
code.MainClass 1 1 1  
code.MyEmojiIdNotFoundException 1.5 2 3  
code.MyEmojiMessage 1 1 3  
code.MyEqualEmojiIdException 1.5 2 3  
code.MyEqualGroupIdException 1.5 2 3  
code.MyEqualMessageIdException 1.5 2 3  
code.MyEqualPersonIdException 1.5 2 3  
code.MyEqualRelationException 2.5 4 5  
code.MyGroup 1.67 3 20  
code.MyGroupIdNotFoundException 1.5 2 3  
code.MyMessage 1.11 2 10  
code.MyMessageIdNotFoundException 1.5 2 3  
code.MyNetwork 2.91 11 96  
code.MyNoticeMessage 1 1 3  
code.MyPerson 1.33 3 20  
code.MyPersonIdNotFoundException 1.5 2 3  
code.MyRedEnvelopeMessage 1 1 3  
code.MyRelationNotFoundException 2 3 4  
code.Relation 1 1 5  

本次代码复杂度控制依旧良好,整体按照JML规格展开,并在可以优化的部分进行了优化,除了sendMessage等具有较多分支条件的方法外,整体复杂度良好。

(4)Bug分析 && Hack策略

本次强测互测环节依旧没出现过bug,顺利的苟过了第三单元,但在互测中也没有什么实质性的斩获,喜提成功Hack评测机一次。

大概是这样的数据,首先addid为1的NoticeMessage,然后发送出来,这样messages里已经没有该id的消息,可以再次添加一条普通的id仍为1的消息,再次send此时接收者的消息列表中就有了两条id1的不同类型消息,此时调用clearNotice,正常应该清除掉第一次发送的Notice保留下第二次的消息,但是在实际操作过程中如果我们调用了remove(message)方法的话,由于官方程序重写过equals方法,会认为所有id相同的message相等,因此,remove方法找到了第一个相同id的消息进行删除,导致删除了不该删除的普通消息,留下了应该删掉的notice消息。

解决办法就是改成remove(下标),这是我在公测结束前发现的神奇bug,感谢jbt同学的数据数据支持,本来想着看看有没有和我一样的冤大头,结果发现提交正确的输出官方说非法,错误的输出反倒通过了,然后就引发了血雨腥风……好在后面迅速修复了这个bug。(同房的xd都没犯这个错误)

 

三、NetWork拓展

  • 新增

  • Advertiser:持续向外发送产品广告

  • Producer:产品生产商,通过Advertiser来销售产品

  • Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息

  • Person:吃瓜群众,不发广告,不买东西,不卖东西

Advertiser、Producer、Customer均继承自Person类,维护自己的私有属性,同时为了满足广告和购买信息可以新增Advertisement和PurchaseMessage继承Message。

  • JML规格

添加广告

/*@ public normal_behavior
     @ requires !(\exists int i; 0 <= i && i < messages.length; messages[i].equals(message)) &&
     @ message.getType() == 2 && (message.getPerson1() instanceof Advertiser);
     @ assignable messages;
     @ ensures messages.length == \old(messages.length) + 1;
     @ ensures (\exists int i; 0 <= i && i < messages.length; messages[i].equals(message));
     @ ensures (\forall int i; 0 <= i && i < \old(messages.length);
     @         (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i]))));
     @ also
     @ public exceptional_behavior
     @ signals (EqualMessageIdException e)
     @ (\exists int i; 0 <= i && i < messages.length; messages[i].equals(message));
     @ signals (AdvertiserNotFoundException e)
     @ !(\exists int i; 0 <= i && i < messages.length; messages[i].equals(message))
     @ && !(messages[i].getPerson1() instanceof Advertiser);
     @*/
public void addAdvertisement(Message message) throws EqualMessageIdException, AdvertiserNotFoundException;

添加产品

/* public normal_behavior
     @ requires !(\exists int i; 0 <= i && i < products.length; products[i].equals(product));
     @ assignable products;
     @ ensures products.length == \old(products.length) + 1;
     @ ensures (\exists int i; 0 <= i && i < products.length; products[i] == product);
     @ ensures (\forall int i; 0 <= i && i < \old(products.length);
     @         (\exists int j; 0 <= j && j < products.length; products[j] == (\old(products[i]))));
     @ also
     @ public exceptional_behavior
     @ signals (EqualProductIdException e) (\exists int i; 0 <= i && i < products.length;
     @                                     products[i].equals(product));
     @*/
   public void addProduct(/*@ non_null @*/Product product) throws EqualProductIdException;

查询销售额

/*@ public normal_behavior
 @ requires (\exists int i; 0 <= i && i <= people.size;
 @ people[i].equals(producer) && people[i] instanceof Producer)
     @ ensures \result == producer.getMoney
     @ also
     @ public exceptional_behavior
     @ signals (PersonIdNotFoundException e)
     @ (\forall i; 0 <= i && i < people.size; !people[i].equals(producer))
     @*/
public int querySaleVolume(Person producer) throws PersonIdNotFoundException;

 

 

四、反思感悟

本单元还算顺利,由于给出了JML规格因此整体工作量并没有很大,优化方面由于没有性能分,仅将O(N^2)复杂度的部分优化,以防T掉。验证方式采用了形式化验证(肉眼比对),加上随机数据(@jbt) & 对拍的方式来保障正确性。最大的困难可能就是解读形式化描述了吧,再次点名qlc。还有就是维护好代码的逻辑结构,由于较早的抽象出了Block(联通块?)这一个类来维护人员之间的关系,后面的最小生成树以及最短路算法均可在该类中实现,并不需要担心NetWork类过长的情况。整体对本单元的学习还算比较满意。

 
posted @ 2022-06-02 23:31  warriors2001  阅读(60)  评论(2编辑  收藏  举报