• 博客园Logo
  • 首页
  • 新闻
  • 博问
  • 专区
  • 闪存
  • 班级
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 简洁模式 ... 退出登录
    注册 登录
zhaosiqi
博客园    首页    新随笔    联系   管理    订阅  订阅
OO第三单元总结

一、如何利用JML规格来准备测试数据:

  • 根据每个方法的normal_behavior,exceptional_behavior及其require,进行初步分类。保证测试数据覆盖到所有不同的情况。同时,在每个normal_behavior中有若干种不同的情况,再对每一种normal_behavior中的情况进行单独讨论与测试。
  • 根据junit生成的检测,保证每种方法都能涵盖到。对于不同的选择分支的测试,使用Junit自动生成测试框架,之后对于各种抛出异常的情况和不同的分支情况编写测试样例,尽量做到每一个方法的各种分支都进行了测试。
  • 分析jml中每一条ensure以及invariant,设置数据专门检测每一个条件以及其边界状况。

二、梳理架构设计,分析自己的图模型构建和维护策略:

  • 架构设计:根据指导书中的提示建立各个异常类以及MyNetwork,MyMessage,MyGroup,MyPerson。同时,在涉及最短路径,最小生成树算法时,由于并查集实现的要求,设置了Bridge,Side,Tree个类,以实现上述算法。在MyNetwork,MyMessage,MyGroup,MyPerson中较为简单的方法基本上根据jml语言可以直接写出,故在此不过多赘述。

  • 模型建构及维护策略:对与最短路径,最小生成树,以及路径搜索算法,在此维护了一个并查集,通过维护两个ArrayList,每个ArrayList中的元素与存Person的ArrayList相对应。其中一个用于存每个Person的父节点,当没有relation时默认为其本身,另一个用于存深度,保证所有Person找其父节点的路径最短。每当有新的Relation加入时,查询其两端的Person,若二者最顶端的的父节点不相同,则查询二者父节点不同,则将两个父节点中深度浅的结点的父节点设置为深度深的结点。通过维护这样的数据结构能够较为快速的找到连通的Person的集合。向关代码可见“第一次作业性能问题”。

三、性能问题和修复情况:

  • 第一次作业:寻找路径,以及图的分支数量。 一开始使用的是DFS算法,该算法较易于实现,思路较为简单,但复杂度较高,故而在之后放弃了该方法转而使用并查集算法进行实现,通过维护两个ArrayList装入父节点以及深度,动态地维护图的各个分支,同时采用路径压缩。从而大大减小了运算的复杂度,省去了递归。
private final ArrayList<Integer> pre = new ArrayList<>();
private final ArrayList<Integer> rank = new ArrayList<>();
 public void addRelation(int id1, int id2, int value) throws
            PersonIdNotFoundException, EqualRelationException {
            ...
            int leadNum1 = methods.findLeader(people.indexOf(getPerson(id1)));
            int leadNum2 = methods.findLeader(people.indexOf(getPerson(id2)));
            if (leadNum1 != leadNum2) {
                //pre.set(leadNum1, leadNum1);
                if (rank.get(leadNum1).equals(rank.get(leadNum2))) {
                    rank.set(leadNum1, rank.get(leadNum1) + 1);
                    pre.set(leadNum2, leadNum1);
                } else if (rank.get(leadNum1) > rank.get(leadNum2)) {
                    pre.set(leadNum2, leadNum1);
                } else {
                    pre.set(leadNum1, leadNum2);
                }
            }
           }

