BUAA OO 2022第三单元总结
作者:互联网
一、结构分析
(一)第一次作业
1、作业要求
本次作业要求为根据Group
,Network
,Person
三个接口内部使用JML语言描述的方法,实现相应的三个类,模拟一个社交网络中的群体、个体及其关系;实现六个抽象异常类,要求具有计数功能。
2、模型架构
首先构建三个基本类MyGroup,MyNetwork,MyPerson。MyGroup与MyPerson的基本方法较为简单,按照JML语言描述即可完成,不需要新建额外的类。
MyNetwork中包含大量的异常抛出,故先构建异常类,为方便计数,构造Allnum类,类内对不同id均进行不同异常的计数。每当抛出异常时,相应的id异常与总异常计数均加一,并再次使用该计数实现异常抛出。Allnum内部分计数代码如下:
public void addEpe(int id) { int i; for (i = 0; i < epes.size(); i++) { if (epes.get(i) == id) { epeNums.set(i, epeNums.get(i) + 1); break; } } if (i == epes.size()) { epes.add(id); epeNums.add(1); } }
public void addEpe() { epeNum++; }
MyNetwork中存在两个重点方法,均需要将MyPerson抽象为节点图进行思考。一个为isCircle方法,判断两节点之间是否存在路径;另一个为queryPeopleSum方法,求出分支数量。
这两个方法其实时想通的,两个节点在同一个分支中当且仅当两个节点之间存在路径。为实现“分支”这一概念,构建了Friends类,每个MyPerson初始时均包含一个Friends,Friends内只有自身一个节点,每当add relation时,将两个MyPerson的Friends进行并集操作,并利用java浅复制的特性使两个MyPerson包含同一个Friends。据此,isCircle只用判断两个MyPerson节点是否在同一个Friends内部即可;queryPeopleSum的结果可以用整数变量block来记录,每当add person时,分支数加一,即block加一,每当add relation且两者不满足isCircle时,两个分支合并,block减一。相关并集代码如下:
public void makeFriend(Person person, int value) { if (((MyPerson)person).getFriends() != friends) { for (Person temp : ((MyPerson)person).getFriends().getFriends()) { friends.add(temp); } ArrayList<Person> old = ((MyPerson)person).getFriends().getFriends(); for (Person temp : old) { ((MyPerson)temp).setFriends(friends); } } }
3、测试样例构造
采用大量随机生成的方法。
让询问的随机范围略大于编号范围以测试异常。
针对性增加qbs/qci的数量,全面测试性能问题。
检查group人数1111的限制满足情况。
4、bug分析
本次作业我自己的代码出现了两处bug。一处为isCircle方法未构建Friends类,而是采用递归遍历的方法;另一处为queryPeopleSum方法未使用变量进行记录,而是遍历各节点并利用isCircle方法进行判断计数。两处错误均因为暴力遍历导致cpu超时。
(二)第二次作业
1、作业要求
在第九次作业的基础上,增加收发私聊与群发消息、查询最小关系(最小生成树)等功能,并为新增功能实现新的异常类。
2、模型架构
本次作业新增异常参考之前作业完成即可,新增MyMessage模块内方法较为简单,故不做讨论。
重点方法为MyNetwork类内的queryLeastConnection方法求指定MyPerson所在分支的最小生成树的总权重,在Friends内增加路径集合与相应节点对集合,使用flag标志位记录是否因成员/关系变化而需要生成新的最小生成树。在Friends使用subFriends与sunValues数组记录最小生成树,为每个MyPerson分配新的Friends subFriends来表示最小生成树内的所在分支。相应的并集操作改进与克鲁斯卡尔生成方法的代码如下:
public void makeFriend(Person person, int value) { if (((MyPerson)person).getFriends() != friends) { for (int i = 0; i < ((MyPerson)person).getFriends().getLineValues().size(); i++) { friends.add(((MyPerson) person).getFriends().getLines().get(2 * i), ((MyPerson) person).getFriends().getLines().get(2 * i + 1), ((MyPerson) person).getFriends().getLineValues().get(i)); } for (Person temp : ((MyPerson)person).getFriends().getFriends()) { friends.add(temp); } ArrayList<Person> old = ((MyPerson)person).getFriends().getFriends(); for (Person temp : old) { ((MyPerson)temp).setFriends(friends); } } friends.add(this, person, value); }
public void makeSub() { if (subFlag == 0) { return; } subFriends = new ArrayList<>(); subValues = new ArrayList<>(); for (Person person : friends) { Friends temp = new Friends(); temp.add(person); ((MyPerson)person).setSubFriends(temp); } for (int i = 0; i < lineValues.size(); i++) { if (!subFriends.contains(lines.get(2 * i)) || !subFriends.contains(lines.get(2 * i + 1)) || ((MyPerson)lines.get(2 * i)).getSubFriends() != ((MyPerson)lines.get(2 * i + 1)).getSubFriends()) { subValues.add(lineValues.get(i)); if (!subFriends.contains(lines.get(2 * i + 1))) { subFriends.add(lines.get(2 * i + 1)); } if (!subFriends.contains(lines.get(2 * i))) { subFriends.add(lines.get(2 * i)); } for (Person temp : ((MyPerson)lines.get(2 * i + 1)).getSubFriends().getFriends()) { ((MyPerson)lines.get(2 * i)).getSubFriends().add(temp); ((MyPerson)temp).setSubFriends(((MyPerson)lines. get(2 * i)).getSubFriends()); } } } subFlag = 0; }
3、测试样例构造
在上一次作业的自动生成样例思路基础上,完善新增功能,增加网络成员与关系,尽可能多的查询最小生成树。
4、bug分析
克鲁斯卡尔法最小生成树加入最小边时仅考虑端点是否包含,未考虑端点是否连通,导致最小生成树内部可能出现多个分支。
每次add relation时生成一次最小生成树,导致cpu超时,更改为每次查询最小生成树时检测标志位flag是否需要重新生成最小生成树。
每次query_group_value_sum时均双重遍历一次组内成员导致cpu超时,更改为使用变量记录,每次group人员更新或关系变化时更新变量。
(三)第三次作业
1、作业要求
在第十次作业的基础上,增加红包收发,收发emoji并对emoji的热门程度排序,除去冷门emoji,收发间接消息(堆优化的迪杰斯特拉图)等功能,并为新增功能实现新的异常类。
2、模型架构
本次作业重点在于MyNetwork中的sendIndirectMessage方法,构建迪杰斯特拉图以查询最短路径。
在Friends类内,构建二维数组迪杰斯特拉图minMao,每次增加节点时扩展二维数组,每次仅增加关系时,查询是否需要更新矩阵元素。
每次生成迪杰斯特拉图时记录起点,同时使用标志位记录节点与关系是否发生变化,若未发生变化且起点与上一次的起点形同,则不必重新构建。相关构建代码如下:
public int makeMin(Person p1, Person p2) { //制作迪杰斯特拉图 if (minFlag == 1 || !((preLeft == p1 && preRight == p2) || (preLeft == p2 && preRight == p1))) { preLeft = p1; preRight = p2; ArrayList<Integer> used = new ArrayList<>();//0为未使用,1为已标记,2为在优先队列中 for (Person ignored : friends) { used.add(0); } prio = new ArrayList<>();//优先队列,存储序号 prio.add(friends.indexOf(p1)); used.set(friends.indexOf(p1), 2); while (prio.size() > 0) { int target = minPrio(p1); used.set(prio.get(target), 1); for (int i = 0; i < friends.size(); i++) { if (friends.get(i).isLinked(friends.get(prio.get(target)))) { if (used.get(i) == 0) { prio.add(i); used.set(i, 2); } if (used.get(i) != 1) { if (getMap(friends.indexOf(p1), prio.get(target)) + getMap(prio.get(target), i) < getMap(friends.indexOf(p1), i)) { setMap(friends.indexOf(p1), i, getMap(friends.indexOf(p1), prio.get(target)) + getMap(prio.get(target), i)); setMap(i, friends.indexOf(p1), getMap(friends.indexOf(p1), prio.get(target)) + getMap(prio.get(target), i)); } } } } prio.remove(target); } } minFlag = 0; return getMap(p1, p2); }
3、测试样例构造
在上一次作业的自动生成样例思路基础上,完善新增功能,增加网络成员与关系,尽可能多的查询迪杰斯特拉图。
4、bug分析
在扩展迪杰斯特拉图时,addMap(Person person)函数的前置条件为friends的数量仅增加了1,但在调用函数时先将所有人加入friends再使用addMap(Person person),造成前置条件不满足,数组越界产生断点。
构建迪杰斯特拉矩阵的策略错误,仅构建了起点为friends[0]矩阵。更改为每次生成迪杰斯特拉图时记录起点,同时使用标志位记录节点与关系是否发生变化,若未发生变化且起点与上一次的起点形同,则不必重新构建。
二、单元拓展
1、题目要求
假设出现了几种不同的Person
- Advertiser:持续向外发送产品广告
- Producer:产品生产商,通过Advertiser来销售产品
- Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
- Person:吃瓜群众,不发广告,不买东西,不卖东西
如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等 请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格(借鉴所总结的JML规格模式)
2、拓展方式
从功能性的角度来讲,Advertiser、Producer、Customer均可以作为Person的子类,Advertiser添加发送广告、接受购买消息的方法,Producer添加生产产品、发货的方法,Customer添加发送购买消息方法;产品广告与购买消息均作为Message的子类。
选择发送广告、生产产品、发货三个方法撰写JML规格。
发送广告
/*@ public normal_behavior @ requires containsMessage(id) && (getMessage(id) instanceof Advertisement); @ assignable messages, people[*].messages; @ 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 int i; 0 <= i && i < people.length && !getMessage(id).getPerson1().isLinked(people[i]); @ people[i].getMessages().equals(\old(people[i].getMessages())); @ ensures (\forall int i; 0 <= i && i < people.length && getMessage(id).getPerson1().isLinked(people[i]); @ (\forall int j; 0 <= j && j < \old(people[i].getMessages().size()); @ people[i].getMessages().get(j+1) == \old(people[i].getMessages().get(j))) && @ people[i].getMessages().get(0).equals(\old(getMessage(id))) && @ people[i].getMessages().size() == \old(people[i].getMessages().size()) + 1); @ also @ public exceptional_behavior @ signals (MessageIdNotFoundException e) !containsMessage(id); @ signals (AdvertisementTypeException e) !(getMessage(id) instanceof Advertisement); @*/ public void sendAdvertisement(int id) throws MessageIdNotFoundException, AdvertisementTypeException;
生产产品(producer包含现存产品与已售出产品)
/*@ public normal_behavior @ requires !containsAllProduct(productId);
@ assignable things; @ ensures things.length == \old(things.length) + 1; @ ensures (\forall int i; 0 <= i && i < \old(things.length); @ (\exists int j; 0 <= j && j < things.length; @ things[j] == \old(things[i]))); @ ensures (\exists int i; 0 <= i && i < things.length; things[i] == getProduct(productId));
@ alse
@ public exceptional_behavior
@ signabls (ProductSameException) containsAllProduct(productId); @*/ public void makeProduct(int productId) throws
ProductSameException;
发货
/*@ public normal_behavior @ requires containsProduct(productId);
@ assignable things, outThings, money, person.money, person.things; @ ensures person.money == \old(person.money) - getProduct(productId).getValue;
@ ensures money == \old(money) - getProduct(productId).getValue;
@ ensures outThings.length == \old(outThings.length) + 1;
@ ensures (\forall int i; 0 <= i && i < \old(outThings.length);
@ (\exists int j; 0 <= j && j < outThings.length;
@ outThings[j] == \old(outThings[i])));
@ ensures (\exists int i; 0 <= i && i < outThings.length; outThings[i] == getProduct(productId)); @ ensures things.length == \old(things.length) - 1;
@ ensures (\forall int i; 0 <= i && i < things.length;
@ (\exists int j; 0 <= j && j < \old(things.length);
@ things[i] == \old(things[j])));
@ ensures !(\exists int i; 0 <= i && i < things.length; things[i] == getProduct(productId));
@ ensures person.things.length == \old(person.things.length) + 1;
@ ensures (\forall int i; 0 <= i && i < \old(person.things.length);
@ (\exists int j; 0 <= j && j < person.things.length;
@ person.things[j] == \old(person.things[i])));
@ ensures (\exists int i; 0 <= i && i < person.things.length; person.things[i] == getProduct(productId));
@ alse
@ public exceptional_behavior
@ signabls (ProductIdNotFoundException) !containsProduct(productId);
@*/
public void sendProduct(Person person, int productId) throws
ProductIdNotFoundException;
三、学习体会
本单元采用了大量的自定义异常,让我对java的异常抛出的定于与原理有了更加深入的了解;本单元作为JML单元,也让我能够对JML语言有了一个较为熟练的理解与运用,让我体会到方法能够通过严谨的定义来避免许多不必要的bug。
为实现部分较为复杂的功能,必要时可以单独创建一个类专门用来储存相关数据并进行操作。本单元作业中在第一次作业创建的Friends类可以看到在三次作业的关键部分都起到了重要的容器作用,这一实践证明了适当地添加类对代码的后续开发迭代能够起到重要作用。
对于需要遍历或递归实现的操作,要多多思考能否用动态规划的思路,将可能需要的结果提前以变量或数组的形式存储,在查询时只需要直接取出即可,可以有效避免重复遍历导致的超时问题。
标签:OO,MyPerson,get,int,BUAA,Person,person,2022,friends 来源: https://www.cnblogs.com/djr1371299801/p/16341686.html