Loading

BUAA-OO-Unit3总结

JML规格分析

本单元存在四大类:Network,Group,Person,Message。它们的从属等级是层层递降的。

直接由规格可以得到一个架构:Network管理Group,Group组织Person;由Network负责Message的分发。这种架构与现实中的社交网络相吻合。

测试数据的准备

概论

JML表述符合我们大一下学期学过的离散数学之逻辑部分,有鉴于此,我构造测试数据的过程,就像以工程师的方法(而不是数学家的纯粹推理)解一道离散数学的题目:

阅读JML▶️产生一个形象化(图形)的理解▶️尝试在JML规则之内扩展上一步的图形▶️探索图形边界▶️发现坑点,出成数据

以上是手搓数据的大致流程。另外,这次作业涉及到并查集、MST、最短路等基础图论算法,可以去luogu对应板子题白嫖点测试点然后魔改一下,来测试实现的正确性。

由于舍友写了个数据生成器,自动化测试就全部交给对拍完成了。实际结果表明自动生成的数据还不够强,仍存在拍不出来的bug。🐱

实例

 1    /*@ public normal_behavior
 2       @ requires contains(id1) && contains(id2);
 3       @ ensures \result == (\exists Person[] array; array.length >= 2;
 4       @                     array[0].equals(getPerson(id1)) &&
 5       @                     array[array.length - 1].equals(getPerson(id2)) &&
 6       @                      (\forall int i; 0 <= i && i < array.length - 1;
 7       @                      array[i].isLinked(array[i + 1]) == true));
 8       @ also
 9       @ public exceptional_behavior
10       @ signals (PersonIdNotFoundException e) !contains(id1);
11       @ signals (PersonIdNotFoundException e) contains(id1) && !contains(id2);
12       @*/
13     public /*@ pure @*/ boolean isCircle(int id1, int id2) throws PersonIdNotFoundException;

(直观感受)在不满足异常条件的情况下,这个方法要求判断a、b之间是否存在一个路径。(边界条件探索)注意到array.length >= 2要求路径至少包含一条边和两个点。又注意到a.isLinked(a) == true,且异常行为判定条件中没有a != b,所以a和a自己也是"isCircle"的,应当返回true。(出点数据)

架构设计

建模

PersonRelationGroupNetwork
Node Edge Subgraph Combination of Subgraphs

整个单元都是围绕图来做的。

对于isCircle,要做的是判断两个点是否在同一个强连通分量上,即 a直接或间接认识b && b直接或间接认识a。朴素的算法是dfs或bfs,单次复杂度O(n)。由于数据允许动态加点加边,Tarjan可被卡成dfs。观察发现,节点间的边一定是成对存在的,且权值必相等,可以看作是无向边,整张图可以看作无向图。问题转化为求无向图的连通分量,并查集可做。

对于sendIndirectMessage,阅读JML,发现是最短路的板子。

对于queryLeastConnection,阅读JML,发现是最小生成树的板子。由于上面isCircle我们已经维护了并查集,因此可以用Kruskal来做。实际上,我理解的人际关系网应该以稠密图为主,Prim+堆优化要好一些。

维护

Network内用HashMap维护id到Person等类的映射;Group内用HashSet保存Person;Person内用HashSet保存熟人Id,用HashMap保存熟人id到价值的映射,LinkedList保存收到的Message。同时维护了一些变量用于快速响应询问,比如groupValueSum等。

注意到每个Person仅和熟人有关系,也就是Edge,而又由于对每一个Person都维护了熟人集合和对应的边权集合,因此,在最短路或最小生成树中就没有必要再开Edge和Node了,直接利用前面写好的getAquantaince和queryValue来获取邻接表和边权即可。这种维护策略,有助于节约空间。

复杂度

最后一次作业的时间复杂度如下:

OperationComplexity
add_relation O(n)
query_value O(1)
query_people_sum O(1)
query_circle O(logn)
query_block_sum O(1)
add_group O(1)
add_to_group O(n)
del_from_group O(n)
query_group_people_sum O(1)
query_group_value_sum O(1)
query_group_age_var O(n)
add_message O(1)
send_message O(n)
query_social_value O(1)
query_received_messages O(1)
query_least_connection O(nlogn)
add_red_envelope_message O(1)
add_notice_message O(1)
clean_notices O(1)
add_emoji_message O(1)
store_emoji_id O(1)
query_popularity O(1)
delete_cold_emoji O(n)
query_money O(1)
send_indirect_message O(nlogn)

