2022北航面向对象第三次作业分享及总结
2022北航面向对象第三次作业分享及总结
本次作业从5月2号开始,经过三周的迭代开发,在JML规格理解的基础上,完成代码实现,最终完成了一个具有模拟查询功能的社交网络。
作业背景
JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言。 JML是一种行为接口规格语言(Behavior Interface Specification Language,BISL),所谓接口即一个方法或类型外部可见的内容。
一般而言,JML有两种主要的用法:
(1)开展规格化设计。这样交给代码实现人员的将不是可能带有内在模糊性的自然语
言描述,而是逻辑严格的规格。
(2)针对已有的代码实现,书写其对应的规格,从而提高代码的可维护性。这在遗留代码的维护方面具有特别重要的意义。
本次作业即针对现有jml描述的规格设计,完成相应的代码实现。
架构设计
UML图
-
由于此社交网络所有的查询关系都是以连通分支为最小单位,因此本次作业中我以连通分量为单位进行图模型构建,并且动态构造了自定的边(side)类进行存储。
-
这里我基于并查集算法采取了动态方式进行图模型的维护,并且实现了路径压缩。
性能问题
此次作业中,我尽量都采用了hashMap的存储结构,这样存储的优点在于查询速度很快,免去了list中遍历查找的复杂性,并且十分适合于本题中保证的一一映射的数据要求,从而大大提升运行性能。
此次作业中需要考虑性能优化的方法有以下几种,我针对不同的方法采取了几种不同的优化策略,但总体都是基于动态维护,静态查询的方式进行处理。
-
queryValueSum:
如果采用静态查询方法,则需要对group内的peopleList进行两遍遍历,复杂度为O(n**2),在大规模数据中很容易超时,这一点也是我之前有所忽略的。之后改用动态维护,每一次addRelation都更新valueSum,等到查询的时候只需要返回对应值即可,复杂度降为O(1)。
注意项目应当排除任何复杂度大于等于O(n**2)的方法
- isCircle
函数原型public boolean isCircle(int id1, int id2);
此方法查询id1对应的person和id2对应的person是否在同一个连通分量中。上文提到,我采用了并查集算法对连通分量进行了动态的维护,并且实现了路径压缩,因此在此方法中,我只需要查询id1与id2所在的集合编号是否相同即可,复杂度为O(1);
-
queryBlockSum
此方法用来返回当前network中连通分支的数量。此方法与上一个方法配套,在实现了连通分支管理的基础上,只需要在每次改变图的同时增加计数即可,方法内只需要返回对应的记录值,复杂度为O(1); -
queryLeastConnection
此方法要求返回id所在的连通分量对应的最小生成树中的权值。
在这里,我采用了kruskal算法求最小生成树,采用优先队列的方式进行排序,并且在方法内部重新维护了一个并查集,实现是否处于连通分支的判断。 -
sendIndirectMessage
此方法要求返回id1到id2的最短路径,这里我采用了堆优化的dijkstra算法。在实现这一部分的时候我忽略了每个person已经存有了以其为节点的所有边,导致我在初步实现的时候进行了一次对图中所有边的遍历以得到处在当前连通分支里的边集合,这也导致了我此方法复杂度过高,在强测过程中有一个点没过去。
测试数据生成
在构造图的过程中,我采用了连通分支集中构造的思想,类似于深度优先遍历,从根节点开始延伸构造连通分支。
通过对jml规格的观察分析,针对几个可能出现高复杂度的方法进行了集中构造,以测试cpu性能。
NetWork扩展
要求:假设出现了几种不同的Person
-
Advertiser:持续向外发送产品广告
-
Producer:产品生产商,通过Advertiser来销售产品
-
Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
-
Person:吃瓜群众,不发广告,不买东西,不卖东西
如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等 请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格(借鉴所总结的JML规格模式)
//新建类
//三种person
public class Advertiser implements Person;
public class Producer implements Person;
public class Customer implements Person;
//产品类
public class Product;
//一种message
public class productmentMessage implements Message;
public class advertisementMessage implements Message;
public class purchaseMessage implements Message;
/*业务逻辑
advertiser中存储不同的producer对应的advertisementMessage;
通过addProduct为pruducer中增加产品
通过sendAdvertisement(int advertisementId)方法将销售产品的广告投放给广告商;
customer通过purchase(int purchaseMessageId)提出购买需求
通过queryProductSale查询商品销售额
通过queryProductPath查询商品销售路径
*/
//为三个核心业务功能接口方法撰写JML规格
/*@ public normal_behavior
@ requires containsAdvertisement(id);
@ assignable AdvertisementList;
@ ensures !containsAdvertisement(id) && AdvertisementList.length == \old(AdvertisementList.length) - 1 &&
@ (\forall int i; 0 <= i && i < \old(AdvertisementList.length) && \old(AdvertisementList[i].getId()) != id;
@ (\exists int j; 0 <= j && j < AdvertisementList.length; AdvertisementList[j].equals(\old(AdvertisementList[i]))));
@ ensures (\forall int i; 0 <= i && i < CustomerList.length;
@ (\forall int j; 0 <= j < \old(CustomerList[i].AdvertisementList.length)
@ (\exists int k; 0 <= k < CustomerList[i].AdvertisementList.length;
@ \old(CustomerList[i].AdvertisementList[j]) == CustomerList[i].AdvertisementList[k])));
@*/
public void sendAdvertisement(int id);
/* @ public normal_behavior
@ requires !(\exists int i; 0 <= i && i < productList.length; productList[i].equals(product))
@ assignable productList
@ ensures (\forall int i; 0 <= i && i < \old(productList.length);
@ (\exists int j; 0 <= j && j < productList.length; productList[j].equals(\old(productList[i]))));
@ ensures (\exists int i; 0 <= i && i < productList.length; productList[i].equals(product));
@ ensures productList.length == \old(productList.length) + 1;
@ also
@ public exceptional_behavior
@ signals (ProductException e) (\exists int i; 0 <= i && i < productList.length; productList[i].equals(product))
@*/
void addProduct(Product product, Person advertiser) throws ProductException;
/*@ public normal_behavior
@ assignable messages
@ requires contains(customerId) && contains(advertiserId) && contains(product.getProducer);
@ requires getPerson(advertiserId).containsProduct(product);
@ 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) && message.getPerson1.equals(getPerson(advertiserId)) && message.getPerson2.equals(product.getProducer);
@ ensures messages.length == \old(messages.length) + 1;
@ ensures !getPerson(advertiserId).containsProduct(product);
@ also
@ public exceptional_behavior
@ signals (PeronIdNotFoundException) !contains(customerId);
@ also
@ public exceptional_behavior
@ signals (PeronIdNotFoundException) !contains(advertiserId);
@ also
@ public exceptional_behavior
@ signals (PeronIdNotFoundException) !contains(product.getProducer);
@ also
@ public exceptional_behavior
@ signals (ProductIdNotFoundException) !getPerson(advertiserId).containsProduct(product);
@*/
void purchase(int purchaseMessageId);
本单元学习体会
通过本单元的学习,我不仅学会了如何面对JML规格书写代码,同时也体会到了如何在项目完成过程中兼顾性能,并且回顾了算法的相关的知识。