BUAA-OO-Unit3-CommunicationNetwork-Summary

第三单元作业总结--JML与社交网络实现

关于基于JML测试

本单元指导书中推荐的测试方法是进行JUnit测试。在简单尝试后,我感觉JUnit测试能够正常运作的前提是需要构造出合适的测试样例,并且根据官方给出的JML,自己用assert等方式设定正确结果,也就是说使用JUnit测试的前提依然是正确理解JML。既然势必要自行构造测试样例,并且无法通过JUnit判断自己对于官方给出的JML的理解,我最终还是没采用JUnit的测试方式,而是通过传统的编写随机生成数据脚本+搭建评测机和小伙伴对拍的方式实现的测试。

数据生成和测试方面,我和小伙伴合作编写了数据生成脚本和评测机,并进行了对拍。

第一次作业中,我主要完成了第一次作业中所有指令的有策略的随机生成部分,可以实现调整生成异常指令的概率,并且在每次更改信息后,通过生成查询指令对状态进行查询。

例如

def addPersonPackaged(canErr): #canErr参数控制此次生成指令是否可以异常
    ifErr = random.randint(0,100)%errHappenRate == 0 #errHappenRate参数控制异常发生的概率
    if(ifErr and canErr and len(personIdList) > 0):
        id = equalPersonIdErr()
        add_person(id, random.choice(nameList), random.randint(0, 201))
    else:
        id = personIdNoFoundErr()
        add_person(id, random.choice(nameList), random.randint(0, 201))
        personIdList.append(id)

第二次作业中,我主要完成了与Message和Group相关的指令的随机生成

第三次作业中,由于新增指令较多,新增的message类型较多,由于时间原因,我列出了测试要点,手动构造测试数据进行测试。

XGKRTH.png

关于架构

本次的架构比较简单,就是按照官方给出的接口实现了要求我们实现的类和异常类。除此之外,为了便于实现图的各种算法,我额外实现了一个并查集类和边节点类。具体的查找最短路,最小生成树的算法部分,由于比较简单,并且没有重复利用,于是我直接在MyNetWork里实现了。

关于图模型的维护和构建,我在储存数据时均采用id->ObjectHashMap来储存,这样可以比较快的获取目标元素。至于具体到每个类储存什么信息,则和官方给出的JML保持一致即可。

关于算法性能以及bug修复

第一次作业

性能分析

第一次作业中,时间复杂度可能较高的方法是查询两人是否处于同一联通分支的isCircle方法。我采用了并查集来保证时间复杂度不超出限制。并且增加了路径压缩优化和按秩合并优化,使得复杂度降低到\(O(1+log*n)\)

如果没有实现并查集,那么另一个比较危险的指令则是query block sum。在实现了并查集的前提下,可以维护一个记录当前联通分支数量的变量,在每次对并查集进行merge操作时减少1,在进行add操作时增加1,即可实现\(O(1)\)复杂度的查询。

最后一个问题是,如果使用了递归方法来维护并查集,那么在面对形如

1 -> 2 -> 3 ... -> n

这样的超长链时,在链长大约5000时会爆栈,因此需要用循环代替递归来维护并查集

bug分析

这次作业没有发现bug,互测房内也没有人成功刀中

第二次作业

性能分析

第二次作业中,时间复杂度可能较高的方法为查询一个group中的总value值、查询group年龄方差和求最小生成树的方法。如果不增加缓存,那么query group value sum指令带来的时间复杂度可以达到\(O(n^2)\)(n代表group中的人数),这是非常危险的,因此我在group中增加了一个value sum的属性,在每次增加人、减少人和增加关系的时候更新,这样在查询时就能实现\(O(1)\)复杂度,在其他时候均摊\(O(n)\)复杂度。

对于query group age var指令,我在group中维护一个ageSum属性,在每次增加和减少人员时更新,保存上一次算出的ageVar,并且设置一个类似脏位的属性,在每次有人员变动时置1,若脏位为0,则直接返回上次的计算结果,脏位为1时再重新\(O(n)\)计算年龄的平均值。