public int findLeader(int num) {
        int personNum = num;
        int supNum = pre.get(personNum);
        while (supNum != pre.get(supNum)) {
            supNum = pre.get(supNum);
        }
        return supNum;
    }
  • 第二次作业:采用克鲁斯卡尔算法通过排列所有边从而得到最小生成树。因为之前的并查集以及实现了对图的各个分支的维护,此时只需要新增一个ArrayList用于存储所有的边即可。将属于该分支的边选出再排列大小顺序,从而选出最小生成树。在这里并没有出现复杂度的问题。但发现在第一次作业中存在一个不必要的多重遍历,到置复杂度大大上升,这是由一个函数选择参数失误导致的,传参数前将index转为元素,而进入函数后又将元素转为index,修改后直接传递index即可。

    for (Tree tree : trees) {
                    if (tree.checkPerson(side.getPerson1()) && tree.checkPerson(side.getPerson2())) {
                        flag = 1;
                        break;
                    }
                    else if (tree.checkPerson(side.getPerson1())) {
                        endpoints.add(tree);
                    }
                    else if (tree.checkPerson(side.getPerson2())) {
                        endpoints.add(tree);
                    }
                }
    

    此外在计算group的value时也出现了复杂度问题,由于循环过多导致超时。解决方案则是为每个group维护一个value在添加person,relation时增加value,减少person时删除value

     public void addPerson(Person person) {
            people.add(person);
            for (Person j : people) {
                if (person.isLinked(j)) {
                    totalvalue += (2 * person.queryValue(j));
                }
            }
        }
    public void newvalue(MyPerson p1, MyPerson p2, int value) {
            if (people.contains(p1) && people.contains(p2)) {
                totalvalue += (2 * value);
            }
        }
    public void delPerson(Person person) {
            for (Person j : people) {
                if (person.isLinked(j)) {
                    totalvalue -= (2 * person.queryValue(j));
                }
            }
            people.remove(person);
        }
    
  • 第三次作业:第三次作业中性能问题主要集中与最短路径算法。最短路径算法中采取迪杰斯特拉算法正好处于超时的边缘,于是在该算法的基础上采取了更改容器的办法使得速度加快。将边使用PriorityQueue存储,并在Bridge中设置比较方法。从而使得每一条边加入时能够快速的通过value置升序排列的方法进入队列中,使得时间复杂度小幅提升,满足性能要求。

public int getPathM(Person start, Person dest, ArrayList<Person> branchPeople) {
        HashSet<Integer> finished = new HashSet<>();
        PriorityQueue<Bridge> paths = new PriorityQueue<>();
        paths.add(new Bridge(start.getId(), 0));

        while (true) {
            int newDown = paths.element().getPersonId();
            int newSP = paths.element().getDst();
            paths.remove();

            if (newDown == dest.getId()) {
                return newSP;
            }

            if (!finished.contains(newDown)) {
                finished.add(newDown);

                Person person = hashpeople.get(newDown);
                for (int i = 0; i < ((MyPerson) person).getAquaintance().size(); i++) {
                    int personId = ((MyPerson) person).getAquaintance().get(i).getId();
                    int value = ((MyPerson) person).getValues().get(i);
                    if (!finished.contains(personId)) {
                        paths.add(new Bridge(personId, newSP + value));
                    }
                }
            }
        }
       }

