第一次作业
设计策略
按照jml的描述完成各个接口,实现接口中的方法
测试方法
手动构造样例之后和同学的结果比较,很难找到自己的问题也无法判断cpu时间,果然在强测和互测中被打傻了
容器选择
由于每个person的id是唯一的,且需要从id查询到person,很自然的想法就是使用HashMap来存储,id作为key,person作为value
性能问题
第一次作业的jml描述和接口都十分简单,导致我没有多加思考,直接按照jml的描述把所有的方法完成,导致强测和互测时超时十分严重,强测挂掉了6个点。最后在bug修复时改用并查集的做法才避免了ctle。
图模型构建与维护策略
第一次作业时我并没有考虑到图,只是简单地把各个方法的jml描述加以实现,在第二次作业时才考虑图的算法
第二次作业
设计策略
第二次策略加入了Group类和Message类,Message类中只需要按照jml规格描述一步步实现就好。
Group类中如果只是简单的按照jml描述的写出方差的计算结果,时间复杂度为O(n2),很容易出现ctle,因此我在Group中维护了ageSum和ageSqrSum三个变量来求方差。而求方差时容易出错的点为一下写法
(ageSquaredSum - 2 * mean * ageSum) / people.size() + mean * mean;
会出现由于向上取整导致的负数的错误
要非常小心的点是除了addPerson和delPerson是会导致valueSum的改变,已经在一个group中的两个person,在network中addRelation时,也会改变valueSum,因此我增加了一个setValueSum的方法。
测试方法
这次我吸取了第一次作业中样例过少和无法判断cpu时间的教训,使用python中的time库来判断cpu时间
容器选择
依然采用HashMap来存储Group和Message,key都是id,value为group和message
性能问题
由于吸取了第一次作业大量ctle的教训,第二次作业我在处理person有关请求时采用了并查集,维护连通分量数,保证qbs请求的时间复杂度为O(1),并且在Group的求方差的方法中也使用了时间复杂度为O(1)的查询,使这次测试并没有出现ctle。
但由于我的粗心,在sendMessage方法中,错将PersonId的异常抛出写成了MessageId,导致强测挂了一个点,实在很不应该。
图模型构建与维护策略
由于第一次作业挂的点太多,从第一次作业的bug修复开始,我就开始注重图模型的构建,并且我渐渐明白了老师课上所说的,jml描述并不规定方法的具体实现方式,我在多次阅读jml规格之后,理解了jml所描述的内容和图的知识所对应起来,再进行对方法的实现,可以让代码的编写更加简单。
维护策略是维护一个并查集,在Network中,实现HashMap<id,father_id>存储每个用户和其祖先的id。在addPerson方法中,新增id对应的键值对,将father_id设为自己。在addRelation方法中,合并两个人的father_id。另实现find方法,路径压缩+返回祖先。在isCircle方法中,对两个id分别find出祖先,以判断二人是否连通。
第三次作业
设计策略
这一次作业在第二次作业的基础上增加了EmojiMessage RedEnvelopeMessage和NoticeMessage这三个类,这三个类继承字Message类,没什么好说的。
在Network中增加了若干方法,其中值得注意的是sendIndirectMessage 和 deletColdEmoji。
sendIndirectMessage方法中需要注意的是求最短路径。我的方法是采用迪杰斯特拉算法的堆优化方法,如果不堆优化可能会ctle。具体实现方法为,新建MyPair类,包含属性id和dis(表示到起点的距离),在Network中实现比较器。但之后我认为更优雅的做法是在MyPair内部实现Comparable接口,重写compareTo方法。
deleteColdEmoji要注意的就是除了要将EmojiMessage从Emoji容器中删除,还要将其从messages中删除,这一点也让我最开始提交时产生了一次错误。
测试方法
本次作业我使用了同学的评测机,大致思路和第二次作业我的测试方法类似,只是大佬的评测机对可能出错的点、卡t的点都有特殊的构造,这种构造样例的能力是我所欠缺的。
容器选择
依然使用HashMap来存储新增的id和对应的对象
性能问题
这次作业在弱测中测强测互测中均没有bug,同一个room中也没有同学hack出了其他同学代码的问题。
感想
本单元的代码和前两个单元相比难度下降了很多,但对我个人而言,前两次作业的强测结果和前两个单元相比却很不理想,我认为是我自己对规格的理解不透彻导致的。
在根据JML规格书写代码时,读懂jml规格描述并且按照其要求一步一步将代码写对并运行正确并不难,但很多时候我们要理解其规格描述背后的深意,比如第一次作业的isCircle、queryBlockSum方法,第二次作业的方差计算,第三次作业的最短路径。只要分析出了这些背后的东西,那么分别对其真正要表示的内容进行维护、优化,就显得不那么困难了。