OO第三单元实验总结报告
一.自测数据生成
本次作业中一大好处是不用考虑输入合法性,非法数据也有相应的异常来处理,这给了数据生成很大的便利,使得生成部分免去了合法性检验。我采用的策略是随机数据,我自己随便选了个比例来生成各个指令,对于复杂度较高的指令,我也专门写了针对该指令的生成数据。这样就对时间上也有了检验。
当然我们也会注意到纯粹的随机会导致生成一大堆只会抛出找不到异常的指令,这对我们全面的测试是不利的,我们可以利用分布来生成集中在中心的数据(这也会导致过多的重复异常,只能说需要找到一个平衡点吧)
def my_random(m: int, M: int, r=5) -> int:
def logistic(x):
return 1 / (1 + (math.exp(-r * (x))))
x = random.random() * 2 - 1
M = M + 1
k = (M - m) / (logistic(1) - logistic(-1))
return math.floor(k * (logistic(x) - logistic(-1)) + m)
但这种测试方法也要其不利之处,比如有关Group
大小不能超过1111的限制这一条件无法体现(因为按我的比例来生成的Group
大小期望不够大),这样如果没认真读JML的话就发现不了这个Bug。
二.图模型构建与维护
整个图的储存上我用了一个HashMap
来储存所有的边,这样可以\(O(1)\)来查询两点间是否有边、边权的大小。其他的信息我都用了一个以id
为键的HashMap
来存,这样我们使用时用的是Integer
,便于处理,输出时在用HashMap
找到相应对象即可。
先从简单的说起,Group
当中要维护一个点权与边权。点权部分我们维护和与平方和,从而可以直接计算出方差,这样添加、删除点都可以\(O(1)\)修改。边权和要在添加/删除点与添加边时都得维护。这样与点相关的是\(O(groupSize)\),加边则需要遍历Group
,对所有包含该边的Group
修改,是\(O(groupNum)\)
对于连通分支的查询,我们维护一个并查集与联通分支数,这样就可以利用并查集来达成\(O(1)\)的查询两点连通性,以及\(O(\alpha(V))\)的维护并查集。通过采用一个变量来储存来连通分支个数,我们得以\(O(1)\)的维护与修改来联通分支个数。
最小生成树我采用了Kruskal算法(这样就可以利用已经写好的并查集模板了),复杂度是\(O(Elog(E))\)。我们直接遍历将相应联通分支的点与边加入我们的最小生成树算法中,直接计算即可。
查询两点间的最短路我用的是Dijkstra算法,复杂度是\(O((E+V)logV)\)。遍历遍历时我们可以通过利用MyPerson
中的edges
来直接取出与之相连的边,而免去了遍历所有边造成的冗余。
与Message
相关的信息我们都储存在Person
当中,就按着JML的描述来维护与修改即可。
整个项目的架构就按着UML里面给的来就行了。
在此之上我添加了一些自己的工具类来便于使用最小生成树、并查集、最短路相关算法。
三.自我问题修复
虽说只用按照JML来写正确性就没有问题,采用上面所述的维护策略复杂度也是能满足测试要求的,但在实际上还是写出了一些问题。如第一部分所言,我搭了个数据生成器与我室友对拍,在这个过程中,我发现了许多自己写的Bug。
比较愚蠢的像是把Exception
的格式给复制粘贴错了之类的。第一次作业中对拍发现我们的qvs结果不一样。原因是我没有在添加边的时候维护这一值。第三次作业中我更是把Dijkstra板子抄错了,导致复杂度假了,对着自己代码瞪了好久才发现问题。
四.学习体会
这次作业代码写起来还是比较轻松愉快的,只要一开始就把为了复杂度而维护的中间变量都想到,这样写起来就不需要重构之类的,总体来说还是很顺的。架构已经确定了,写算法也比较简单与经典,结合起来就完成了。
说到JML,我们可以意识到一个好的规范性标准是十分重要的,这可以消除不确定性并同时使写出来的代码确保有效。
但在阅读本次代码的JML时也可以感受到它的许多问题,最大的是你所能使用的元素太少了,我不是很能理解把一切都用数组来描述会有什么优势所在,我只能看到这导致我们的JML规范又长又缺乏可读性,这还是我们实验中所提供的简单业务情况下。恐怕在更加复杂的情形下写出JML本身就很困难,同时也很折磨读它的人。
我是感觉用于描述的工具可以有更高层次的抽象的,就像数学一样,我们也不会都从集合论的公理开始推导(这样就变成《数学原理》了),通过一些公认的得到证明的中间层次的使用,我们可以极大的增加可读性,缩减长度,而与此同时又不失准确性。我感觉这样的一个抽象结构才是我们所需要的。当然,自然语言的抽象水平太高了,而它的二义性这导致它不堪其职,我们也许需要找到一个合适的中继点。
于此同时JML不对复杂度与实现方式进行限制,这恐怕也是略有却显得。
回到契约式编程本身上来,我认为这能极大程度的保证程序员的结果与业务需求的一致性,从而避免许多问题,这是很好的范式。我唯一的小小问题便是JML可能不是一个很好的契约书写者。
五.Network
扩展
假设出现了几种不同的Person
Advertiser
:持续向外发送产品广告Producer
:产品生产商,通过Advertiser来销售产品Customer
:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息Person
:吃瓜群众,不发广告,不买东西,不卖东西
为了拓展,我们添加如下的类AdvertisementMessage extends Message
来储存广告,其中还要包含一个Product
Product
包含有id
、money
等属性productList
是Network的属性,记录所有的产品preferredList
是Customer
的属性,它们表示了喜爱的产品。boughtList
也是Customer
的属性,表明他购买的产品
soldNumber()
/* @ public normal_behavior
@ requires (\exists int i; 0 <= i && i < productList.length; productList[i] == product)
@ assignable nothing
@ \result = (\sum int i; 0 <= i && i < people.length; (\sum int j; 0 <= j && j < people[i].boughtList && people[i].broughtList[j] == product; 1));
@ also
@ public exceptional_behavior
@ signals (ProductNotFoundException e) !(\exists int i; 0 <= i && i < productList.length; productList[i] == product)
@*/
public int soldNumber(/*@ non_null @*/ Product product) throws ProductNotFoundException
addProduct()
/* @ public normal_behavior
@ requires !(\exists int i; 0 <= i && i < productList.length; productList[i] == product);
@ assignable productList;
@ ensures productList.length == \old(productList.length) + 1;
@ ensures (\forall int i; 0 <= i && i < \old(productList.length);
@ (\exists int j; 0 <= j && j < productList.length; productList[j] == (\old(productList[i]))));
@ ensures (\exists int i; 0 <= i && i < productList.length; productListi] == product);
@ also
@ public exceptional_behavior
@ signals (EqualProductIdException e) (\exists int i; 0 <= i && i < productList.length;
@ productList[i].equals(product));
@*/
public void addProduct(/*@ non_null @*/Product product) throws EqualProductIdException;
setPreference()
/* @ public normal_behavior
@ requires contains(personId) && contains(product) && (\forall int i; i >= 0 && i < \old(getPerson(personId).perferredList).length; \old(getPerson(personId).perferredList)[i] != product);
@ assignable getPerson(personId).preferredList
@ ensures getPerson(personId).perferredList.length = \old(getPerson(personId).perferredList.length) + 1;
@ ensures (\forall int i; i >= 0 && i < \old(getPerson(personId).perferredList).length; \old(getPerson(personId).perferredList)[i] == getPerson(personId).preferredList[i]);
@ ensures getPerson(personId).perferredList[getPerson(personId).perferredList.length - 1] = profuct;
@ also
@ public normal_behavior
@ requires contains(personId) && contains(product) && (\exists int i; i >= 0 && i < getPerson(personId).perferredList.length; getPerson(personId).perferredList[i] == product);
@ assignable \nothing
@ also
@ public exceptional_behavior
@ signal (PersonIdNotFoundException e) !contains(personId);
@ signal (ProductNotFoundException e) contains(personId) && !contain(product);
@*/
public void setPreference(int personId, /*@ non_null @*/Product product) throws PersonIdNotFoundException, ProductNotFoundException