BUAA_OO_2022_第三单元总结
面向对象第三单元总结
一、作业架构分析
本单元的作业是模拟一个小型社交网络,本质是维护一个图,person是图上的节点,relation是图上连接两个节点的边,相邻两个节点或者节点对群组(group)中的所有节点可以通过发送消息(message)来改变节点的属性。
JML已经为我们规划好了社交网络系统的整体架构,按部就班按照JML描述编码就可以了,UML类图如下:
为了统计不同id触发异常的次数,我建立了一个额外的Counter类,每种异常类中都实例化一个static的counter对象来进行计数。
为了提高图的检索速度,我采用了HashMap容器来保存图中的信息,其中key为图中元素(person, group, message, emoji)独一无二的ID,value为图中元素,查找指定ID的元素的复杂度降低到了O(1)。
需要注意的是,在删除某个元素时,一边遍历一边删除会导致异常的产生,可以采用removeIf()或者stream()来解决这个问题。
二、性能问题和优化方法
图问题提升性能的一个重要的手段是首先对输入的信息进行预处理,做出必要的统计和计算,并加以保存。这样在接下来查询图中信息时就不必每次都进行重复计算,在查询次数较多时体现的更为明显。
在读入信息时,我对同组内成员的年龄和、年龄平方和、价值之和加以统计和维护,能够大大降低qgvs,qgav指令的复杂度,不必进行暴力遍历和搜索。
第九次作业
本次作业的性能问题体现在qci指令和qbs指令。qci指令是查询两个节点是否相互连通,qbs指令是查询图中有多少个独立的连通子图。和大多数人一样,这里采用的是并查集优化,从而更加快速的解决不相交集合之间的合并和查询问题。并查集有路径压缩和按秩优化两种优化方式,同时实现时能够达到复杂度均摊的效果。
第十次作业
本次作业的性能问题体现在qlc指令,本质是寻找最小生成树。实现时我将上一次作业写好的并查集算法分离出来构建一个类,直接采用并查集优化的kruskal算法来寻找最小生成树。
第十一次作业
本次作业的性能问题体现在sim指令,本质是寻找单源最短路径,这里我采用的是堆优化的dijkstra算法,并将qlc指令寻找最小生成数的算法也更改为和dijkstra算法相似的prim算法。由于我在Java中没有找到二元组(update: 似乎Pair类可以作为二元组),于是自己写了一个实现Comparable接口的二元组类参与堆排序。
三、根据JML规格编写测试数据
在本单元的测试过程中,我首先根据JML规格的每种behavior,构造出相应的测试数据,例如对于以下的JML规格:
/*@ public normal_behavior
@ requires contains(id);
@ ensures \result == getPerson(id).getMoney();
@ also
@ public exceptional_behavior
@ signals (PersonIdNotFoundException e) !contains(id);
@*/
public /*@ pure @*/ int queryMoney(int id) throws PersonIdNotFoundException;
构造如下的测试数据,分别测试normal_behavior和exceptional_behavior:
@Test
public void testAddPerson() throws Exception {
Mynetwork mynetwork = new Mynetwork();
Myperson myperson = new Myperson(1,"1",1);
mynetwork.addPerson(myperson);
Assert.assertEquals(mynetwork.queryMoney(1), 0); // test normal_behavior
try {
mynetwork.queryMoney(2);
fail("Expected an PersonIdNotFoundException to be thrown");
} catch (PersonIdNotFoundException e) {
Assert.assertEquals(e.getMessage(), "pinf-1, 2-1\n"); // test exceptional_behavior
}
}
本单元也非常好编写评测机,通过和同学的代码对拍也能发现不少的bug。
四、互测bug分析和hack策略
第九次作业
本次作业没有被测出bug,hack到一位同学,原因是这位同学的qbs没有采用并查集优化,造成复杂度较高,输入大量qbs时产生超时错误。
第十次作业
本次作业被hack了qgvs的超时bug,写代码时由于考虑到可能先向group中加人,再给人之间添加relation,为图方便我就没有在群组中维护记录群组人员总价值这个变量,而是每次都搜索一遍,这导致了qgvs的超时。这个bug被hack了6次,也hack了别人六次,基本达到了收支平衡。
第十一次作业
本次作业因为红包都发到了自己手里而被hack了4次(强测居然没测出来我很高兴),原因就是把person1写成了person,给我的教训是以后命名要更加规范,发红包人命名为sender,收红包人命名为receiver,这样就不至于写混变量名。互测期间由于准备回家心情大好就没hack别人。
五、扩展Network
增加广告类信息,增加广告热度列表(类似于表情热度列表,每发送一次广告广告热度加一),增加商品类,商品的价格定义为广告的热度,人可以消费购买商品。
广告接口:
public interface AdvertiseMessage extends Message {
//@ public instance model int AdvertiseId;
//@ public invariant socialValue == AdvertiseId;
//@ ensures \result == AdvertiseId;
public /*@ pure @*/ int getAdvertiseId();
}
发送广告:
/*@ public nomal_behavior
@ requires containsMessage(id) && (getMessage(id) instance of AdvertiseMessage) && getMessage(id).getType() == 0 &&
@ getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2()) &&
@ getMessage(id).getPerson1() != getMessage(id).getPerson2();
@ assignable messages, advertiseHeatList;
@ assignable getMessage(id).getPerson2().messages;
@ assignable getMessage(id).getPerson1().socialValue, getMessage(id).getPerson2().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() &&
@ \old(getMessage(id)).getPerson2().getSocialValue() ==
@ \old(getMessage(id).getPerson2().getSocialValue()) + \old(getMessage(id)).getSocialValue();
@ ensures (\exists int i; 0 <= i && i < advertiseIdList.length && advertiseIdList[i] == ((AdvertiseMessage)\old(getMessage(id))).getAdvertiseId();
@ advertiseHeatList[i] == \old(advertiseHeatList[i]) + 1);
@ ensures (\forall int i; 0 <= i && i < \old(getMessage(id).getPerson2().getMessages().size());
@ \old(getMessage(id)).getPerson2().getMessages().get(i+1) == \old(getMessage(id).getPerson2().getMessages().get(i)));
@ ensures \old(getMessage(id)).getPerson2().getMessages().get(0).equals(\old(getMessage(id)));
@ ensures \old(getMessage(id)).getPerson2().getMessages().size() == \old(getMessage(id).getPerson2().getMessages().size()) + 1;
@ also
@ public normal_behavior
@ requires containsMessage(id) && (getMessage(id) instance of AdvertiseMessage) && getMessage(id).getType() == 1 &&
@ getMessage(id).getGroup().hasPerson(getMessage(id).getPerson1());
@ assignable people[*].socialValue, messages, advertiseHeatList;
@ 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 (\forall Person p; \old(getMessage(id)).getGroup().hasPerson(p); p.getSocialValue() ==
@ \old(p.getSocialValue()) + \old(getMessage(id)).getSocialValue());
@ ensures (\forall int i; 0 <= i && i < people.length && !\old(getMessage(id)).getGroup().hasPerson(people[i]);
@ \old(people[i].getSocialValue()) == people[i].getSocialValue());
@ ensures (\exists int i; 0 <= i && i < advertiseIdList.length && advertiseIdList[i] == ((AdvertiseMessage)\old(getMessage(id))).getAdvertiseId();
@ advertiseHeatList[i] == \old(advertiseHeatList[i]) + 1);
@ also
@ public exceptional_behavior
@ signals (MessageIdNotFoundException e) !containsMessage(id);
@ signals (NotAdvertiseException e) !(getMessage(id) instance of AdvertiseMessage);
@ signals (RelationNotFoundException e) containsMessage(id) && (getMessage(id) instance of AdvertiseMessage) &&
@ getMessage(id).getType() == 0 && !(getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2()));
@ signals (PersonIdNotFoundException e) containsMessage(id) && (getMessage(id) instance of AdvertiseMessage) &&
@ getMessage(id).getType() == 0 && !(getMessage(id).getGroup().hasPerson(getMessage(id).getPerson1()));
@*/
public void sendAdvertiseMessage(int id) throws MessageIdNotFoundException, NotAdvertiseException, RelationNotFoundException, PersonIdNotFoundException;
定价:
/*@ public nomal_behavior
@ requires (\exists int i; 0 <= i && i < advertiseIdList.length; advertiseIdList[i] == id);
@ ensures (\exists int i; 0 <= i && i < advertiseIdList.length; advertiseIdList[i] == id &&
@ \result == advertiseHeatList[i]);
@ also
@ public exceptional_behavior
@ signals (GoodIdNotFoundException e) !AdvertiseIdList.contains(id);
@*/
public /*@ pure @*/ int setPrice(int id) throws GoodIdNotFoundException;
购买商品:
/*@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < advertiseIdList.length; advertiseIdList[i] == id);
@ assignable money;
@ ensures (\exists int i; 0 <= i && i < advertiseIdList.length; advertiseIdList[i] == id &&
@ money == \old(money) - advertiseHeatList[i]);
@ also
@ public exceptional_behavior
@ signals (GoodIdNotFoundException e) !AdvertiseIdList.contains(id);
@*/
public void buyProduct(int id) throws GoodIdNotFoundException;
六、学习体会
本单元学习了JML形式化描述语言,这种语言的特点是表述严谨,没有二义性,能够给编码者一个绝对清晰明确的要求,降低编码出错的可能性。但它的缺点是太长太琐碎了,以至于我读着读着就开始头晕眼花……
第三单元给我带来的最大的教训是写代码要认真细致。尽管本单元在难度上和前两个单元相比有巨大的差距,程序的整体架构都已经通过JML规格确定下来了,但我的bug产出却丝毫不差(-),可见自己在写代码时候的细致耐心程度还不够。另一个收获是我亲身体会到了采用恰当的数据结构和算法在提高程序性能上的重要性,在图论问题上,这种提升往往可以达到指数量级,探索和应用恰当的数据结构和算法也是我今后需要学习的地方。