返回顶部

BUAA_OO 第三单元总结——JML

BUAA_OO 第三单元总结——JML

第三次作业的目的是建立一个社交通信网络,通过JML帮助大家理解并且掌握JML有关内容。说一说一,这单元为了让我们认识到JML,图论的内容还是相当多的,如果我们按照正常JML的规格去写代码的话,最后的结果一定是超市的,这个也是学习JML的一部分,理解起来JML还是相当费劲的。当然最后因为偷懒导致有的地方还是超时了。

从JML角度构造测试数据

在测试方面,我在第一次作业的时候尝试去junit去对于每个方法分别进行测试,其实测试方法很简单,主要根据对应的方法设置对应的网络图,然后进行简单的测试,然后就翻车了

翻车的原因很简单,抛出异常错误的时候没有写换行符,然后强测直接寄了。当然我也不知道为什么弱测没有测出来,而且也不知道自己为什么看到没换行还没有反映出了点什么,总之真的有点崩溃。

当然这个zz错误暂且不提,在后来的测试中,我逐渐发现了junit这个测试方法具有相当大的局限性,那就是测试样例内容构造的难度,当具有大量数据的时候,该方法就无法测出一些比如说数据越界或者并查集错误等问题,因此并不适用,最后我还是回到了数据生成器+对拍的方法。

但是最后的数据生成器还是需要与JML联系起来,那就是分部进行方法测试。这样的测试的原因或者好处便是在于数据生成的不确定性,无论是添加人还是关系,由于数据生成的不确定性,因此覆盖率很难达到,所以需要的方法是限定范围内大量构造数据,这里变我用了c++的方法构造。

范围限定使用了随机数的方法

mt19937 mt(time(0));
uniform_int_distribution<int> fop(1,2),op(1,9),V(1,1000),val(1,500),type(0,1);

首先大量生成基础网络

	while(t--) {
		int nowfop=fop(mt);
		switch(nowfop) {
			case 1: {
				ap();
				break;
			}
			case 2: {
				ar();
				break;
			}
			case 3: {
				ag();
				break;
			}
			case 4: {
				atg();
				break;
			}
		}
	}

然后在对于每个方法单独随机生成数据

为什么不测所有的方法:因为如果想保证构造方法时随机生成的数据能连接到网络,就需要大量的数据生成,如果每个方法都这样,就有点太卡了。而另一个好处是,这样测试能把错误精确到定位到具体方法上。

JML的另一个好处在于能看出方法的复杂度,方便进行优化。

例如GroupgetValueSum方法:

/*@ ensures \result == (\sum int i; 0 <= i && i < people.length; 
      @          (\sum int j; 0 <= j && j < people.length && 
      @           people[i].isLinked(people[j]); people[i].queryValue(people[j])));
      @*/
public /*@ pure @*/ int getValueSum();

我们能从JML中直观的读取到他所要表示的内容,但是在另一方面,我们又不能用它所写的内容直接进行表示,这就是JML语言魅力之处:它能直观地帮助我们获取代码的写法,但是有需要我们开动脑筋,将方法构造最优化。

图模型构建和维护策略

其实无论是怎么优化,优化的方法都是把O(N^2)乃至更高复杂度的算法优化为O(n)甚至O(1)的算法

第九次作业

并查集的使用qci

isCircle函数中,为了降低复杂度,实现了一个并查集,并查集的维护在addRelation方法中

而我看大多数人用的都是通过find的递归方法来进行查询和处理,这样做的方法确实很简单。我最开始也是这么写的,但是后来我在查Kruskal算法的时候发现其实还可以不用find方法,即把每次更新就把所有相连的节点指向同一个节点,因为需要遍历,这样的时间复杂度确实会是上升,但是还是O(n),而因此最后使用了这个方法。

看着也挺方便点

容器的选择:hashMap的使用

按照我的想,如何把复杂度降下去,那就是用hashmap,它的查询时间复杂度是(logN),因此极大地降低了查找的时间,因为有好多的方法需要遍历然后查询,这样如果是数组遍历无疑时间复杂度会上升。

第十次作业

Kruskal算法qlc

这个算法其实没啥好说的,我是自己生成了一个tree类来进行点和边的组合,然后其中调用了compareTo先进行了排序,然后就是正常的计算了。

阴险狡诈的qgvs

这个互测可把我给淦烂了,之前我知道这是O(N^2)复杂度,但是在测试之前我确实没有想到什么好的方法去降低,互测时候想起来了。。。

如何降低人与人之间的关系价值,把他降低复杂度,最好的方法当然还是在这个Group内就把每个人与这个组里面的每个人的关系价值先计算出来,这里用HashMap表示这里面的影响因素在于三点:

  • Group增加新人
  • Group删除新人
  • 添加关系addRelation

