OO第三单元总结

实现规格的设计策略

  • 类已经由课程组规定好,能设计的部分只有类的字段。
  • 一般JML给出的都是疑似数组的形式,但是实际上又会出现大段的JML来描述一个数组和另一个数组对应的情况,其实这完全可以用一个HashMap来解决。
  • 对于contains..has..这种名字其实稍微阅读JML确认一下,就知道只要调用容器的contains(key)就可以完成。
  • 由简到难,先把所有pure方法写完。
  • 剩下的方法,分为异常和正常表现两部分。个人倾向于:先用if在前面把异常行为都写完。然后正常情况就可以直接写了。
  • 异常类:所有的异常类其实是同一个模板。字段可以设计为:
    • 一个静态字段统计出现总次数。
    • 一个私有字段关联构造函数,每次保存最新的异常id,用于printf输出

测试策略

  • 阅读JML:

    重点比对signal的条件,以及ensures中有关于计算的语句。保证设计完全符合JML的要求才是最稳的。

  • 随机数据对拍:

    使用python制作了关于指令的随机输入。具体可以分几个部分:

    • 首先是ap指令,数据随机。
    • 然后是ar,ag指令,数据固定在之前ap的随机数集合中。
    • 接下来是其他指令的组合。

    接下来只要打包jar和同学愉快的对拍就好了。

