BUAA-OO2022-UNIT3总结

1. JML基础总结

  1. 变量规格

    静态规格变量://@public static model non_null int []elements

    实例规格变量: //@public instance model non_null int []elements

  2. 方法规格

    • normal_behavior

      • 前置条件(requires):行为执行的前提条件,不满足则不做动作
      • 后置条件(ensures):行为执行的结果。用\result表示结果。
      • 副作用(assignable):行为执行期间对对象状态的改变。(可以改变哪些对象的状态)
    • exceptional_behavior

      • 前置条件(requires):通常与正常行为的前置条件互补
      • 后置条件(ensures):通常没有(也许只是我还没见到过吧,捂脸/.jpg)
      • signals子句:最常用,通过signals子句来抛出异常。
  3. 一些常用“变量”与表示:

    含义 JML
    全称量词 \forall
    存在量词 \exists
    充分 ==>
    必要 <==
    充要 <==>

    除此之外,JML还有很多方便的符号:

    含义 JML
    对给定范围内满足条件的表达式和 \sum
    对给定范围内满足条件的表达式的积 \product
    对给定范围内表达式求最大(最小) \max(\min)
    求给定范围内满足条件的个数 \num_of

    ……

2. 测试数据

对于JML测试而言,首要任务就是仔细阅读规格,在源头上避免方法实现错误。

通常的测试可以采用Junit,其测试方法类似于上学期计组的testbranch,进行assert操作,可以初步验证方法的正确性。

本次没有自己写整体的评测机,主要是手造数据,在针对某些特定指令时利用利用程序随机生成输入。

当考虑测试数据时,主要从以下方面考虑:

  1. normal_behavior和exception_behavior都要覆盖
  2. 随机生成满足前置条件的数据,用函数测试
  3. 手动构造容易超时的数据,如针对图最短路径查找的操作。

3. 架构设计

这三次作业架构都差不多,建立一个社交网络,其中有Message,Person,Group三种“成员”,在Network类中实现相互之间的交互和各种查询。

4. 图模型构建和维护

本次新建了一个Block类进行最小生成树、图、并查集的维护与简化。

Block中保存一个连通块信息:

  1. Hashset——linkedPeople:连通块中所有人
  2. valueSum:最小生成树的value总和
  3. mst:最小生成树的邻接表,同时保存value。

左右在这个连通块内的人都有一个属性指向相同的Block

简化的并查集

第9次作业时助教介绍了并查集,可以对相互认识的人之间进行低复杂度的处理。由于在实际查询时,有不涉及并查集具体树结构的查询操作,可以直接判断两者是否指向同一个联通块即可——linkedBlock属性是否指向同一个Block。

连通块的合并:判断两个结点A、B是否处于同一个连通块,只需要判断A的Map中是否含有B。增加关系事实上等价于连通块的合并,可以先判断两个结点是否位于同一个连通块中(即调用isCircle方法),如果不在的话,就修改数目较少的连通块中结点的引用。

最小生成树维护与简化查询

  1. 最小生成树维护

    在每次建立联系时考虑连通块更新。

    • !isCircle——不在同一个连通块

      这种情况可以考虑联通块的合并,然后直接将原来的两个最小生成树加上一条新边

    • isCircle——原来就在一个连通块

      这种情况不需要连通块合并,但是需要更新最小生成树。添加了这条边后原来的最小生成树一定会多一条回路,因此直接在原来的最小生成树中dfs遍历建立联系两个结点之间的路径(一定是唯一的),然后寻找这条回路中权值最大的边,从而进行最小生成树的简单更新。

  2. 简化计数。

    在每一次更新最小生成树时会保留各种信息,之后直接取出即可。

图的维护与最短路径

在Block中记录了连通块中的所有Person,而在每个Person中保存了与其直接相连的结点以及对应边的权重。因此通过遍历Block中所有Person可以对图进行遍历。