因此只要在三个地方注意更新就好了。然后就通过遍历HashMap就可以得到最后的结果。

public int getValueSum() {
        int sum = 0;
        for (Person person : people) {
            sum += values.get(person);
        }
        return sum;
}

第十一次作业

Dijkstra算法

在第十一次作业中,有一个间接的发送消息,会进行最短路径搜寻的工作,因此需要用Dijkstra算法进行搜索,但是这里面存在的问题是:正常的Dijkstra算法时间复杂度是O(N^2),因此需要使用堆优化。

java的堆优化还是比较方便的,这个PriorityQueue类是真的神奇,他会直接对队里面的数据进行排序,形成小顶堆,因此时间复杂度就降低为O(nlogn)了,它不用我们再额外新建堆优化的容器,因此实现起来也要轻松许多。

维护过程和bug修复

其实对于本单元的设计,维护过程就是一个bug修复的过程,我所有的错误都在时间复杂度上(除了那个特别nt的),因此维护过程也就是我一步步对于JML的理解更加深刻的过程,如何为维护各种方法,理解JML的语言的实际算法的不同,这就是一个逐步了解JML并学习的过程。

Network扩展

出现了几种不同的Person

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

首先,由于这三种新的身份仍然是Person,因此扩展直接定义三个类去继承Person

  • Advertiser发送到广告可以看作是一个Message,本身带有Message
  • Customer有一个偏好的属性preference
  • Message增加两个信息,AdvertiseMessage(type = 2)和SaleMessage(type = 3)分别代表广告信息和购买信息

NetWork增加的方法为:containsAdvertiseMessage,getAdvertiseMessageaddAdvertiseMessagesendAdvertiseMessagecontainsSaleMessagegetSaleMessageaddSaleMessagesendSaleMessage

是否有广告信息

//@ ensures \result == (\exists int i; 0 <= i && i < messages.length && message.getType() == 3; messages[i].getId() == id);
public /*@ pure @*/ boolean containsAdvertiseMessage(int id);

是否有销售信息

//@ ensures \result == (\exists int i; 0 <= i && i < messages.length && message.getType() == 4; messages[i].getId() == id);
public /*@ pure @*/ boolean containsSaleMessage(int id);

获得广告信息

/*@ public normal_behavior
      @ requires containsAdvertiseMessage(id);
      @ ensures (\exists int i; 0 <= i && i < messages.length; messages[i].getId() == id &&
      @         \result == messages[i]);
      @ public normal_behavior
      @ requires !containsAdvertiseMessage(id);
      @ ensures \result == null;
      @*/
    public /*@ pure @*/ Message getAdvertiseMessage(int id);

获得销售信息

/*@ public normal_behavior
      @ requires containsSaleMessage(id);
      @ ensures (\exists int i; 0 <= i && i < messages.length; messages[i].getId() == id &&
      @         \result == messages[i]);
      @ public normal_behavior
      @ requires !containsSaleMessage(id);
      @ ensures \result == null;
      @*/
public /*@ pure @*/ Message getAdvertiseMessage(int id);

发送广告

/*@ public normal_behavior
      @ requires containsAdvertiseMessage(id);
      @ assignable advertiseMessages,productMessages;
      @ ensures !containsAdvertiseMessage(id) && advertiseMessages.length == \old(advertiseMessages.length) - 1 &&
      @		(\forall int i; 0 <= i && i < \old(advertiseMessages.length) && \old(advertiseMessages[i].getId()) != id;
      @ ensures productMessages.length == \old(productMessages.length)+1;
      @ (\forall int i; 0 <= i && i < \old(productMessages.length);
  	  @          (\exists int j; 0 <= j && j < productMessages.length; productMessages[j].getId() == \old(productMessages[i].getId());
  	  @ also
  	  @ public exception_behavior
  	  @ signalonly (AdvertiseMessageNotFound e) !containsAdvertiseMessage(id);
public void sendAdvertiseMessage(int id);

学习体会

这次课程虽然看起来相对于前面的课程任务较小,但是实现难度还有存在的,而且,由于我自己的疏忽或者说知识漏洞,错误的地方比之前还有多少。对于我而言,JML规格无疑仍然是一个不小的挑战,一方面JML的写法复杂度与实际有出入需要化简,另一方面对于想最短路径等图JML的理解仍然是一个不小的挑战。从JML撰写的角度,客观并且全面的评价实现的功能,并且用一种约束化的语言来描述,是一个很困难的事情,也是一个很规范的问题。回顾这一单元的内容,我的同学们无疑给了我很大的帮助,让我能想起来我已经忘记的图论。(忘记之后写算法真是一件折磨人的事情啊)。

posted @ 2022-05-31 22:08  wpy的小黑屋  阅读(41)  评论(0编辑  收藏  举报