可以看到,单次操作复杂度为O(nlogn),总复杂度为O(n2logn),在时限允许的范围内。

性能问题及修复

第二次作业指令量加大,原来能用O(n2)水过的qgvs现在不行了,于是我和我所在的房间T惨了。

修复方法为复杂度均摊,对每个group添加几个维护量,凡涉及到导致维护量变动的操作,都要同时维护维护量。如此,总复杂度从O(n3)均摊到了O(n2)。

其他问题无。

hack

第一次作业对同房间代码简单跑了几个样例,没有发现bug,遂放弃。

第二次作业做到了“不首先使用核武器”。

第三次作业针对新增的指令肉眼探测bug,发现了一个bug。

hack到的bug
左侧为正确结果

hack到的代码对应的代码块

可以看出作者对removeIf不太熟悉,或对题意理解有误

Network扩展及JML规格

 1 /*** 生产产品 ***/
 2 public void addProduct(Product product);    // Network
 3 /*** 推广产品 ***/
 4 public void addAdvertisementMessage(int advertiserId, int productId, int personId, int cost); // 1对1
 5 public void addAdvertisementMessage(int advertiserId, int productId, int gourpId, int cost); // 1对多
 6 public void sendAdvertisementMessage(Message message);  // Network
 7 /*** 购买产品 ***/
 8 public void buyProduct(int customerId, int productId);
 9 /*** 查询销售情况 ***/
10 public int querySales(int id); // 查询某一类商品的销量
11 public int queryAdvertiseCost(int id); // 查询某一类商品的广告费用

