BUAA_OO_2022 第三单元总结

BUAA_OO_2022 第三单元总结

O.前言

本单元主要内容是学习理解JML语言,在此规格的限制下进行迭代开发,完成对特定社交网络的简单模拟。

一、架构与性能分析

1.1 UML类图

对于阅读jml开发,个人理解通俗来说就是”看注释“写代码,因此在这三次作业中,其实架构大致已经被助教们设计好了,我们要完成的就是填充细节,因此在这里就只放上最后一次作业的UML类图。

1.2 hw9

​ 其实最一开始接触到jml,我个人来讲是比较懵的,但是在通读一遍所给的文档后发现,这个单元的作业还是相对简单的,我们要做的主要是将jml翻译成现成的java代码,并保证一定的性能。(虽然说作业简单了,但是个人感觉实验就相对难了不少,毕竟作业更多的是在阅读理解jml,而实验就是要写一些jml,尤其是关于二叉树的一些jml描述,着实令人有些头疼不已)

而在第一次作业中,涉及到性能问题的主要指令就是:

  • qci:查询两个结点是否未于同一个连通分量(两个人是否存在联系)
  • qbs:查询图中有多少个连通分量(大致意思就是查询有多少个小圈子吧🤔)

毫无疑问,如果使用比较暴力的dfs进行深度搜索,qci、qbs都很容易会超时,因此我使用了并查集来进行优化,维护各个人所在集合的信息,大致实现如下:

public class DisjointSet {
    private HashMap<Integer, Integer> parents = new HashMap<>();
    private int branchNum = 0;

    public void insert(int id) {
        parents.put(id, id);
        branchNum++;
    }

    public int find(int id) {
        int now = id;
        int root;
        int father;
        while (true) {
            father = parents.get(now);
            if (father == now) {
                root = father;
                break;
            }
            now = father;
        }
        now = id;
        while (true) {
            father = parents.get(now);
            if (father == now) {
                break;
            }
            parents.replace(now, root);
            now = father;
        }
        return root;
    }

    public void clear() {
        parents.clear();
        branchNum = 0;
    }
    ...

}

每次插入节点,该节点的父节点都是自己。每次进行查找时,将进行路径压缩,将路径上所有节点的父节点都设置为根节点,以实现再次查询时速度的提升。

1.3 hw10

在本次作业中,涉及到了Group的相关操作,但是涉及到性能问题的主要还是MyNetWork中的相关方法,主要指令如下:

  • qgvs:查询小组中每个人的SocialValue总和
  • qgav:查询小组中年龄的方差
  • qlc:查询某个人距离另一个人的最近”距离”

首先对于qgvs命令,如果按照很直接的思路,每次查询都要遍历一遍小组内的人,超时的可能性是非常大的,因此我们可以通过维护MyGroup中的valueSum这一成员变量,实现查询时O(1)的复杂度。

具体实现主要包括,Group内添加或删除Person时对ValueSum进行加减。比较容易忽略的是,在addRelation时,也可能会影响到valueSum,因此对于每一个Person要知道自己所在的Group,在每次addRelation时要进行进行维护,具体实现如下:

public void addValueSum(Person person) {
    for (Integer id : groups.keySet()) {
        MyGroup myGroup = (MyGroup) groups.get(id);
        if (myGroup.hasPerson(person)) {
            myGroup.addValueSum(queryValue(person) * 2);
        }
    }
}

而对于qgav命令,做一个很简单的数学推导把平方式展开就可以,具体实现就不再多做赘述。

对于qlc命令,涉及到了我们数据结构中学习到的Prim、kruskal算法,二者主要区别在于Prim利用节点构造最小生成树,而kruskal则利用边来进行构造。在实现的过程中,我使用了kruskal算法,并利用了之前使用的并查集DisJionSet来进行了优化。

