其他分享
首页 > 其他分享> > OO unit3 summary

OO unit3 summary

作者:互联网

Unit3

​ JML(Java Modeling Language) 是用于对 Java 程序进行规格化设计的一种表示语言,它对于方法内部具体是如何实现的并无要求,只是对方法的接口以及行为进行限制,具体来说就是对方法里面允许接受的数据,可以做出的改动,必须返回的数据,异常表现等进行限制,最终保证方法的无二义性。

​ 本单元的作业背景是一个简单的社交系统,允许对数据进行有序存储,建立数据之间的关系,以及精确查询,由于已经给出了JML规格,所以并不需要自己从零开始设计架构,只用把需要实现的方法按照JML规格实现即可,在实现的过程中必须特别注意方法的复杂度。卷不了性能分 那就在复杂度上区分

一、实现规格所采取的设计策略

​ 为了实现规格首先肯定需要学习JML相关语法,在掌握相关语法之后,仔细研读课程组给出的JML规格,并进行实现就行了。

具体设计策略:

​ 有时明明已经实现的查询方法,但是碰到查询就直接使用对应容器的查询方法了。

例如getMessage里对messageid进行查询时有同学会直接用对应容器的查询方法:messages.containsKey(id)

由于容器以及对应查询比较简单,直接应用容器的查询方法或者使用已经实现的查询方法代码长度上好像差别不大,但是这样不仅代码不美观,可维护性也不好。如果先整体把握代码,就不会出现这种情况。

比如addToGroup方法,首先处理exceptional_behavior,容易发现三种抛异常的情况时层层递进的,也就是说如果没在第一个地方抛异常,而在第二个地方抛了异常,那么异常原因就一定不满足一个if里的条件,同理最后正常行为的requires也可以此类推。这样就不会出现恶心的if-else三四次嵌套的情况了,个人感觉这样实现起来不但简洁美观,而且正确性也能得到保证。

二、基于JML规格设计测试的方法和策略

1.Junit

按照教程费了很大力气终于配好了环境,结果发现测试样例还是得自己构造,找同学了解后发现,貌似可以通过JML语法自动生成测试样例,但是这些样例大都是边界样例,强度还是不高,因此在三次作业中我基本都没有使用Junit进行单元测试。

2.评测机

我和hxd一起构造了评测机,进行了所有指令的正确性测试压力测试

评测机大概思路如图:

优点

缺点

效果

三、容器选择和使用

HashMap:用于存储键值对,本单元来说就是id与对应的实例,具体来说有Person,Message,Group类等等。

​ 由于对键可以实现O(1)查找,而作业中对于内存使用几乎可以说没有限制,所以凡是能用HashMap的坚决不用其他的容器,因此它也是我本单元作业中主要使用的容器类型。

需要注意的是,在进行集合遍历得时候,如果存在对集合内部的元素进行改动的操作,最好使用迭代器进行遍历,否则会出错。例如dce这条指令需要对heat过小的emoji进行过滤:

Iterator iterator = emojiIdToemojiHeat.entrySet().iterator();
while (iterator.hasNext()) {
	Map.Entry entry = (Map.Entry) iterator.next();
	if ((Integer)entry.getValue() < limit) {
		iterator.remove();
    }
}

ArrayList:qrm这条指令需要查询某个person最新收到的四条message,可以利用ArrayList进行存储,每次加到末尾,查询的时候从最后开始反向遍历即可。

Priority Queue:内部用堆进行实现,可以用于dijikstra算法中进行堆优化。

在构造队列的时候可以传入比较器,也可以将要传入的实例对应的类实现Comparable接口,否则会出错:

Comparator comparator = new Comparator() {
    @Override
	public int compare(Object o1, Object o2) {
       assert o1 instanceof Node;
       assert o2 instanceof Node;
       return ((Node) o1).getDis() - ((Node) o2).getDis();
       }
   };
   PriorityQueue<Node> queue = new PriorityQueue<>(comparator);

四、设计容易出现的性能问题

1.第一次作业

​ 容易出现性能问题的就是qbs这个操作了,由于涉及到连通块的查询,如果无脑按照JML的暴力做法来做,是一定会CTLE的。

由于本单元三次作业都没有对relation的删除操作,因此可以使用并查集:

