OO2021-第三单元作业总结

Posted BUAA-Panzer

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OO2021-第三单元作业总结相关的知识,希望对你有一定的参考价值。

OO2021-第三单元作业总结

 

(1)总结分析自己实现规格所采取的设计策略

这里选取一些比较有代表性的方法,简单介绍一下我的设计策略,较为详细的方法性能分析在第(4)部分。

Network类

isCircle:

第一次作业时,采用dfs深度优先搜索的方式完成。

第二、三次作业时,改为并查集的方式实现,在addPerson时对映射关系进行初始化,在addRelation时对映射关系进行更改维护。

deleteColdEmoji:

通过HashMap的removeIf()方法实现对emojiId、emojiHeat的维护。

通过Iterator实现对messages的维护。

sendIndirectMessage:

主要难点——最短路径通过迪杰斯特拉算法计算,通过添加private void myDijkstra(int s, int n)方法实现堆优化的迪杰斯特拉算法,可以计算出起点到终点的最短距离。

Group类

getValueSumgetAgeMeangetAgeVar:

建立三个私有属性int:totalValuetotalAgetotalAge2,在MyGroup加入人、删除人以及MyNetwork加入关系时对相关的属性进行维护。调用方法时,利用三个int值结合this.people.size()进行简单计算即可。

Person类、Message类、RedEnvelopeMessage类、NoticeMessage类、EmojiMessage类

实现3个具体消息类的时候,直接复制粘贴课程网站上的规格指示,写的是:

public class MyRedEnvelopeMessage implements RedEnvelopeMessage {
······
}

结果IDEA提示说要实现接口的具体方法,想到RedEnvelopeMessage继承自Message,所以就改为:

public class MyRedEnvelopeMessage extends MyMessage implements RedEnvelopeMessage {
······
}

就可以解决问题。(这一点本身不值一提,只是当时确实产生了一点点困扰,所以写在博客里)这几个类的关系如下图所示:

 

规格的具体实现较为简单,不再赘述。

异常类

包括PersonIdNotFoundExceptionEqualPersonIdExceptionRelationNotFoundExceptionEqualRelationExceptionGroupIdNotFoundExceptionEqualGroupIdExceptionEqualMessageIdExceptionMessageIdNotFoundExceptionEmojiIdNotFoundExceptionEqualEmojiIdException

所有的异常类的具体实现大同小异,我的思路是建立TotalCounter类,对总异常数进行统计,并用HashMap对各个id导致的异常进行统计:

import java.util.HashMap;

public class TotalCounter {
   private int total;
   private HashMap<Integer, Integer> line;
   
   public TotalCounter() {
       this.total = 0;
       this.line = new HashMap<>();
  }
   
   public void setTotal(int in) {
       this.total = in;
  }
   
   public int getTotal() {
       return this.total;
  }
   
   public HashMap<Integer, Integer> getLine() {
       return this.line;
  }
}

然后,实现各个具体异常类时,建立静态TotalCounter对象(private static TotalCounter counter = new TotalCounter();),即可进行异常抛出的累计统计。注意print()的文字正确性。

 

(2)结合课程内容,整理基于JML规格来设计测试的方法和策略

通过课程网站的第三单元训练项目学习了Junit的简单使用。对于一些易于测试的方法,直接用Junit测试方法进行测试,包括空集状态删除等极端情况。

此外,通过几个同学组队,互相构造数据,然后修改主函数输出到文本进行对拍也是进行测试的有效方法,在此期间也能互相交流算法实现的经验,促进代码的进一步优化。比如,第三次作业的最短路径一开始写的是比较暴力的算法,后来和室友讨论之后才上网学习了堆优化的写法。

在构造数据时,更倾向于大量重复出现方法实现较为复杂的命令,以此尝试找出问题所在,比如第一次作业重点考察qci(query_circle),qbs(query_block_sum),第三次作业重点考察sim(send_indirect_message),dce(delete_cold_emoji)等等。

 

(3)总结分析容器选择和使用的经验

本单元作业主要使用ArrayList和HashMap两种容器。

  • ArrayList

这是我使用最熟练的容器,从寒假的OO Pre开始就经常使用。

这一段时间的应用中主要有一个发现,导致我在第一次作业的自测中出现了IndexOutOfBoundException的问题。

这个问题出现在当ArrayList的数据类型为Integer时。当时使用了ArrayList实现的visited数组进行dfs计算,有一条语句是visted.remove(item.getId());,结果爆出了IndexOutOfBoundException的异常,经过试验,修改为visted.remove((Integer) item.getId());,解决了问题。经过IDEA的提示以及上网查阅资料,才知道该类容器有两个remove方法,分别定义如下:

public E remove(int index);  
public boolean remove(Object o);

当直接传入int类型的参数时,会默认自动调用第一个方法,即处理为index而非我们想要的Object,所以需要进行强制类型转化。后来改为了并查集实现,就不用考虑这个问题了。

  • HashMap

这个容器接触较晚,相对生疏一些,所以我第一次作业基本都用的是ArrayList,但是后来迭代的时候,将部分容器更换为了HashMap,比如规格里的emojiIdListemojiHeatList,直接通过一个private HashMap<Integer,Integer> emojiList实现,维护难度更小。感觉确实Map好用一些,因为查找更加高效迅速,而且写起程序来更有高级感

 

(4)针对本单元容易出现的性能问题,总结分析原因;如果自己作业没有出现,分析自己的设计为何可以避免

本单元作业的很多方法都强调算法性能,如果不认真优化很可能超时。

queryNameRank:

采用遍历的方式计算名字排名,需要调用比较函数。一开始的时候,使用的是规格要求实现的compareName方法,先取出两个对象,判断是否为null,然后再比较。后来发现这里会浪费许多时间,因为在本方法比较名字的时候,两个Person对象都确定会在容器中,就不用经过判断“是否存在”的步骤了,所以直接改为:

// faster than compareName
if (person.getName().compareTo(item.getName()) > 0) { i++; }

queryBlockSum:

由于第二次作业刚开始时,对并查集了解不够深入,考虑不够细致,把isCircle方法改为并查集后,不知道queryBlockSum也可以直接用并查集实现,还是调用的isCircle方法,浪费大量的时间。后来改为遍历people容器,统计映射对象等于自身的Person数目,即为所求结果。

queryGroupValueSum,queryGroupAgeMean,queryGroupAgeVar(getValueSumgetAgeMeangetAgeVar):

Network里的实现很简单,直接调用相关Group对象的对应方法即可。但是在Group里,如果相关方法每次都直接从头计算则会浪费较长时间,所以在Group类里设置3个新属性:

private int totalValue;       
private int totalAge;
private int totalAge2;   // 平方

当向组内加人、删人的时候,对这三个int属性进行维护。当加关系的时候,也要进行判断,对ar命令里的person1、person2的共有Group的totalValue进行维护。这样,最终的函数就较为快捷了。需要注意的是,getValueSum在早期维护时仅仅是单向的计算value和,最后返回的应该是totalValue * 2getAgeVar也不能直接照着JML规格翻译成遍历的形式,可以计算(totalAge2 + getAgeMean() * getAgeMean() * this.people.size() - 2 * mean * totalAge) / this.people.size()

sendIndirectMessage:

采用迪杰斯特拉算法计算最短路径。刚开始写的是类似离散数学PPT上的那种计算过程,通过10000 * 10000的二维数组记录关系,O(n ^ 2)的方法计算最短路径(当时想着先写出来,之后再改,所以比较暴力)。后来优化的时候,改成用储存“边”的“图”记录关系,然后用堆优化的方法计算person1到所有人员节点的最短距离,然后返回到person2的值即可。

另外,还有一点比较重要:初始版本的作业,我都是先用contains()方法判断对象是否存在,然后再取出使用;研讨课上听了助教的提醒,改为了方法开头直接按照id取出对象,然后再通过检测是否为null判断是否存在,之后直接使用。这样能减少容器的遍历,提高性能。

 

(5)梳理自己的作业架构设计,特别是图模型构建与维护策略

MyNetwork:

通过people存储所有的人员,通过groups存储所有的小组,通过messages存储所有的消息,通过disjoint存储各个人员对象的映射关系,通过emojiList存储所有emoji的id与heat(整合了emojiIdListemojiHeatList的功能),通过graph存储当前的图模型,通过personTotal存储当前的总人数。

MyGroup:

通过people存储小组内所有人员,通过totalValue存储小组内所有人员彼此之间的value值的一半(单向遍历),通过totalAge存储小组内所有人的年龄和,通过totalAge2存储小组内所有人的年龄的平方和。

MyPerson:

通过acquaintance存储本人员对象的所有联系人,通过value存储对应的value值,通过messages存储本人员的消息,通过group存储本人员对象所在的所有小组(便于添加关系(ar)之后维护所在小组内相关属性)。

对于图模型构建与维护策略,主要在MyNetwork里添加了graph,它是一个存储ArrayList<MyEdge>()的容器,作用就是构建社交网络的图模型。

以及新建的类MyEdge:

public class MyEdge implements Comparable<MyEdge> {
   private int toTarget;
   private int weight;
   
   public MyEdge() {
       toTarget = 0;
       weight = 0;
  }
   
   public MyEdge(int in1,int in2) {
       toTarget = in1;
       weight = in2;
  }
   
   public int getToTarget() {
       return toTarget;
  }
   
   public int getWeight() {
       return weight;
  }
   
   public int compareTo(MyEdge obj) {
       return this.weight - obj.getWeight();
  }
}

这样,每当添加新的关系(ar)时,只需要向graph的两个对应的ArrayList<MyEdge>()里,分别加入一条指向对方的边即可完成图的维护。在迪杰斯特拉算法实现求最短路径时,采用借助PriorityQueue<MyEdge> que = new PriorityQueue<MyEdge>();实现的堆优化算法,可以求出person1到所有人员节点的最短路径,然后取出person2对应项即可。

 

单元感想

  • 一定要多想、多测试!!!第三单元的作业成绩不太理想,很大一部分原因还是因为测试不够全面,这直接导致强测、互测出了不少bug。现在回想起来,很多问题都是写代码的时候就已经想到了的,结果自己测试的时候没能构造对应的数据,检查代码的时候也没有跳出惯性思维,等到了bug修复阶段才弥补过失。时间、精力的花费总量是基本固定的,不如在强测之前把该做的工作做好,这样分数也高,bug修复也轻松······

  • 与他人交流依旧十分重要。第12周的研讨课上,老师、助教的一些提醒给了我很大的帮助;与室友和其他同学的交流帮我发现、改进了许多问题。这些都带来了很多收获。

  • JML规格刚接触的时候感觉很繁琐,现在来看确实有很多优点,比如通过前置条件、后置条件、副作用组成的规约对代码功能进行更精确的描述,保证正确性等等。

总之,OO第三单元结束了,本单元强调的主要是规格二字,以规格为基础,结合自己的算法优化,才能正确高效地完成每一次的作业。

以上是关于OO2021-第三单元作业总结的主要内容,如果未能解决你的问题,请参考以下文章

OO第三单元总结

OO第三单元总结

BUAA_OO_第三单元作业总结

OO第三单元总结 —— JML规格

OO第三单元单元总结

OO第三单元总结