四、对Network进行扩展:

  • 新增类:

    Product类:产品类

    /*@ public instance model int id; // 产品id
       @ public instance model int price; // 价格
       @ public instance model int producerId; // 生产商id
        @ public instance model int salescount; // 表示产品的销量
       @*/
    

    Advertiser继承 Person

    Producer继承 Person

    Customer继承 Person

    AdvertiseMessage继承 Message

    • 新增属性product表示宣传产品

    PurchaseMessage继承 Message

    • 新增属性product表示购买产品

    1.发布广告 advertise:

    Advertiser将向所有关联的Customer发送信息。

    /*@ public normal_behavior
      @ requires containsMessage(id) && getMessage(id) instanceof AdvertiseMessage
      @ assignable messages;
      @ assignable getMessage(id).getPerson1().socialValue;
      @ ensures !containsMessage(id) && messages.length == \old(messages.length) - 1 &&
      @         (\forall int i; 0 <= i && i < \old(messages.length) && \old(messages[i].getId()) != id;
      @         (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i]))));
      @ ensures \old(getMessage(id)).getPerson1().getSocialValue() ==
      @         \old(getMessage(id).getPerson1().getSocialValue()) + \old(getMessage(id)).getSocialValue() &&
      @ ensures (\forall int i; 0 <= i && i < people.length; (\old(getMessage(id)).getPerson1().isLinked(people[i]) && people[i] instanceof Customer) ===> (\forall int j; 0 <= j && j < \old(people[i].getMessages().size());
      @          people[i].getMessages().get(j+1) == people[i].getMessages().get(j)));
      @ ensures (\forall int i; 0 <= i && i < people.length; (\old(getMessage(id)).getPerson1().isLinked(people[i]) && people[i] instanceof Customer) ===> (people[i].getMessages().get(0).equals(\old(getMessage(id)))));
      @ ensures (\forall int i; 0 <= i && i < people.length; (\old(getMessage(id)).getPerson1().isLinked(people[i]) && people[i] instanceof Customer) ===> (people[i].getMessages().size() == \old(people[i].getMessages().size()) + 1));
      @ also
      @ public exceptional_behavior
      @ signals (MessageIdNotFoundException e) !containsMessage(id);
    public void advertise(int id) throws MessageIdNotFoundException;
    

    2.查询销售额查询商品销售额 searchProductSale
    根据某productId查询其销售额。

    /*@ public normal_behavior
      @ requires containsProduct(id)
      @ ensures (\exists int i; 0 <= i && i < productIdList.length && productIdList[i] == id; \result == productSaleList[i]);
      @ also
      @ public exceptional_behavior
      @ signals (ProductIdNotFoundException e) !containsProduct(id);
      @*/
    public /*pure*/ int searchProductSale(int id) throws ProductIdNotFoundException;
    

    3.购买产品 purchase

    customer找到对应广告信息,然后advertiser向producer发送信息。其中getAdvertisement()方法是根据productId返回person.messages中首个关于该产品的广告信息,hasAdvertisement()则是根据productId判断是否有该产品的广告信息。

    /*@ public normal_behavior
      @ requires contains(pid) && getPerson(pid) instanceof Customer
      @ requires containsMessage(mid) && getMessage(mid) instanceof PurchaseMessage
      @ requires getPerson(pid).hasAdvertisement(getPerson(pid).productId) && getPerson(pid).getAdvertisement(getPerson(pid).productId).getPerson1().equals(getMessage(mid).getPerson1())
      @ assignable messages;
      @ assignable getMessage(mid).getPerson1().socialValue;
      @ assignable getMessage(mid).getPerson2().socialValue;
      @ assignable productSaleList;
      @ ensures !containsMessage(mid) && messages.length == \old(messages.length) - 1 &&
      @         (\forall int i; 0 <= i && i < \old(messages.length) && \old(messages[i].getId()) != id;
      @         (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i]))));
      @ ensures \old(getMessage(mid)).getPerson1().getSocialValue() ==
      @         \old(getMessage(mid).getPerson1().getSocialValue()) + \old(getMessage(mid)).getSocialValue() &&
      @         \old(getMessage(mid)).getPerson2().getSocialValue() ==
      @         \old(getMessage(mid).getPerson2().getSocialValue()) + \old(getMessage(mid)).getSocialValue();
      @ ensures (\forall int i; 0 <= i && i < \old(getMessage(id).getPerson2().getMessages().size());
      @          \old(getMessage(mid)).getPerson2().getMessages().get(i+1) == \old(getMessage(mid).getPerson2().getMessages().get(i)));
      @ ensures \old(getMessage(mid)).getPerson2().getMessages().get(0).equals(\old(getMessage(mid)));
      @ ensures \old(getMessage(mid)).getPerson2().getMessages().size() == \old(getMessage(mid).getPerson2().getMessages().size()) + 1;
      @ ensures (\exist int i; 0 <= i && i < productIdList && productIdList[i] == getPerson(pid).productId; productSaleList[i] == \old(productSaleList[i]) + 1)
      
      @ also
      @ public exceptional_behavior
      @ signals (PersonIdNotFoundException e) !contains(pid);
      @ signals (NotCustomerException e) contains(pid) && !(getPerson(pid) instanceof Customer)
      @ signals (MessageIdNotFoundException e) !containsMessage(mid);
      @*/
    public void purchase(int pid, int mid) throws MessageIdNotFoundException,PersonIdNotFoundException, 
    

五、学习体会:

​ jml语言为用户提供接口方法调用的规格,同时也指导了方法本身的设计,很好的隔开了用户调用与具体的实现。通过这个单元的学习我明白了对于一个编程任务,编写过程并不是最重要的,也不是最花时间的。而设计是很重要的。通过契约编程的思想,将设计由自然语言变为JML规格,使得其更为清晰,并且可以由工具来检查规格与实现的不一致。这样进一步分离了设计与实现。JML规格通过谓词逻辑与java自带的保证正确性的函数来描述自己程序的行为,从效果入手,这给了与实现者不一样的视角,有助于更好的理解自己的程序,写出bug少的程序。

posted on 2022-06-05 15:46  realNobody  阅读(8)  评论(1)  编辑  收藏  举报
刷新评论刷新页面返回顶部
Copyright © 2023 realNobody
Powered by .NET 7.0 on Kubernetes