private int kruskal() {
    int foundNum = 0;
    Edge e;
    int point1;
    int point2;
    int sum = 0;
    for (int i = 0; foundNum < (hasFound.size() - 1) && i < list.size(); i++) {
        e = list.get(i);
        point1 = e.getPoint1();
        point2 = e.getPoint2();
        if (disjointSet.find(point1) != disjointSet.find(point2)) {
            foundNum++;
            disjointSet.link(point1, point2);
            sum += e.getWeight();
        }
    }
    return sum;
}

1.4 hw11

在本次作业中,性能主要涉及的命令只有sim,大致实现就是利用Dijsktra + 堆优化

但是我在实现时犯了一个较大错误就是我的关系图并没有维护,而且在实现kruskal算法时,我就新增了一个Map类,用来进行求解最小生成树,而在实现Dijsktra算法时,我又用了另一个DMap来实现(真是不知道当时怎么想的,服了)。并且由于我的DMap没有维护,每一次调用时,都要遍历NetWork中的所有Person重新建图,性能可想而知。

public class DMap {
    private PriorityQueue<Node> distances = new PriorityQueue();
    private HashMap<Edge, Integer> minDisSet = new HashMap<>();
    private static final int MAX_DISTANCE = Integer.MAX_VALUE;
    ....
}

public class Map {
    private LinkedList<Edge> list = new LinkedList<>();
    private DisjointSet disjointSet = new DisjointSet();
    private HashSet<Integer> hasFound = new HashSet<>();
    ....
}

回头看这两个图就像是都是匆匆忙忙建起,只是为了应付"一时之需"。个人认为改进成如下这样才可以更好。在每次添加或删除时及时维护,在进行求解最小生成树、最短路径时,部分深克隆建图。

public class Map {
	private HashMap<Person, Hashset<Edge>> edges = new HashMap<>();
	private HashMap<Person, Node> nodes = new HashMap<>();
	private DisjoinSet set;
	
	public addPerson(Person person) {
	 ...
	}
	
	public delPerson(Person person) {
	 ...
	}
	
	public link(Person p1, Person p2) {
	 ...
	}
	
	public kruskal(Person from) {
	 ...
	}
 	
 	public dijsktra(Person from, Person ) {
 	 ...
 	}
 	
}

二、测试与bug分析

测试部分

1.随机生成数据进行与同学进行对拍,既可以减少因理解jml有误而导致的bug,又相对节省时间,群策群力。

2.根据jml规格和性能限制手捏一些极端数据,比如group.size() < 1111, 又或者大量输入qci、大量输入qlc等。