容器选择经验

  • ArrayList:最贴近JML描述,易于实现,但是如果拘泥于此,性能会出大问题。

    • 之前有提到过存在JML描述两个数组元素具有对应关系的情况,对于此,绝对不应该继续使用ArrayList。
    • 即使不得不使用ArrayList,其实也可以额外保存一份HashMap作为cache,增加访问速度。
  • HashMap:提高性能首选。

    • 由于作业的独特要求:任何人,组,消息,都有独一无二的id,这就已经为使用HashMap创造了有利条件。可以为他们都写一个cache增加访问速度。
    • 根据需要还可以有HashMap和HashMap的嵌套,用于关系的cache。
  • PriorityQueue:可以使用优先队列对第三次作业中最短路算法做时间优化。

    • 插入和取顶结点时的调整操作由java自己完成。
    • 只需要编写相应的comparator即可,十分方便。
  • 其他:自己实现了Relation类用于记录关系。用于优先队列优化的dijkstra算法。

    public class Relation implements Comparable {
        private int to;
        private int value;
        ...
    

性能问题

三次作业里,几乎性能问题每次都集中出现在某几个函数上。

第一次作业:

  • queryBlockSumisCircle

    • 通过阅读JML我们可以知道这里是关于关系无向图,求连通分支的一些问题。

    • 采用并查集解决:

      • 初始化father数组,每词addPerson时,father为自己。

      • 在每次addRelation的时候,进行father的归并。规定总是以更小的id为father。

        private int father(int ori) {
            int f = ori;
            while (find.get(f) != f) {
                f = find.get(f);
            }
            return f;
        }
        
      • 最后father函数返回结果相同的人,属于同一个连通分支。

      • BlockSum只需要把father数组转化为set,然后查询元素个数即可得到最大连通分支数。

第二次作业:

  • getAgeMeangetAgeVar

    • 在group类的内部设置这两个私有字段,每次addPerson,deletePerson时更新。更新实际用时为o(n),调用返回为o(1)。

    • 其中对于方差的计算:通过对表达式的展开,可以发现其为年龄平方和、年龄和、年龄平均数的形式,就方便在add和delete中直接维护这三个数据,然后直接计算方差。

      public void addPerson(Person person) {
              ...
              ageSum += person.getAge();
              ageSqureSum += person.getAge() * person.getAge();
          }
      public int getAgeVar() {
              ...
              return (ageSqureSum - 2 * mean * ageSum + size * mean * mean) / size;
          }
      
  • getValueSum:

    • 需要在NetWork中对于人的关系,人在哪些组进行缓存。
    • 在每次addRelation的时候,通知两人所在组更新ValueSum。并对该Relation缓存。
    • 在每次addToGroup的时候,对人所在的组进行缓存。
    • 由此每个组的valueSum就在添加任何关系时自动更新,最后调用的时候只要直接返回值。更新实际用时为o(n),调用返回为o(1)。

第三次作业:

  • sendIndirectMessage:读了一遍JML,简称就是求最短路。

  • 这里暂时没有想到在加人和关系的时候能直接算出来的方法。(估计不存在)所以要尽力优化每一次调用的时间。使用了优先队列优化的dijkstra算法。

  • 自己实现了Relation类用于记录关系,其实现comparable接口,以便使用优先队列对路径长度排序。

    ...
    dj.offer(new Relation(id1, 0));
    while (dj.isEmpty() != true && id1 != id2) {
        Relation r = dj.poll();
        id1 = r.getTo();
        if (dis.containsKey(id1) == true) { continue; }
        dis.put(id1,r.getValue());
        Set<Integer> s = relations.get(id1).keySet();
        for (Integer i:s) {
            dj.add(new Relation(i, relations.get(id1).get(i) + r.getValue()));
        }
    }
    ...
    

作业架构设计

主要分析自己实现的NetWork部分,其他的官方大都遵循官方架构。

第一次作业

  • 类图:

  • 度量分析:
method CogC ev iv v
MyNetwork.union(int,int) 3.0 2.0 2.0 3.0
MyNetwork.queryValue(int,int) 5.0 4.0 4.0 7.0
MyNetwork.queryPeopleSum() 0.0 1.0 1.0 1.0
MyNetwork.queryNameRank(int) 4.0 2.0 2.0 4.0
MyNetwork.queryBlockSum() 1.0 1.0 2.0 2.0
MyNetwork.MyNetwork() 0.0 1.0 1.0 1.0
MyNetwork.isCircle(int,int) 10.0 8.0 4.0 9.0
MyNetwork.getPerson(int) 3.0 3.0 2.0 3.0
MyNetwork.father(int) 1.0 1.0 2.0 2.0
MyNetwork.contains(int) 3.0 3.0 2.0 3.0
MyNetwork.compareName(int,int) 3.0 3.0 2.0 4.0
MyNetwork.addRelation(int,int,int) 11.0 8.0 6.0 11.0
MyNetwork.addPerson(Person) 1.0 2.0 2.0 2.0
Total 45.0 39.0 32.0 52.0
Average 3.4615384615384617 3.0 2.4615384615384617 4.0
  • 图模型构建与维护策略:
    • find描述了结点自身的id和他的父结点的id的联系,用于并查集操作。
    • 具体为在每次addRelation时,会调用union函数,union中以两个结点的id调用father函数找到父节点,然后把两个结点连接到父节点上,修改find。
  • 程序BUG分析:没有被测出bug

第二次作业

  • 类图:

  • 度量分析:
method CogC ev iv v
MyNetwork.union(int,int) 3.0 2.0 2.0 3.0
MyNetwork.sendMessage(int) 12.0 5.0 11.0 12.0
MyNetwork.queryValue(int,int) 5.0 4.0 4.0 7.0
MyNetwork.querySocialValue(int) 1.0 2.0 1.0 2.0
MyNetwork.queryReceivedMessages(int) 1.0 2.0 1.0 2.0
MyNetwork.queryPeopleSum() 0.0 1.0 1.0 1.0
MyNetwork.queryNameRank(int) 4.0 2.0 2.0 4.0
MyNetwork.queryGroupValueSum(int) 1.0 2.0 1.0 2.0
MyNetwork.queryGroupSum() 0.0 1.0 1.0 1.0
MyNetwork.queryGroupPeopleSum(int) 1.0 2.0 1.0 2.0
MyNetwork.queryGroupAgeVar(int) 1.0 2.0 1.0 2.0
MyNetwork.queryGroupAgeMean(int) 1.0 2.0 1.0 2.0
MyNetwork.queryBlockSum() 1.0 1.0 2.0 2.0
MyNetwork.MyNetwork() 0.0 1.0 1.0 1.0
MyNetwork.isCircle(int,int) 10.0 8.0 4.0 9.0
MyNetwork.getPerson(int) 0.0 1.0 1.0 1.0
MyNetwork.getMessage(int) 3.0 3.0 3.0 3.0
MyNetwork.getGroup(int) 1.0 2.0 2.0 2.0
MyNetwork.father(int) 1.0 1.0 2.0 2.0
MyNetwork.delFromGroup(int,int) 7.0 4.0 4.0 7.0
MyNetwork.containsMessage(int) 3.0 3.0 2.0 3.0
MyNetwork.contains(int) 1.0 2.0 1.0 2.0
MyNetwork.compareName(int,int) 3.0 3.0 2.0 4.0
MyNetwork.addToGroup(int,int) 12.0 4.0 6.0 9.0
MyNetwork.addRelation(int,int,int) 10.0 4.0 9.0 12.0
MyNetwork.addPerson(Person) 1.0 2.0 2.0 2.0
MyNetwork.addMessage(Message) 3.0 3.0 5.0 5.0
MyNetwork.addGroup(Group) 1.0 2.0 2.0 2.0
Total 87.0 71.0 75.0 106.0
Average 3.107142857142857 2.5357142857142856 2.6785714285714284 3.7857142857142856
  • 图模型构建与维护策略:

    • 新增peopleCache,在不改变之前并查集实现的基础上,增加了对person的访问速度。
    • 使用peopleInGroup,在每一次addToGroup的时候,记录person被加入到哪一个group中。
    • 使用relations,实质上是把关系当做三元组<from, to, value>存储,在addRelation的时候在NetWork中保存到Cache,并根据peopleInGroup通知先关组更新其value。
  • 程序BUG分析:没有被测出bug

第三次作业

  • 类图:

  • 度量分析:
method CogC ev iv v
MyNetwork.update(int,int,int) 9.0 3.0 5.0 7.0
MyNetwork.union(int,int) 3.0 2.0 2.0 3.0
MyNetwork.storeEmojiId(int) 1.0 2.0 1.0 2.0
MyNetwork.setMatrix(int,int,int) 1.0 1.0 1.0 2.0
MyNetwork.sendMessage(int) 28.0 5.0 18.0 19.0
MyNetwork.sendIndirectMessage(int) 14.0 5.0 10.0 14.0
MyNetwork.queryValue(int,int) 5.0 4.0 4.0 7.0
MyNetwork.querySocialValue(int) 1.0 2.0 1.0 2.0
MyNetwork.queryReceivedMessages(int) 1.0 2.0 1.0 2.0
MyNetwork.queryPopularity(int) 1.0 2.0 1.0 2.0
MyNetwork.queryPeopleSum() 0.0 1.0 1.0 1.0
MyNetwork.queryNameRank(int) 4.0 2.0 2.0 4.0
MyNetwork.queryMoney(int) 1.0 2.0 1.0 2.0
MyNetwork.queryGroupValueSum(int) 1.0 2.0 1.0 2.0
MyNetwork.queryGroupSum() 0.0 1.0 1.0 1.0
MyNetwork.queryGroupPeopleSum(int) 1.0 2.0 1.0 2.0
MyNetwork.queryGroupAgeVar(int) 1.0 2.0 1.0 2.0
MyNetwork.queryGroupAgeMean(int) 1.0 2.0 1.0 2.0
MyNetwork.queryBlockSum() 1.0 1.0 2.0 2.0
MyNetwork.MyNetwork() 0.0 1.0 1.0 1.0
MyNetwork.isCircle(int,int) 4.0 4.0 2.0 5.0
MyNetwork.getPerson(int) 0.0 1.0 1.0 1.0
MyNetwork.getMessage(int) 0.0 1.0 1.0 1.0
MyNetwork.getMatrix(int,int) 1.0 1.0 1.0 2.0
MyNetwork.getGroup(int) 1.0 2.0 2.0 2.0
MyNetwork.father(int) 1.0 1.0 2.0 2.0
MyNetwork.delFromGroup(int,int) 7.0 4.0 4.0 7.0
MyNetwork.deleteColdEmoji(int) 6.0 1.0 4.0 4.0
MyNetwork.containsMessage(int) 0.0 1.0 1.0 1.0
MyNetwork.containsEmojiId(int) 0.0 1.0 1.0 1.0
MyNetwork.contains(int) 1.0 2.0 1.0 2.0
MyNetwork.compareName(int,int) 3.0 3.0 2.0 4.0
MyNetwork.addToGroup(int,int) 12.0 4.0 6.0 9.0
MyNetwork.addRelation(int,int,int) 10.0 4.0 9.0 12.0
MyNetwork.addPerson(Person) 2.0 2.0 3.0 3.0
MyNetwork.addMessage(Message) 6.0 4.0 9.0 9.0
MyNetwork.addGroup(Group) 1.0 2.0 2.0 2.0
Total 129.0 82.0 107.0 146.0
Average 3.4864864864864864 2.2162162162162162 2.891891891891892 3.945945945945946
  • 图模型构建与维护策略:

    • 相比于之前的策略,只是封装了对于缓存的Relation读写的函数,可以直接调用setMatrixgetMatrix。(其余的部分本来想写在加入关系的时候直接求出最短路,但是没能找出算法)
    • 最短路的算法部分除了利用这两个读取cache,和之前的没有太多关联。
  • 程序BUG分析

    • sendMessage里面往群里发红包的时候,JML的条件里漏看了一句,导致直接给自己也发了一份钱,被hack了。
    @(\forall Person p;\old(getMessage(id)).getGroup().hasPerson(p) && p != \old(getMessage(id)).getPerson1();
    @p.getMoney() == \old(p.getMoney()) + i));
    
posted @ 2021-06-01 16:21  空心麻团  阅读(47)  评论(0)    收藏  举报