其他分享
首页 > 其他分享> > BUAA OO 第三单元总结

BUAA OO 第三单元总结

作者:互联网

BUAA OO 第三单元总结

JML规格和测试

​ 本单元的主题是根据JML规格编写代码,架构设计方面只需要严格地按照JML规格即可。相应地,在测试方面也可以根据JML规格进行相对应的测试。课程组的建议是使用JUnit,但是在简单尝试后觉得时间成本比较高,加上舍友完成了对拍机,遂选择和舍友对拍进行主要测试,自测只针对边缘数据和效率问题进行测试。在自测的过程中,相比前两个单元的作业,有了JML规格,所有的情况其实都穷举了出来,所以在构造数据时可以参考的准则有:

图模型构建和维护策略

本单元的主题是实现一个简单的社交网络,在基本的图模型基础上,主要涉及到的图论知识有:连通分支的查询(两点是否连通以及连通分支数),最小生成树的查询,最短路的查询。这些方法的JML规格都比较复杂,如果严格按照JML规格实现存在效率问题,应当合理优化以确保方法复杂度都在\(O(n^2)\)以下。具体实现过程中主要是用了以下几个方法或者技巧,在下面的具体实现过程中会加以体现:

第一次作业

第一次作业开始需要对图的信息进行读入和存储,使用的容器基本为HashSet以及HashMap。例如:

private HashMap<Integer, Person> people;
private HashMap<Integer, Group>  groups;
//其中,Key为元素的id,value为元素

从而查询方法的复杂度就是\(O(1)\)。

第一次作业中可能存在效率问题的是查询连通分支数量的指令qbs,以及指令qgav。两条指令如果严格根据JML规格写的话复杂度为\(O(n^2)\),在互测部分可能会损失一定的分数。因此需要进行相应的优化

一个合理地解决方案是使用并查集,并维护一个blockSum变量,这里我使用的是按秩合并以及路径压缩版本的并查集。由于只有这一条指令需要用到,所以没有考虑那么多,直接将并查集内嵌在了MyNetwork内,使得整体的结构并没有那么明晰,在第二次作业中改进为了新建一个DisjoinSet类。

通过维护\(\sum{age^2}\)和\(\sum{age}\),再利用getAgeMean(),即可优化为\(O(1)\)复杂度。

第二次作业

第二次作业主要需要优化的方法是最小生成树查询方法(对应指令qlc)。我采取的策略是将并查集DisjoinSet单独建为类,同时新建Block类,使用基于并查集的kruskal算法,同时将最小生成树的值进行缓存。

具体来说,我在使用并查集的同时维护了网络中所有的连通块,也就是Block,其属性为:

private HashSet<Person> items = new HashSet<>();//连通块中的所有人
private Person father;//并查集中的头部变量,该连通块中所有人的father为同一个元素
private int leastValue;//查询最小生成树的结果
private int sign;//是否可以直接获取leastValue的标志

而在实际的查询过程中,首先取得Person对应的Block,如果sign为0,则直接获取结果,否则运用kruskal算法更新leastValue的值,再将sign置为0,并返回。在更新的过程中需要对边进行遍历,所以方法的复杂度为\(O(n)\)。

值得说明的一点是:并不是所有修改了Block的情况下都需要修改sign,当两个连通块由于一条边的添加而合并为一条连通块时,如果两个连通块的leastValue均可以直接获取,则合并连通块的leastValule可以直接用两个连通块的leastValue相加,再加上该边的值即可。这个可以稍稍提高一点效率。

第三次作业

第三次作业主要需要优化的方法是最短路的查询(对应指令sim)。我采用的策略是使用堆优化的dijkstra方法,具体的实现参照了网络上的教程,主要是用的容器是HashMapHashSet。实现过程中出了一个难以溯源的BUG,将在后续的性能问题及其修复部分讨论。

性能问题及其修复

得益于舍友高覆盖率的数据生成器,本单元的作业中测,强测,互测均未被发现BUG。但是在实现过程中,以及hack过程中发现的BUG依然值得探讨。

实现过程中的BUG

实现过程中的BUG主要集中在第三次作业中。

一个BUG是因为对JML理解产生偏差,认为一个EmojiId对应的MessageId是唯一的,从而造成BUG,这里就不细说了。另一个BUG就是堆优化的dijkstra实现过程中由于对堆理解不够深刻导致的BUG。