3.使用Junit进行单元测试(实话实说用的比较少


bug

hw9:没有测出bug

hw10:因为queryReceivedMessages()实现打错了方法,返回了所有的messages

hw11:大规模重新建图 + 堆优化时使用PriorityQueue的方法太过丑陋被卡TLE了


总结

个人认为,多数人的bug(包括我自己)主要是出现在优化时,对某些中间变量的维护。最典型的就是qgvs创建中间变量,在addRelation时,忘记维护,或者没有维护好。同时阅读jml时粗心大意,也是容易犯的错误。

并且随机生成测试数据进行测试也有着一定弊端,比如有些时候数据量不够大,bug就难以显现(比如hw10中我的bug,由于随机性,一个人收到的Message并不足够多,这个bug就没显现出来,不过也主要是因为当时注意力主要集中在性能方面,没有做什么测试)

值得一提的是,在对拍的时候,我们三个人曾出现一模一样的bug(sim命令会先把Message发送出去,在判断返回值,导致返回-1时,Message也会被发送),幸亏发现的早,及时修改(不然就强寄了😕)

三、NetWork接口扩展

对于新增的Advertiser、Producer、Customer三类建模:

  • Advertiser:增加salesVolume字段以表示销售额,增加Producer字段表示合作的生产商,productType表示持有的商品的类型,productNum字段表示持有商品的数目
  • Producer:增加productNum字段表示生产的商品的数目,productType表示生产的商品的类型
  • Customer:增加preferenceType字段表示对商品的喜爱偏好,并增加subscription字段表示关注的订阅商,containsNum表示已购买的商品数量。

上述三者均继承于Person

而对于Advertisement继承于Message,其中新增advertiserId表示投放广告商的ID,customerId表示投放目标的ID

核心业务规格

sendAdvertisement:发送广告,顾客根据商品类型选择是否关注

  /*@ public normal_behavior
    @ requires containMessage(messageId) && getMessage(messageId) instance of Advertisment &&
    @           containAdvertiser(getMessage(messageId).getAdvertiserId()) &&
    @           containCustomer(getMessage(messageId).getCustomerId()) &&
    @           getAdvertiser(getMessage(messageId).getAdvertiserId()).getProductType == getCustomer(getMessage(messageId).getCustomerId()).getPreferenceType &&
    @           getCustomer(getMessage(messageId).getCustomerId()).isSubscribe() == false;
    @ assignable getCustomer(getMessage(messageId).getCustomerId()).subscrition;
    @ ensures getCustomer(getMessage(messageId).getCustomerId()).subscrition.size() == \old(getCustomer(getMessage(messageId).getCustomerId()).subscrition.size()) + 1;
    @ ensures (\exists int i; 0 <= i && i <= getCustomer(getMessage(messageId).getCustomerId()).subscrition.size();
    @            getCustomer(getMessage(messageId).getCustomerId()).subscrition[i] == getAdvertiser(getMessage(messageId).getAdvertiserId()));
    @ ensures (\forall int i; 0 <= i && i <= \old(getCustomer(getMessage(messageId).getCustomerId()).subscrition.size());
    @          (\exists int j; 0 <= j && j <= getCustomer(getMessage(messageId).getCustomerId()).subscrition.size());
    @          \old(getCustomer(getMessage(messageId).getCustomerId()).subscrition)[i] == getCustomer(getMessage(messageId).getCustomerId()).subscrition[j]);
    @ also
    @ public normal_behavior
    @ requires containMessage(messageId) && getMessage(messageId) instance of Advertisment &&
    @        containAdvertiser(getMessage(messageId).getAdvertiserId()) &&
    @        containCustomer(getMessage(messageId).getCustomerId()) &&
    @        getAdvertiser(getMessage(messageId).getAdvertiserId()).getProductType == getCustomer(getMessage(messageId).getCustomerId()).getPreferenceType &&
    @        getCustomer(getMessage(messageId).getCustomerId()).isSubscribe() == true;
    @ assignable \nothing;
    @ also
    @ public normal_behavior
    @ requires containMessage(messageId) && getMessage(messageId) instance of Advertisment &&
    @        containAdvertiser(getMessage(messageId).getAdvertiserId()) &&
    @        containCustomer(getMessage(messageId).getCustomerId()) &&
    @        getAdvertiser(getMessage(messageId).getAdvertiserId()).getProductType != getCustomer(getMessage(messageId).getCustomerId()).getPreferenceType;
    @ assignable \nothing;
    @ also
    @ public exceptional_behavior
    @ signals (MessageIdNotFoundExceprion e) !containMessage(messageId) ||
    @         !(getMessage(messageId) instance of Advertisment);
    @ also
    @ public exceptional_behavior
    @ signals (AdvertiserIdNotFoundExceprion e) containMessage(messageId) ||
    @         !(getMessage(messageId) instance of Advertisment) &&
    @         !containAdvertiser(getMessage(messageId).getAdvertiserId());
    @ also
    @ public exeptional_behavior
    @ signals (CustomerIdNotFoundExceprion e) containMessage(messageId) ||
    @         !(getMessage(messageId) instance of Advertisment) &&
    @         containAdvertiser(getMessage(messageId).getAdvertiserId()) &&
    @         !containCustomer(getMessage(messageId).getCustomerId());
    @*/
    public void sendAdvertiserment(int id) throws MessageIdNotFoundExceprion,AdvertiserIdNotFoundExceprion,CustomerIdNotFoundExceprion;

purchase:表示购买意向,从广告商获得product

  /*@ public normal_behavior
    @ requires containAdvertiser(advertiserId) && containCustomer(customerId) && getAdvertiser(advertiserId).hasGoods() == true;
    @ assignable getAdvertiser(advertiserId).saleVolume,getAdvertiser(advertiserId).productNum, getCustomer(customerId).containsNum, getCustomer(customerId).money;
    @ ensures getAdvertiser(advertiserId).saleVolume == \old(getAdvertiser(advertiserId).saleVolume) + getAdvertiser(advertiserId).getProductPrice();
    @ ensures getCustomer(customerId).containsNum == \old(getCustomer(customerId).containsNum) + 1;
    @ ensures getCustomer(customerId).money == \old(getCustomer(customerId).money) - getAdvertiser(advertiserId).getProductPrice();
    @ ensures getAdvertiser(advertiserId).productNum = \old(getAdvertiser(advertiserId).productNum) - 1;
    @ also
    @ public normal_behavior
    @ requires containAdvertiser(advertiserId) && containCustomer(customerId) && getAdvertiser(advertiserId).hasGoods() == false;
    @ assignable \nothing;
    @ also
    @ public exceptional_behavior
    @ signals (AdvertiserIdNotFoundExceprion e) !containAdvertiser(advertiserId);
    @ also
    @ public exceptional_behavior
    @ signals (CustomerIdNotFoundExceprion e) containAdvertiser(advertiserId) && !containCustomer(customerId);
    @*/
    public void purchase(int customerId, int advertiserId) throws AdvertiserIdNotFoundExceprion,CustomerIdNotFoundExceprion;

cooperate:达成合作后,生产商会给予广告商部分产品

/*@
  @ public normal_behavior
  @ requires containAdvertiser(advertiserId) && containProducer(producerId) && getAdvertiser(advertiser).isCooperate(producerId) == false;
  @ assignable getAdvertiser(advertiseId).productNum, getProducer(producerId).productNum, getAdvertiser(advertiseId).producer;
  @ ensures getAdvertiser(advertiseId).producer == getProducer(producerId);
  @ ensures (\old(getProducer(producerId).productNum) > 5) ==>
      (getProducer(producerId).productNum == \old(getProducer(producerId).productNum) - 5 &&getAdvertiser(advertiseId).productNum = 5);
  @ ensures (\old(getProducer(producerId).productNum) <= 5) ==>
      (getProducer(producerId).productNum == 0 && getAdvertiser(advertiseId).productNum == \old(getProducer(producerId).productNum));
  @ also
  @ public normal_behavior
  @ requires containAdvertiser(advertiserId) && containProducer(producerId) && getAdvertiser(advertiser).isCooperate(producerId) == true;
  @ also
  @ public exceptional_behavior
  @ signals (AdvertiserIdNotFoundExceprion e) !containAdvertiser(advertiserId);
  @ also
  @ public exceptional_behavior
  @ signals (AdvertiserIdNotFoundExceprion e) containAdvertiser(advertiserId) && !contains(producerId);
  @*/
  public void cooperate(int producerId, int advertiserId) throws AdvertiserIdNotFoundExceprion,ProducerIdNotFoundExceprion;

四、思考与感想

  1. 通过本单元的学习,我认识并理解了JML。站在个人角度来看,他似乎更像是强化版的注释,可以帮助我们更精确的理解规格,极大程度上避免二义性产生。同时JML也可以更好的将任务划分,架构层次设计和具体实现可以在一定程度上剥离,更好地让我们投入到不同的任务当中去。
  2. 第三单元的任务对于前两个单元简单一些,但这也导致了我在学习的过程中,没花很多心思,犯了不少低级错误,态度不够认真,必须警醒。
  3. 总的来说JML是一个很强大的工具,但是网上的资料似乎并不多,要找一些东西更多需要去外网上查询,中文搜索大多数时候都会搜到往年学长学姐们的OO博客作业,比较希望OO课程组能提供一些课外阅读资料(伸手党)
  4. 本单元也让我复习了一些数据结构的知识,让我深刻意识到数据结构的重要性,以后有空还是要在好好深入学习数据结构,不然就真的成”调包侠“了。
posted @ 2022-06-05 13:50  997ddler  阅读(73)  评论(0编辑  收藏  举报