public class MyPerson implements Person {
  private int fatherId;
  ……
	}

public int find(int id) {
        MyPerson person = (MyPerson) getPerson(id);
        if (person.getFatherId() == id) {
            return id;
        }
        int fatherId = find(person.getFatherId());
        person.setFatherId(fatherId);
        return fatherId;
    }

public void merge(int id1,int id2) {
        int fatherId1 = find(id1);
        int fatherId2 = find(id2);
        if (fatherId1 != fatherId2) {
            MyPerson person = (MyPerson) getPerson(fatherId1);
            person.setFatherId(fatherId2);
        }
    }

这里我留下了一个bug,在merge方法中fatherId 写成了id,导致虽然不会出错(之后再进行find,会自动更新为祖先节点)会出错 就好了但是多做了很多没无意义的工作,增大了时间成本,而第一次作业中强测指令数较小,bug没有发现

第二次作业发现CTLE了,一查发现是第一次的残留问题 (╯°Д°)╯

然后就修了bug,顺带将连通块的统计做成了动态更新的,每次加人、合并都要考虑连通块数量的更新,这样就能实现qbs的动态查询了,修复之后发现运行时间大概快了一倍。

@Override
public void addPerson(Person person) throws EqualPersonIdException {
        int personId = person.getId();
        if (people.containsKey(personId)) {
            throw new MyEqualPersonIdException(personId);
        } else {
            people.put(personId,person);
            block++;
        }
    }

public void merge(int id1,int id2) {
        int fatherId1 = find(id1);
        int fatherId2 = find(id2);
        if (fatherId1 != fatherId2) {
            MyPerson person = (MyPerson) getPerson(fatherId1);
            person.setFatherId(fatherId2);
            block--;
        }
    }

2.第二次作业

qgvs:如果完全按照JML,采用二重循环实现,同样也是必CTLE的

解决方法是采用动态维护的方法:

qgav qgam:一个是求方差,一个是求平均值,考虑到内存无限,在实现较简单的情况下,能做记忆化就尽量做记忆化

在更新方差的时候,需要将公式展开:从而可动态维护,⚠️这里不能化简,以免造成精度与JML规格不符导致出错

\[Var(x) = \frac {\sum ^n _{i =1}(x_i - \overline {x})^2} {n} = \frac {\sum ^n _{i=1}x_i^2 -2\times\overline {x}\times \sum ^n _{i=1}x_i + n\times\overline{x}^2}{n} \]

每次只有向组内加人减人才会影响方差和均值,这时才需要进行相应的更新,包括平方和总和均值等。考虑到需要查询两个属性,因此设置两个标记位,如果标志是false,就直接查询,如果是true,则进行相应更新。每次加人减人后需要将两标志位都设成true,更新完之后相应标志位设为false

所以我的group类里面增设了很多属性。

    private int valueSum;
    private int ageSum; //组内所有人的年龄和
    private int ageMean;//组内所有人的年龄平均值
    private int ageVar; //组内所有人的年龄方差
    private int ageSquareSum; //所有人的年龄平方和
    private boolean needUpdateMean; //需要更新年龄平均值
    private boolean needUpdateVar; //需要更新年龄方差

3.第三次作业

sim:非直接相邻节点发送信息,单源最短路径,采用dijikstra算法实现,为了防T采用堆优化(甚么堆优化,java里就是优先队列,不要重复造轮子)

由于加人,加边等会影响原来的图结构,因此记忆化不太好做,选择放弃。

4.效果

总的来说效果很好,第三次作业最长CPU使用时间是1.944s。6s似乎太长了(⊙_⊙)?

五、框架设计(图模型的构建和维护策略)

本单元涉及到的需要图论知识解决的指令较少,只有:

qbs并查集

simdijikstra

两种算法不再赘述

在搜索单源最端路径的时候,我新建了Node类,里面存了Personid距离起点的distance,每次进行搜索时都要重新new一轮Node实例,并按照算法流程走一遍

其他框架设计感觉课程组都提前弄好了,没有什么可说的,唯一需要自己做的就是不同XXmessagemessage的继承和Node类的构建

六、感想

标签:OO,int,作业,summary,person,JML,unit3,方法,id
来源: https://www.cnblogs.com/ito-/p/OO_unit3_Summary.html