面向对象第三单元博客作业
第三单元博客作业
架构设计
本单元的主要内容是建立一个社交网络(图)模型,并实现预设的方法,架构设计的重点在于图模型的设计。
图模型设计
从本单元第二次作业开始,出现了最小生成树问题,这时需要我们自己建立图模型。
-
分析题目,我们可以得到,输入的社交网络是一个不一定联通的较为稀疏的图。因此我设计了两个容器类:
GraphSet
和Graph
类。其中,GraphSet
类是若干个不相同的联通图(Graph
)的集合。 -
由于在指令过程中我们的图是不断迭代变化的,因此需要容器中的图支持增边与合并。
首先从Graph
类进行介绍。
Graph
类
Graph
类是一个连通图,大致具有以下修改方法:
- 初始化(新增第一个节点)
- 合并图(与另外一个联通图间增加一条边)
除此之外,支持以下查询方法:
- 查询最小生成树(
Kruskal
)- 查询某两点最短路(
Dijkstra
)
然后是GraphSet
类。
GraphSet
类
GraphSet
类是若干连通图的集合,大致拥有以下修改方法:
- 增加节点(增加新的连通图)
- 增加边(已有连通图内加边/合并两个连通图)
除此之外,查询方法与
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的准确性导致了过于抽象。