OO第三单元总结
一、自测策略
本次实验最开始是想使用第九次ppt上的openjml
来进行测试,但是这个方法实在是太“先进”了,网上几乎找不到他相关的任何资料(除了我们学校大佬写的博客😭),不是我这种人能够掌握的。所以我的目光移向了第二种方法,即课程组介绍的工具Junit
,但是这个方法的数据与答案都需要自己构造,(我觉得这个方法检测bug的前提是已经找到了bug),而且无法支持大规模的测试,最后还是利用数据生产器与对拍器来检测错误。在数据生成器中,在每次都会生产大量必要数据的前提下,支持对于不同指令的泛式或集中式测量。
while i < 10000:
number = random.randint(0, 14)
#number = 8
if i < 50:
number = 8
else:
number = 14
if number == 0:
f.write("qci " + str(random.randint(0, numof_person)) + " " +
str(random.randint(0, numof_person)) + "\n")
i += 1
elif number == 1:
f.write("qbs\n")
i += 1
..........
通过与同学的代码进行对拍来检测错误,并返回此次总共运行的时间,方便进行代码的优化。
import os
import time
if __name__ == "__main__":
for i in range(1):
os.system("python testDijkstra.py")
now = time.time()
os.system("java -jar jar1.jar < testPoints.txt > output1.txt")
print(time.time() - now)
now = time.time()
os.system("java -jar jar2.jar < testPoints.txt > output2.txt")
print(time.time() - now)
os.system("fc output1.txt output2.txt > result.txt")
二、图模型构建和维护策略
图模型的构建:本次作业中是把Person当作点,Person与Person之间的value当作权重来构建带权无向图。
维护策略:由于在互测时,通常会构建复杂度高的数据来卡爆时间,所以在写算法是应该尽量使用维护发而不是计算法。在本单元作业中需要用维护的指令有:
qbs:
这一条指令要着重了解指令的真正含义:按照JML
的描述复杂度是O(n^2),但实际的含义是一共有多少个"Block"
,通过维护可以将复杂度降至O(1)。
qlc:
求最小生成树,可以通过维护来降低复杂度,但是这条指令被限制在了20条以内,不会产生太大的影响。
qgvs:
平平无奇的指令,但是按照JML
的描述复杂度是O(n^2),需要用维护的方法将复杂度降至O(n)。
维护的方法:
是否处于同一连通图:加入(删除)Person(value)时,利用并查集对队列信息进行更新,在调用时,直接从队列中取出。
最小生成树:先将该连通图中的边按照从大到小的顺寻排列,然后利用Kruskal算法,值得一提的时类的创建与删除是一个很费时间的操作,如果可以应该尽量重复利用,来减少cpu运行花费的总时间。
单源最短路径:利用Dijkstra算法,但使得注意的是,利用java内置的PriorityQueue来实现对元素的大小排序(在不知道这个容器时,自己写了一个功能类似的容器,但因为有太多的创建与删除,性能上远不及java内置的容器),将复杂度从O(n^2)降到O(n)。
三、性能问题和修复情况
1)第九次次作业
在本次作业中,我的强测用于初心大意wa掉了一个点,并且在queryBlockSum与并查集的性能上没有做的优化。优化方案:1)在queryBlockSum中,我原本是按照JM规格的要求,写了O(n^2)复杂度的表达式,我改良其为维护一个常数,即只需判断此时的并查集中根节点的数量(fa[i]==i),就能获得此时block的数量。在并查集上,我也是用压缩路径来减少了访问的次数。
原路径:
转化为:
2)第十次作业
本次作业在强测与互测中均为被检测处bug,但在自己测试时却出现了一个令人哭笑不得的bug,对我就有教育意义,bug始于上一次作业(由于第九次作业并未测试所有的指令)。JML的规格描述如下:
/*@ 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();
我的写法是:
@Override
public int getValueSum() {
int sum = 0;
for (int i = 0; i < people.size(); i++) {
for (int j = 0; j < people.size() && people.get(i).isLinked(people.get(j)); j++) {
sum += people.get(i).queryValue(people.get(j));
}
}
return sum;
}
看是何描述一模一样,实际上意义却完全不同。这告诉我,写的时候要动脑子。同时,这样O(n^2)的指令并不能满足互测的需求,最后改为维护一个常数来将指令的复杂度降为O(1)。
3)第十一次作业
本次作业在强测与互测中均为被检测处bug,但我着重于性能上的优化,对于我的程序而言,有两个指令的耗时远远超过其他两个指令,第一个便是qlc,这个指令时所有指令中耗时最大的一条指令,但幸运的是,本条指令的次数被限定为20条,无论是否进行优化都不会产生致命的影响。第二个就是sim,这个指令关乎着此次是否会超时,首先,按照常规的写法,很容易就会被卡爆。最开始,我是使用自己定义的容器去优化,但后来发现java自带的PriorityQueue可以实现自动排序,拥有比自己容器更高的效率。
四、扩展Network,撰写JML规格
假设出现了几种不同的Person
- Advertiser:持续向外发送产品广告
- Producer:产品生产商,通过Advertiser来销售产品
- Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
- Person:吃瓜群众,不发广告,不买东西,不卖东西
如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等 请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格(借鉴所总结的JML规格模式)
显然Advertiser,Producer,Customer都是继承自Person的子类。
同时,商品时一种特殊的消息,经由Advertiser转发给Customer
查询销售额:
/*@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < messages.length; messages[i].getId() == id && messages[i] instanceof Product)&&
@ (\exists int j; 0 <= j && j < people.length; people[j] instanceof Producer &&
@ people[j].getId() == ((Product)messages[i]).getProducer);
@ assignable \nothing
@ ensures \result == getMessage(id).getProducer().getSale();
@ also
@ signals (MessageIdNotFoundException e) !(\exists int i; 0 <= i && i < messages.length; messages[i].getId() == id
@ && messages[i] instanceof Product)
@ signals (EqualPersonIdException e) (\exists int i; 0 <= i && i < messages.length; messages[i].getId() == id
@ && messages[i] instanceof Product)&&
@ !(\exists int j; 0 <= j && j < people.length; people[j] instanceof Producer &&
@ people[j].getId() == ((Product)messages[i]).getProducer);
@*/
public /*@ pure @*/int queryProductValue(int id);
查询销售路径
/*@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < messages.length; messages[i].getId() == id && messages[i] instanceof Product)&&
@ (\exists int j; 0 <= j && j < people.length; people[j] instanceof Producer &&
@ people[j].getId() == ((Product)messages[i]).getProducer);
@ assignable \nothing
@ ensures \result == getMessage(id).getProducer().getAdvertisers();
@ also
@ public exceptional_behavior
@ signals (MessageIdNotFoundException e) !(\exists int i; 0 <= i && i < messages.length; messages[i].getId() == id
@ && messages[i] instanceof Product)
@ signals (EqualPersonIdException e) (\exists int i; 0 <= i && i < messages.length; messages[i].getId() == id
@ && messages[i] instanceof Product)&&
@ !(\exists int j; 0 <= j && j < people.length; people[j] instanceof Producer &&
@ people[j].getId() == ((Product)messages[i]).getProducer);
@*/
public /*@ pure @*/List<Advertiser> queryProductPath(int id);
购买商品
/*@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < \old(messages).length; \old(messages)[i].getId() == id1
&& \old(messages)[i] instanceof Product)&&
@ (\exists int j; 0 <= j && j < people.length; people[j] instanceof Producer &&
@ people[j].getId() == ((Product)messages[i]).getProducer)&&
@ (\exists int k; 0 <= k && k < people.length; people[k].getId() == id2 && people[k] instanceof Customer);
@ assignable people, messages
@ ensures getPerson(id2).money == \old(getPerson(id2).money)-\((Product)old(getmessage(id1))).getMoney;
@ ensures (\forall int i; 0 <= i && i < \old(people.length) && people[i].getId != id2;
@ (\exists int j; 0 <= j && j < people.length; people[j] == (\old(people[i]))));
@ ensures getMessage(id).getProducer() = \old(getMessage(id).getProducer()) + \((Product)old(getmessage(id1))).getMoney;
@ ensures (\forall int i; 0 <= i && i < \old(messages.length) && messages[i].getId != id1;
@ (\exists int j; 0 <= j && j < messages.length; messages[j] == (\old(messages[i]))));
@ public exceptional_behavior
@ signals (MessageIdNotFoundException e) !(\exists int i; 0 <= i && i < messages.length; messages[i].getId() == id1
@ && messages[i] instanceof Product)
@ signals (EqualPersonIdException e) (\exists int i; 0 <= i && i < messages.length; messages[i].getId() == id
@ && messages[i] instanceof Product)&&
@ !(\exists int j; 0 <= j && j < people.length; people[j] instanceof Producer &&
@ people[j].getId() == ((Product)messages[i]).getProducer);
@ signals (EqualPersonIdException e) (\exists int i; 0 <= i && i < messages.length; messages[i].getId() == id
@ && messages[i] instanceof Product)&&
@ (\exists int j; 0 <= j && j < people.length; people[j] instanceof Producer &&
@ people[j].getId() == ((Product)messages[i]).getProducer)&&
@ !(\exists int k; 0 <= k && k < people.length; people[k].getId() == id2 && people[k] instanceof Customer);
@*/
public void buyProduct(int id1, int id2);
五、学习体会
经过本单元的学习,我学会了使用JML规格来描述代码实现的基本要求。总的来说,JML语言与java语言有着明显的相同之处,总所周知,JML适用于描述程序实现的要求的,即在开始编程前用JML先表述一次要求。其实在本单元刚开始时,我不是很理解JML的作用,我当时认为,既然你都开始用语言规范化描述了,还不如直接用java写,但在后来的编程练习中,我渐渐的感受到了JML语言的好处,举个简单的例子,JML语言不需要考虑方法的复杂度,只需要描述时保障正确性即可,具体实现方法交给实现的人去做,在多人合作编程时,先用JML语言描述一遍功能,无疑可以减少协作的难度,总的来看,是一门很有用的技术。