面向对象第三单元博客作业

第三单元博客作业


架构设计

本单元的主要内容是建立一个社交网络(图)模型,并实现预设的方法,架构设计的重点在于图模型的设计。

图模型设计

从本单元第二次作业开始,出现了最小生成树问题,这时需要我们自己建立图模型。

  • 分析题目,我们可以得到,输入的社交网络是一个不一定联通的较为稀疏的图。因此我设计了两个容器类:GraphSetGraph类。其中,GraphSet类是若干个不相同的联通图(Graph)的集合。

  • 由于在指令过程中我们的图是不断迭代变化的,因此需要容器中的图支持增边与合并。

首先从Graph类进行介绍。

  • Graph

Graph类是一个连通图,大致具有以下修改方法:

  1. 初始化(新增第一个节点)
  2. 合并图(与另外一个联通图间增加一条边)

除此之外,支持以下查询方法:

  1. 查询最小生成树(Kruskal
  2. 查询某两点最短路(Dijkstra

然后是GraphSet类。

  • GraphSet

GraphSet类是若干连通图的集合,大致拥有以下修改方法:

  1. 增加节点(增加新的连通图)
  2. 增加边(已有连通图内加边/合并两个连通图)

除此之外,查询方法与Graph相同,首先找到对应的图然后调用对应图的查询方法。

在维护GraphSet时,用到了并查集。这里的并查集使用了两个HashMap映射,一个是节点到连通图编号的映射,一个是连通图编号的迭代映射。如此设计的目的是为了避免在合并两个图后,不用将两个图的所有节点的对应图编号都进行更新,只需要增加一个连通图编号的迭代的键值对即可(从原有图编号到新图编号)。如此,在查询某节点对应的图时,首先通过节点编号取出图编号,然后通过图编号在迭代映射中找到目前的图编号,最后完成对应操作。

测试

构造数据

通过JML表面的循环层数,以及语义上的复杂度,我们可以找出性能较差的指令。针对这些算法复杂度较高的指令构造压力测试,从而避免超时的问题。除此之外,对于一些采用递归的方法,我们可以构造特殊数据尽可能多的增加递归层数尝试爆栈。

此外,对于正确性测试,采用构造ID池的大压力随机数据测试,将生成的数据进行覆盖率测试,以覆盖所有代码。

数据支持ID池大小设置、支持具体指令条数限制、支持数据范围限制。

性能优化

本单元作业的性能优化较为重要,需要最大限度的减小复杂度,以免超时。

图优化

其中数据的存储方式比较重要。为了方便最小生成树的计算,使用了有序集合TreeSet<Edge>存储边元素,使得在计算最小生成树时天然拥有边权从小到大的序列,降低了算法复杂度。同时使用并查集来避免生成回路,也有效的降低了复杂度。

先前对于最小生成树采用了Prim算法,效率较低,后改正。

对于最短路,使用了优先队列PriorityQueue(堆)来存储节点信息,使用堆优化的Dijkstra算法。

先前对于最短路没有进行堆优化,导致程序效率较低,后改正。

对于用到的并查集,使用路径压缩,减少查询次数。

此外,对于Graph的属性,引入缓存机制,每次查询后将结果缓存,若没有插入新节点或合并图时再次查询,即可直接返回结果,而不需要再次运算。

其它优化

对于社交网络的其它查询元素,采用动态更新+缓存的机制,使得几乎所有操作约等于查表。

先前对于数字性操作采用现场计算的方式,但一些指令如方差会出现O(n**2)的现象从而自己出现超时,后改正。

设计拓展

综合需求,需要增加购买/广告机制,结合现有机制,一种可能的实现是:

  • 增加广告消息(广告群组/广告商群发给个人/广告商指定推送给某用户)
  • 增加购买消息(广告消息中推送生产商联系方式,用户与广告商认识,广告商认识生产商,发送非直接消息来购买,发送购买消息需要广告消息的指标(含广告商信息、价格和优惠信息等)、消耗一定金钱)
  • 增加广告商用户:新增代理物品列表属性、各商品销售额属性
  • 增加制造商用户:新增代理商列表属性、各商品成本等相关属性
  • 增加消费者用户:新增人物消费画像(偏好)、消费能力等偏好

对于社交网络,增加以下Market接口。

public interface Market {
    //增加商品信息
    void addGood(Producer producer, GoodMessage good);

    //授权广告商来销售
    void authorize(Producer producer, Advertiser advertiser, GoodMessage good);
    
    //广告商开始销售(可以有许多重构、个人销售、群体销售等)
    void promote(Advertiser advertiser, GoodMessage good);

    //消费者购买
    void purchase(Customer customer, SaleMessage good);

    //...
}

其中,选择三个方法撰写规格如下。

    /*@ public normal_behaviour
      @ requires !producer.goods.hasKey(good);
      @ assignable producer.goods;
      @ ensures producer.goods.size() = \old(producer.goods.size()) + 1;
      @ ensures producer.goods.hasKey(good);
      @ ensures producer.goods.get(good.getId()) = good;
      @ ensures (\forall int id; \old(goods).hasKey(id); 
      @             goods.hasKey(id) && \old(goods.get(id)).equals(goods.get(id)));
      @ also 
      @ public exceptional_behaviour
      @ signals (DuplicatedGoodException e) producer.goods.hasKey(good);
      @*/
    void addGood(Producer producer, GoodMessage good);

    /*@ public normal_behaviour
      @ requires advertiser.hasAuthority(good);
      @ assignable messages;
      @ ensures messages.length = \old(messages.length) + advertiser.getCustomerSum();
      @ ensures (\forall int i; 0 <= i && i < \old(messages.length); messages[i] = \old(messages[i]));
      @ ensures (\forall Customer c; \old(hasCustomer(c)); 
      @           (\exists SaleMessage s; s.isSaleOf(good) && s.getPerson2().equals(c); containsMessage(s)));
      @ also
      @ public exceptional_behaviour
      @ signals (IllegalSaleBehaviourException e) !advertiser.hasAuthority(good);
      @*/
    void promote(Advertiser advertiser, GoodMessage good);

    /*@ public normal_behaviour
      @ requires \old(customer.getMoney()) >= good.getPrice();
      @ assignable customer.money, good.getAdvertiser().money, good.getProducer().money;
      @ assignable good.getAdvertiser().salesVolume.get(good.getId);
      @ assignable good.getProducer().goods.get(good.getId);
      @ ensures customer.getMoney() = \old(customer.getMoney()) - good.getPrice();
      @ ensures good.getAdvertiser().salesVolumeOf(good.getId()) = \old(good.getAdvertiser().salesVolumeOf(good.getId())) + 1;
      @ ensures good.getAdvertiser().getMoney() = \old(good.getAdvertiser().getMoney) + good.getPrice() - good.getCost();
      @ ensures good.getProducer().getMoney() = \old(good.getProducer().getMoney) + good.getCost();
      @ ensures good.getProducer().getGood(good.getId()) = \old(good.getProducer().getGood(good.getId())) - 1;
      @ also
      @ public exceptional_behaviour
      @ signals (InsufficientFundException e) \old(customer.getMoney()) < good.getPrice();
      @*/
    void purchase(Customer customer, SalesMessage good);

心得体会

本单元中,学习了新的数据结构,同时复习了原来所学的图算法。本单元较为简单,对于设计方面要求较低,需要实现的方法已经通过JML给出了明确的要求,只需要稍加设计即可完成。相比前两单元作业,本单元作业较为简单,使我对JML有了一定的了解。对于一些复杂的方法而言,JML确实十分抽象,仅仅通过JML理解方法的要求较为困难,理解起来不太容易(例如最小生成树、最短路等),可能是JML的准确性导致了过于抽象。

posted @ 2022-06-01 22:07  raspstudio  阅读(32)  评论(0编辑  收藏  举报