具体而言,我有问题的版本的实现如下:

private int dijkstra(Person person1, Person person2) {
    HashMap<Person, Integer> distance = new HashMap<>();
    HashSet<Person> visited = new HashSet<>();
    Comparator<Person> disComparator = Comparator.comparingInt(distance::get);
    PriorityQueue<Person> personPriorityQueue = new PriorityQueue<>(disComparator);
    distance.put(person1, 0);
    personPriorityQueue.add(person1);
    while (!personPriorityQueue.isEmpty()) {
        Person person = personPriorityQueue.poll();
        if (visited.contains(person)) {
            continue;
        }
        visited.add(person);
        for (Person p : ((MyPerson) person).getAcquaintanceValueMap().keySet()) {
            if (!visited.contains(p) && (!distance.containsKey(p) ||
                    distance.get(p) > distance.get(person) + person.queryValue(p))) {
                distance.put(p, distance.get(person) + person.queryValue(p));
                personPriorityQueue.add(p);
            }
        }
    }
    return distance.get(person2);
}

同一个Person会被反复添加到优先队列中,同时该Person在优先队列中的所有“副本”对应的距离都为同一个值(因为对距离的获取是基于一个HashMap(Person, int)实现的),这就会导致在Person被加入时,之前已加入优先队列中的Person的“副本”的距离都会被改变但是该优先队列只会调整一次,这就会导致小根堆的结构崩溃。具体可以参考以下的样例:

第三单元-堆的崩溃

解决方案就是新建一个Node类,属性为Person以及Distance,每次插入的都是新的节点,从而不会改变已在堆中的节点的值,堆的结构也就因此能够得以维持。

Hack过程中发现的BUG

本单元总共成功hack他人7次,都是CTLE的问题,使用的策略都是构造单一种类复杂指令的数据,例如qbs指令,qgvs指令等。修复的策略就是进行优化,具体可以参照本文图模型构建和维护策略 部分的内容。

对Network的扩展

要求

假设出现了几种不同的Person

如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等,请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格。

接口与方法设计

首先,将AdvertiserProducerCustomer设计为Person子接口,新增BuyingMessageAdertisementMessageMessage的子接口,新增Product类。

接口和类中新增方法定义如下:

Advertiser:
List<> getProducers()//返回合作的生产商
List<> getProducts()//返回推销的产品   
    
Producer:
Product getProduct()//产品

Product:
int getId()//产品ID
int getPrice()//产品价格(顾客)
int getProfit()//产品利润(生产商)
int getSales()//销售额
    
Customer:
List<> getAdvertisements()//返回收到的广告信息
List<> getPreferProducts()//返回希望入手的产品
    
AdvertisementMessage:
Advertiser getAdvertiser()
Customer getCustomer()
Product getProduct()
    
BuyingMessage:
//两个构造方法,分别对应顾客购买请求信息和广告商的转述信息
BuyingMessage(Customer,Advertiser)
BuyingMessage(Advertiser,Producer)
int getBuyingType()//返回购买消息类型
Product getProduct()//产品
int getNumber()//购买额 
    
Mynetwork:
Advertiser getAdvertiser(int id)
Customer getCustomer(int id)
Producer getProducer(int id)
Product getProduct(int id)
//相应的还应该由containsXXX()方法
void sendBuyingMessage(int messageid)//顾客向广告商发出购买请求或者广告商向生产商转述购买请求。
boolean ableToBuy(int customerId,int productId)//检查顾客是否能够购买产品,当且仅当顾客通过广告商向对应的生产商发送了购买请求时返回True。
boolean buyProduct(int customerId,int productId,int number)//顾客购买产品,如果顾客ID不存在,抛出异常,如果产品ID不存在,抛出异常,如果对应Product不在顾客偏好产品中,抛出异常。
void sendAdvertisement(int messageId)//投递广告 
void addPreferProduct(int customerId,int productId)//添加偏好产品
int queryProductSales(int productId)//查询产品销售额
List<> queryProductSalePath(int productId)//查询销售路径

JML规格撰写

学习体会和收获

标签:OO,int,BUAA,Person,规格,getMessage,JML,id,单元
来源: https://www.cnblogs.com/Longxmas/p/16341933.html