2022_BUAA_OO 第三单元总结

2022_BUAA_OO 第三单元总结

一、设计架构

本单元的架构几乎不需要设计,就是按照官方包接口以及其中的JML逐步实现作业中用到了类和异常类。只是为了便于一些图论相关算法,我又添加了边节点类、实现了并查集的数据结构,第二三次作业需要实现的最小生成树以及最短路径的实现由于不需要复用,我都塞进了MyNetwork类里(刚好499行。。。)同时为了缩短对元素的查询时间,作业中的存储结构几乎都是使用的HashMap以及HashSet

二、性能分析

本单元的三次作业中,每种指令执行的时间复杂度的底线几乎都是O(n^2),否则很难保证在对该指令的集中测试下全身而退,而本单元作业中对于时间复杂度问题的考虑主要集中在以下几个方面:

存储容器

由于对于各种类(Person、Group、Message等),id是其唯一标识符,从而会涉及到大量的通过id查找的过程,使用HashMapHashSet存取可以保证其复杂度在O(1)

并查集

设置并查集,由于其在查找过程中不断压缩路径,可以使递归的深度接近于1,qci指令的时间复杂度也接近于1。同时可以根据根节点是自己的节点个数判断块的数量,在查找合并过程中可以维护一个块的数量,从而使得qbs的时间复杂度也接近于1。在合并时也可以考虑根据两个块的大小,让小块的根节点指向大块的根节点,也能提升一定的性能。

最小生成树

在完成第二次作业时,我只是简单的实现了一下prim算法,在自行做测试时主要进行了对于该指令的正确性的测试,而在运行时间上虽然与对拍的同学相比较慢,但是由于测试数据没有对于该指令进行集中测试,我没有仔细检查实现的代码,然后强测CTLE了一个点。最后检查发现是我在实现的循环中用了一步没啥用的ArrayList的contains方法,查看源码果然是O(n)的复杂度。。。所以计算复杂度时要将调用关系考虑全面,特别是对于一些常用的自带方法。

最短路径

看了学长学姐的博客选用了堆优化的Dijkstra算法,复杂度为O((m+n)log n)(m为边数,n为点数),利用优先队列简化了查找距离最小节点的操作。我在实现过程中犯了一个很傻的错误,就是在更新节点到其他节点的距离时,没有遍历该Person的acquaintance而是去遍历了所有未被访问的节点,导致优化了寂寞,费了一番功夫才找出问题。

查询遍历

这个点看似简单,实则是本单元出bug相当多的一个地方,如作业中对于关系权重、年龄、年龄平方的求和,如果只是按照JML给出的逻辑实现,方法复杂度一般在O(n^2),很容易TLE,这需要我们维护变量来代替查询时的遍历,维护变量也需要注意更新变量的时机,如Group的valueSum在向组里加人、减人以及添加关系时都要判断是否需要更新。

三、测试策略

本单元指导书中推荐的测试方法是JUnit测试。在尝试了一下后,发现JUnit仅能够自动生成的测试代码的函数名,想要正确运行,主题部分还是需要自行编写,需要自己根据对JMl规格的理解,断言设定正确结果,而我在自测之初最急切知道的是自己对于JML规格的理解是否有谬误,所以最后的测试手段还是自己有针对性的进行了一些数据测试,以及求助与有随机数据评测机的给力小伙伴来进行的。

四、拓展NetWork

设计需求

假设出现了几种不同的Person

  • Advertiser:持续向外发送产品广告

  • Producer:产品生产商,通过Advertiser来销售产品

  • Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息

  • Person:吃瓜群众,不发广告,不买东西,不卖东西

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


设计如下:

  • Person子类:

    • Advertiser:商品列表

    • Producer:产品列表

    • Customer:偏好商品列表

  • 新增Product类:包括id、名称、价格、生产者、销售额、销售价格

  • Message子类:

    • AdMessage:商品信息、销售者

    • ProductMessage:商品信息、生产者、推广者

    • OrderMessage:商品信息、订购者、推广者

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

    • 发送广告advertise:

      • Advertiser向所有关联的Person以及所有包含此人的Group发送AdMessage

    • 生产产品produce:

      • 新建一个Product实例放入Products中,同时加入Producer的产品列表,同时向Advertiser发送一个ProductMessage

    • 查询商品销售额getSale:

      • 返回某商品的总的销售额

发送广告:

    /*@ public normal_behavior
     @ requires contains(aderId) && (getPerson(aderId) instanceof Advertiser);
     @ assignable people[*].messages;
     @ ensures (\forall int i; 0 <= i && i <= people.length &&
     @ people[i].isLinked(getPerson(aderId)) && people[i].getId() != aderId;
     @ (\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(adId)) && people[i].getId() != adId;
     @ \old(people[i].messsages.length) == people[i].messsages.length - 1);
     @ ensures (\forall int i; 0 <= i && i <= people.length &&
     @ people[i].isLinked(getPerson(aderId)) && people[i].getId() != aderId;
     @ (\forall int j; 0 <= j && j <= getPerson(aderId).messages.length;
     @ (\exists int k; 0 <= k && k <= people[i].messages.length;
     @ getPerson(aderId).messages[j] == people[i].messages[k])));
     @ also
     @ public exceptional_behavior
     @ signals (PersonIdNotFoundException e) !contains(aderId);
     @ signals (AdvertiserIdNotFoundException e) contains(aderId) &&
     @                               !(getPerson(aderId) instanceof Advertiser);
     @*/
public void advertise(int aderId) throw
      PersonIdNotFoundException, AdvertiserIdNotFoundException;

生产商品:

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

查询销售额

    /*@ public normal_behavior
 @ reauires (\exists int i; 0 <= i && i < products.length; products[i].getId == producId);
 @ ensures (\exists int i; 0 <= i && i < products.length; products[i].getId == producId) && \result == products[i].getSale;
 @ also
     @ public exceptional_behavior
     @ signal_only ProDuctIdNotFoundException;
 @ */
public int getSale(int productId) throw ProDuctIdNotFoundException;

五、学习体会

相较于前两单元的作业,本单元作业在设计方面消耗的时间要显著减少,在JML规格的限制下,同学们几乎不用太过在意结果的正确性,而更多关注在算法的时间复杂度上。

当然本单元作业也对于我们的心态和耐心有着较强的磨炼作用,毕竟要从长长的JML语言间看出方法两个字还是相当费神的,因为它会将自然语言中许多隐含条件再用较大篇幅描述出来。但是JML的优点也较为显著,它可以精准地传达所有的设计要求,能明确地传达出一些自然语言难以传递的设计细节,避免了二义性。

posted @ 2022-06-03 17:54  luiluizi  阅读(22)  评论(0编辑  收藏  举报