由于最短路径取决于其实Person,因此在每个Person中维护一个保存最短路径的邻接表dst,每次查询最短路径时先查dst是否已经计算好,若没有则重新计算并把结果保存在dst中;当该Person所在连通块中发生更新是需要清空dst,下一次查询时重新计算最短路径。

这样可以减少一部分每次查询都重新计算最短路径的时间花销。

5. 性能问题和修复

部分性能优化已在“图模型构建和维护”中提到,其余的部分:

  1. 第一次作业很匆忙,queryGroupAgeVar的时候采用的是遍历方法,结果互测时超时严重。之后改成在group里建立维护一个ageSum(年龄和)和ageSumSquare(年龄平方和),之后在查询时就不用再遍历。
  2. 最开始算最短路径时是有的是直接遍历,怀疑会超时,之后进行了优化,改成了小顶堆优化的Dijkstra,时间复杂度为O(|E|log|V|)。

6. Network扩展

  • Advertiser:持续向外发送产品广告
  • Producer:产品生产商,通过Advertiser来销售产品
  • Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
  • Person:吃瓜群众,不发广告,不买东西,不卖东西
  • Network:支持市场营销,并能查询某种商品的销售额和销售路径等

基本架构

  1. 建立一个Message类用来表示相互之间传递的消息,用type属性来表示消息类别——广告或购买。

  2. Advertiser、Producer、Customer都继承Person,作为其子类

    • Advertiser:与Producer建立关联,记录其发送的广告对应的产品
    • Producer:与Advertiser建立关联,记录发送其广告的对应广告者
    • Customer:与Advertiser建立关联,记录关注的广告者
  3. Network中维护一系列当前的Advertiser、Producer、Customer、Message的数据结构

  4. 其余部分于本单元的社交网络可以以相似的模式构建,比如如下的基本操作:

    	public /*@ pure @*/ boolean containsAdvertiser(int id);
    
        public /*@ pure @*/ boolean containsProducer(int id);
    
        public /*@ pure @*/ boolean containsCustomer(int id);
    
        public /*@ pure @*/ boolean containsMessage(int id);
    
        public /*@ pure @*/ Person getPerson(int id);
    
        public void addPerson(/*@ non_null @*/Person person, int type)
                throws EqualPersonIdException; //type表示是哪一种人
    
        //添加消费者关注的广告商
        public void addAttention(int id1, int id2, int value) throws
                PersonIdNotFoundException, EqualRelationException;
    
        //添加广告商要投放广告的生产商
        public void addCorporation(int id1, int id2, int value) throws
                PersonIdNotFoundException, EqualRelationException;
    	
    	//查询id制造商制造的商品的销售额
    	public /*@ pure @*/ int queryPrice(int id) throws ProducerIdNotFoundException;
    
    	//查询id商品的销售路径(代理广告商)
    	public /*@ pure @*/ Advertiser queryAgent(int id) throws ProducerIdNotFoundException;
    