生产者生产某一类商品,一类商品可供无限个消费者购买。保证商品种类id唯一。

 1 /*** JML ***/
 2 /*** 生产产品。增加一类商品。 ***/
 3 /*@ public normal_behavior
 4   @ requires !(\exists int i; 0 <= i && i < products.length; products[i].equals(product));
 5   @ assignable products;
 6   @ ensures products.length == \old(products.length) + 1;
 7   @ ensures (\forall int i; 0 <= i && i < \old(products.length));
 8   @          (\exists int j; 0 <= j && j < products.length; products[j] == (\old(products[i])));
 9   @ ensures (\exists int i; 0 <= i && i < products.length; products[i] == product);
10   @ also
11   @ public exceptional_behavior
12   @ signals (EqualProductIdException e) (\exists int i; 0 <= i && i < products.length; products[i].equals(product));
13   @*/
14 public void addProduct(Product product);    // Network
 1 /*** JML ***/
 2 /*** 购买商品。消费者会购买最近收到的信息内的广告消息对应的商品。如果购买成功,会使生产者加钱,消费者减钱,会改变商品热度。 ***/
 3     /*@ public normal_behavior
 4       @ requires contains(customerId);
 5       @ requires (\exist int i; 0 <= i && i < \old(queryReceivedMessage(customerId)).length; 
 6       @             \old(queryReceivedMessage(customerId)[i]) instanceof AdvertisementMessage && containsProduct(\old(queryReceivedMessage(customerId)[i]).getProductId()));
 7       @ assignable productHeatList, people.getPerson(customerId).money;
 8       @ assignable (\forall int i; 0 <= i && i < \old(queryReceivedMessage(customerId).length) && 
 9       @             \old(queryReceivedMessage(customerId)[i]) instanceof AdvertisementMessage;
10       @             people.getPerson(\old(queryReceivedMessage(customerId)[i]).getProducer().money);
11       @ assignable people.getPerson(customerId).getMessages();
12       @ ensures (\forall int i; 0 <= i && i < \old(queryReceivedMessage(customerId)).length; 
13       @         (\old(queryReceivedMessage(customerId)[i]) instanceof AdvertisementMessage) ==>
14       @             (people.getPerson(\old(queryReceivedMessage(customerId)[i])).getProducer().money = \old(people.getPerson(\old(queryReceivedMessage(customerId)[i])).getProducer)) + \old(queryReceivedMessage(customerId)[i]).getProduct().getValue()) &&
15       @             (people.getPerson(customerId).getMoney() == \old(people.getPerson(customerId).getMoney()) - \old(queryReceivedMessage(customerId)[i]).getProduct().getValue()) &&
16       @             (\exist int j; 0 <= j && j < productHeatList.length && productIdList[j] == ((AdvertisementMessage)\old(queryReceivedMessage(customerId)[i]).getProduct().getId());
17       @                 productHeatList[j] == \old(productHeatList[j]) + 1;
18       @ ensures (\forall int i; 0 <= i && i < \old(queryReceivedMessage(customerId)).length; 
19       @         !(\old(queryReceivedMessage(customerId)[i]) instanceof AdvertisementMessage) ==>
20       @             \not_assigned(\everything);
21       @ ensures (\forall int i; 0 <= i && i < \old(queryReceivedMessage(customerId).length);
22       @          (\old(queryReceivedMessage(customerId)[i]) instanceof AdvertisementMessage) ==>
23       @           !(\exists int j; 0 <= j && j < people.getPerson(customerId).getMessages().length; people.getPerson(customerId).getMessages()[j].equals(\old(queryReceivedMessage(customerId)[i]))));
24       @ ensures (\forall int i; 0 <= i && i < \old(queryReceivedMessage(customerId).length);
25       @          (!(\old(queryReceivedMessage(customerId)[i]) instanceof AdvertisementMessage)) ==>
26       @           (\exists int j; 0 <= j && j < people.getPerson(customerId).getMessages().length; people.getPerson(customerId).getMessages()[j].equals(\old(queryReceivedMessage(customerId)[i]))));
27       @ ensures people.getPerson(customerId).getMessages().length == (\num_of int i; 0 <= i && i <= \old(queryReceivedMessage(customerId).length);
28       @          (\old(queryReceivedMessage(customerId)[i]) instanceof AdvertisementMessage));
29       @ also
30       @ public exceptional_behavior
31       @ signals (PersonIdNotFoundException e) !contains(customerId);
32       @*/
33     public void buyProduct(int customerId);
 1 /*** JML ***/
 2 /*** 推广产品。具体而言,在addMessage方法内,生产者掏钱请广告商推广,钱数立即减少;而广告商必须等到sendMessage把广告发送出去后,才能收到报酬;这符合商业契约。这里主要是针对后者,即发送广告这一过程。发送广告会使广告商钱数增加,会使社交网络价值得到提升,会在消费者收到的消息列表中增加该广告。 ***/
 3     /*@ ......
 4       @ public normal_behavior
 5       @ requires containsMessage(id) && getMessage(id).getType() == 0 &&
 6       @          getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2()) &&
 7       @          getMessage(id).getPerson1() != getMessage(id).getPerson2();
 8       @ requires getMessage(id).getPerson1() instanceof Advertiser && getMessage(id).getPerson2() instanceof Customer;
 9       @ assignable messages, emojiHeatList;
10       @ assignable getMessage(id).getPerson1().socialValue, getMessage(id).getPerson1().money;
11       @ assignable getMessage(id).getPerson2().messages, getMessage(id).getPerson2().socialValue, getMessage(id).getPerson2().money;
12       @ ensures !containsMessage(id) && messages.length == \old(messages.length) - 1 &&
13       @         (\forall int i; 0 <= i && i < \old(messages.length) && \old(messages[i].getId()) != id;
14       @         (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i]))));
15       @ ensures \old(getMessage(id)).getPerson1().getSocialValue() ==
16       @         \old(getMessage(id).getPerson1().getSocialValue()) + \old(getMessage(id)).getSocialValue() &&
17       @         \old(getMessage(id)).getPerson2().getSocialValue() ==
18       @         \old(getMessage(id).getPerson2().getSocialValue()) + \old(getMessage(id)).getSocialValue();
19       @ ......
20       @ ensures (\old(getMessage(id)) instanceof AdvertisementMessage) ==>
21       @          (\old(getMessage(id)).getPerson1().getMoney() == \old(getMessage(id).getPerson1().getMoeny()) + \old(getMessage(id).getCost));
22       @ ......
23       @ ensures \old(getMessage(id)).getPerson2().getMessages().get(0).equals(\old(getMessage(id)));
24       @ ensures \old(getMessage(id)).getPerson2().getMessages().size() == \old(getMessage(id).getPerson2().getMessages().size()) + 1;
25       
26       @ also
27       @ public normal_behavior
28       @ requires containsMessage(id) && getMessage(id).getType() == 1 &&
29       @           getMessage(id).getGroup().hasPerson(getMessage(id).getPerson1());
30       @ requires getMessage(id).getPerson1() instanceof Advertiser;
31       @ assignable people[*].socialValue, messages, getMessage(id).getPerson1().getMoney;
32       @ ensures !containsMessage(id) && messages.length == \old(messages.length) - 1 &&
33       @         (\forall int i; 0 <= i && i < \old(messages.length) && \old(messages[i].getId()) != id;
34       @         (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i]))));
35       @ ensures (\forall int i; 0 <= i && i < people.length && people[i] instanceof Customer && \old(getMessage(id)).getGroup().hasPerson(people[i]); 
36       @             people[i].getSocialValue() == \old(people[i].getSocialValue()) + \old(getMessage(id)).getSocialValue());
37       @ ensures (\forall int i; 0 <= i && i < people.length && (!\old(getMessage(id)).getGroup().hasPerson(people[i]) || (\old(getMessage(id)).getGroup().hasPerson(people[i]) && !(people[i] instanceof Customer));
38       @          \old(people[i].getSocialValue()) == people[i].getSocialValue());
39       @ ......
40       @ ensures (\old(getMessage(id)) instanceof AdvertisementMessage) ==>
41       @          (\old(getMessage(id)).getPerson1().getMoney() == \old(getMessage(id).getPerson1().getMoney()) + \old(getMessage(id)).getCost * \old(getMessage(id)).getGroup().getSize());
42       @ ensures (\forall int i; 0 <= i && i < people.length &&
43       @         \old(getMessage(id).getGroup().hasPerson(people[i])) && people[i] instanceof Customer;
44       @             (\forall int j; 0 <= j && j < \old(people[i].getMessages().size());
45       @                 people[i].getMessages().get(j+1) == \old(people[i].getMessages().get(j))) &&
46       @             people[i].getMessages().get(0).equals(\old(getMessage(id))) &&
47       @             people[i].getMessages().size() == \old(people[i].getMessages().size()) + 1);
48       @ ......
49       @ also
50       @ public excetpional_behavior
51       @ ......
52       @ signals (WrongPersonTypeExcetpion e) containsMessage(id) && getMessage(id).getType() == 0 &&
53       @          (getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2())) &&
54       @             ((getMessage(id).getPerson1() instanceof Advertiser) || (getMessage(id).getPerson2() instanceof Customer);
55       @ signals (WrongPersonTypeExcetpion e) containsMessage(id) && getMessage(id).getType() == 1 &&
56       @          (getMessage(id).getGroup().hasPerson(getMessage(id).getPerson1())) &&
57       @             ((getMessage(id).getPerson1() instanceof Advertiser);
58       @*/
59     public void sendMessage(int id);