对于求最小生成树,我使用了Prim算法,并且在判断某条边链接的两点是否已经处于同一个联通分支时,沿用了第一次作业中实现的并查集算法。我将第一次作业中的并查集算法包装成了一个新的工具类,便于复用。

bug分析

本次作业没有在中测、强测、互测中被发现bug,但是在自测的时候发现了bug。在维护group value sum的时候,在add relation时没有更新group value sum。惊险的是,和我对拍的小伙伴也出现了这个问题,于是这个bug并没有在自己搭建的评测机中测出TAT,而是我在快到作业截止时间时,心血来潮浏览学长的总结博客时发现的。这也体现出对拍的一个缺点。多找些人对拍即可解决(bushi)

第三次作业

性能分析

第三次作业中可能比较耗时的指令是send indirect message指令,采用了堆优化的Dijkstra算法实现,复杂度为\(O(nlogn)\),在对于其他指令的处理上,时间复杂度不是大问题,反而在正确性上因为细节较多,比较容易出错。

bug分析

本次作业在自测、中测、强测、互测中均没有被发现bug。由于这次作业没有实现随机数据生成,因此手头的数据不多,没有hack出本房间的两个正确性bug。或许是因为偷懒看到成功率不高就没测

关于PPT中的Network拓展

假设出现了几种不同的Person

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

如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等 请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格(借鉴所总结的JML规格模式)


  • 新增Person的子类:

    • Advertiser:包含属性:商品列表
    • Producer:包含属性:可生产的产品列表
    • Customer:包含属性:偏好的产品列表
  • 新增Message的子类:

    • AdvertiseMessage:包含信息:商品信息,销售者
    • ProductMessage:包含信息:商品信息,生产者,广告者
    • PurchaseMessage:包含信息:商品信息,购买者,广告者
  • 新增Product类,属性包括:商品的id、名称,价格,销售额,销售路径、生产者

  • 新增核心功能的接口方法:

    • 生产产品 produce:

      • 生产者生产一个产品,加入自身的产品列表中
      • 生产者Producer向 广告者Advertiser发送一个 ProductMessage,包含的信息有产品信息,消息发送者,消息接收者
    • 发送广告advertise

      • Advertiser向所有与之有关的Person发送AdvertiseMessage,并且向所有包含此Advertiser的群发送AdvertiseMessage
    • 购买产品Purchase

      • 可包含在sendMessage
      • Costomer向关联的Advertiser发送购买信息,并将money转给Producer
        发送广告的JML
// Network.java
	
	/*@ public normal_behavior
      @ requires contains(advertiserId) && (getPerson(advertiserId) instanceof Advertiser);
      @ requires containsMessage(MessageId)
      @ assignable people[*].messages;
      @ ensures (\forall int i; 0 <= i && i <= people.length &&
      @ 	people[i].isLinked(getPerson(advertiserId)) && people[i].getId() != advertiserId;
      @ 	\old(people[i].messsages.length) == people[i].messsages.length - 1);
      @ ensures (\forall int i; 0 <= i && i <= people.length &&
      @ 	people[i].isLinked(getPerson(advertiserId)) && people[i].getId() != advertiserId; 
      @ 	(\forall int j; 0 <= j && j <= \old(people[i].messages.length);
      @ 	(\exists int k; 0 <= k && k <= people[i].messages.length; 
      @ 	people[i].messages[j] == people[i].messages[k])));
      @ ensures (\forall int i; 0 <= i && i <= people.length &&
      @ 	people[i].isLinked(getPerson(advertiserId)) && people[i].getId() != advertiserId; 
      @ 	(\forall int j; 0 <= j && j <= getPerson(advertiserId).messages.length;
      @ 	(\exists int k; 0 <= k && k <= people[i].messages.length; 
      @ 	getPerson(advertiserId).messages[j] == people[i].messages[k])));
      @ ensures  (\forall int i; 0 <= i && i <= people.length &&
      @ 	people[i].isLinked(getPerson(advertiserId)) && people[i].getId() != advertiserId; 
      @ 	(\forall int j; 0 <= j && j <= getPerson(advertiserId).messages.length;
      @ 	(\exists int k; 0 <= k && k <= people[i].messages.length; 
      @ 	getPerson(advertiserId).messages[j] == people[i].messages[k])));
      @ ensures ( (\forall int i; 0 <= i && i <= people.length &&
      @ 	people[i].isLinked(getPerson(advertiserId)) && people[i].getId() != advertiserId; 
      @ 	(\exists int j; 0 <= j && j <= people[i].messages.length; 
      @ 	getPerson(advertiserId).messages[j] == \old(getMessage(messageId))));)
      @ also
      @ public exceptional_behavior
      @ signals (PersonIdNotFoundException e) !contains(advertiserId);
      @ signals (AdvertiserIdNotFoundException e) contains(advertiserId) && 
      @ 	                                !(advertiserId) instanceof Advertiser);
      @ signals (MessageNotFoundException e) !containsMessage(messageId);
      @*/

	public void advertise(int advertiserId, int MessageId) throw PersonIdNotFoundException, AdvertiserIdNotFoundException, MessageIdNotFoundExceprion;