核心功能及接口方法

  1. 添加广告

    把message加入到消息队列中

     /*@ 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 (\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));
          @ also
          @ public exceptional_behavior
          @ signals (EqualMessageIdException e) (\exists int i; 0 <= i && i < messages.length;
          @                                     messages[i].equals(message));
          @ signals (noAdvertiserException 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, noAdvertiserException;
    
  2. 发送广告

    把id是id的Message发送到对应的人

    /*@ public normal_behavior
          @ requires containsMessage(id) && getMessage(id).getType() == 2 &&
          @          getMessage(id).getPerson1() instancedof Advertiser;
          @ assignable messages;
          @ ensures !containsMessage(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 < \old(getMessage(id).getPerson1().acquaintance[i]);
          @          \old(getMessage(id).getPerson1().acquaintance[i].getMessages().get(i+1) == \old(getMessage(id).getPerson1().acquaintance[i].getMessages().get(i)));
          @ ensures \old(getMessage(id).getPerson1().acquaintance[i].getMessages().get(0).equals(\old(getMessage(id)));
          @ ensures \old(getMessage(id)).getPerson1().acquaintance[i].getMessages().size() == \old(getMessage(id).getPerson1().acquaintance[i].getMessages().size()) + 1;
      	  @ also
          @ public exceptional_behavior
          @ signals (noAdvertisementException e) getMessage(id).getType() != 2;
          @ signals (MessageIdNotFoundException e) !containsMessage(id);
          @ signals (noAdvertiserException e) containsMessage(id) && getMessage(id).getType() == 2 &&
          @          !(getMessage(id).getPerson1() instanceof Advertiser);
          @*/   
    public void sendAdvertisement(int id) throws MessageIdNotFoundException, noAdvertisementException, PersonIdNotFoundException, noAdvertiserException;
    
  3. 添加购买信息

    类似添加广告信息,把购买信息加入消息队列中

     /*@ public normal_behavior
          @ requires !(\exists int i; 0 <= i && i < messages.length; messages[i].equals(message)) &&
          @ message.getType() == 3 && (message.getPerson1() instanceof Advertiser) && (message.getPerson2() 		  @ instanceof Customer) && (message.getPerson3() instanceof Producer);
          @ assignable messages;
          @ ensures messages.length == \old(messages.length) + 1;
          @ 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));
          @ also
          @ public exceptional_behavior
          @ signals (EqualMessageIdException e) (\exists int i; 0 <= i && i < messages.length;
          @                                     messages[i].equals(message));
          @ signals (noAdvertiserException e) !(\exists int i; 0 <= i && i < messages.length;
          @                                     messages[i].equals(message)) && !(messages[i].getPerson1() 			  @										instanceof Advertiser);
          @ signals (noCustomerException e) !(\exists int i; 0 <= i && i < messages.length;
          @                                     messages[i].equals(message)) && !(messages[i].getPerson1() 			  @										instanceof noCustomerException);
          @ signals (noProducerException e) !(\exists int i; 0 <= i && i < messages.length;
          @                                     messages[i].equals(message)) && !(messages[i].getPerson() 			  @										instanceof noProducerException);
          @*/
    public void addPurchaseMessage(Message message) throws EqualMessageIdException, noAdvertiserException, noCustomerException, noProducerException; //添加购买消息
    
  4. 发送购买信息

    类似发送广告信息。

7. 本单元学习体会

本单元从难度上比之前小了不少,主要是根据官方包提供的JML来进行功能的具体实现。相较于之前的单元,本单元不用自己涉及架构,基本架构已经由官方包和JML给出;同时,测试起来也比之前方便,可以根据JML进行针对性测试,也不像多线程输出不可控。

但是本单元还是有许多值得花功夫的点。

  1. JML本身的熟悉与在读JML的时候理解“标准化”的思想,其实以前我们写代码是常出错的不是“实现”过程,而是“设计”过程,漏掉了某个条件,或者可能性没考虑全。那么这次阅读JML的时候则可以体会到一些设计的模式,一些常用的思考问题的角度,有助于我们以后自己的设计。
  2. 图、树算法的复习,很久没有用数据结构了,这次作业中也是对这方面的一个复习。
  3. 规范化测试数据生成的经验。以前的测试要么是瞪眼法,偶尔写了评测机也是随机生成测试样例。而这一单元作业结合Junit尝试去针对性地构造测试数据,对某个函数进行单独测试,而非整个流程拉通测试,这样效率明显更高。

本单元结束后个人还有相对薄弱的点,就是写JML,虽然读别人写好的很轻松,但自己写的时候仍然会出现漏掉边界情况,或者对于一个功能不知道怎样合理地用JML的语言表达出来,之后有时间可以加强。

posted @ 2022-06-06 15:30  火花hhh  阅读(34)  评论(1编辑  收藏  举报