真难写。

学习体会

JML实现部分:阅读JML时,要对照着方法名和参数列表去理解。一个好的方法名能准确地反映JML撰写者的设计意图,能使实现者的建模速度显著加快。实现者要想办法把方法名解释成自己能理解的语言。比如isCircle方法 <==> 判断两个人是不是一个“圈子“的。

JML撰写部分:真要命。写出一个严谨的JML规格,难度不亚于实现一个JML规格。

开闭原则:尽量不去改动过去已经实现的代码。第三次作业加入了两种新的消息,相应的改了JML中sendMessage部分。根据开闭原则,我们在实现的时候并不一定要去改变上次写好的sendMessage,完全可以另起炉灶。利用java的方法重载特性,适当的改变第二次作业的参数列表,比如增加一个标志位、改变参数类型,然后再重写能支持三种信息的sendMessage方法,让新方法内部调用旧方法,也不失为一种选择。

JML这一单元的难点在于,如何将规格化的表述匹配到我们之前接触过的模型。我们对相应的模型越熟悉,这个转化的过程就会越顺利。经过训练,应该具有一眼看出模型并找到合适算法的能力。同时,今后在接触新模型时,应该尝试着用规格化的语言表述出来。这应该是一个双向促进的过程。

posted @ 2022-06-04 23:43  Barque  阅读(17)  评论(0编辑  收藏  举报