生产产品的JML:

    /*@ public normal_behavior
      @ requires contains(producerId) && (getPerson(producerId) instanceof Producer);
      @ assignable getProducer(producerId).product;
      @ ensures getProducer(producerId).product.length ==
      @         \old(getProducer(producerId).product.length) + 1;
      @ ensures for(int i; 0 <= i && i <= getProducer(producerId).product.length; \exist(getProducer(producerId).product[i] == product))
      @ also
      @ public exceptional_behavior
      @ signals (PersonIdNotFoundException e) !contains(producerId);
      @ signals (ClassTypeException e) !(getPerson(producerId) instanceof Producer);
      @*/
	public void produce(int producerId, Product product);

购买产品的JML

//仅包含sendMessage的新增功能
/* @ ensures (\old(getMessage(id)) instanceof PurchaseMessage) ==>
   @         (\old(getMessage(id)).getPerson1().getMoney() ==
   @         \old(getMessage(id).getPerson1().getMoney()) - ((PurchaseMessage)\old(getMessage(id))).getMoney() &&
   @         \old(getMessage(id)).getPerson2().getMoney() ==
   @         \old(getMessage(id).getPerson2().getMoney()) + ((PurchaseMessage)\old(getMessage(id))).getMoney());
   @ ensures (!(\old(getMessage(id)) instanceof PurchaseMessage) && !(\old(getMessage(id)) instanceof RedEnvelopeMessage) ) ==> (\not_assigned(people[*].money));
   @*/
	public void  void sendMessage(int id) throws
            RelationNotFoundException, MessageIdNotFoundException, PersonIdNotFoundException;

心得体会

本次作业极大程度的磨练了我的耐心。这次作业的实现细节繁多,需要足够的细心才能准确的完成,因此我不得不耐下心来,一行一行的阅读满屏的JML,虽然有时可以通过方法名猜测这个方法的功能,但是最终在检查时,还是需要不跳过的读完每一行,才能检查出有没有问题。

这也是我体会到的JML的优点,也就是传递设计要求的准确性,如果课程组不使用JML来传递设计细节,而是用自然语言来描述每个方法的功能,那一定会有很多没有传达清楚的地方。JML虽然细节多,比较长,但是它精准无误的传达了所有的设计要求,助教们回答同学们的问题时,大多可以直接回复:请参照JML。

至此全部互测已经结束,OO之旅正在接近尾声,希望在最后一个单元中也能够顺利完成任务!

特别鸣谢一起完成评测机的小伙伴:王钧石同学

posted @ 2022-06-02 21:28  Arthurinnng  阅读(79)  评论(0编辑  收藏  举报