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的数量。在并查集上,我也是用压缩路径来减少了访问的次数。

原路径:

graph TD A[节点A]-->B[节点B] B-->C[节点C] C-->D[节点D] D-->E[节点E] E-->F[节点F]

转化为:

graph TD A[节点A]-->F[节点F] B[节点B]-->F C[节点C]-->F D[节点D]-->F E[节点E]-->F

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语言描述一遍功能,无疑可以减少协作的难度,总的来看,是一门很有用的技术。

posted on 2022-06-03 20:11  计组战力单位  阅读(21)  评论(0编辑  收藏  举报