BUAA - OO - 第三单元作业总结
BUAA - OO - 第三单元作业总结
1. 单元任务
- 本单元相对电梯而言人性化了许多
- 在不存在TLE的情况下,进行完全遵从
jml
的设计即可
1.1 测试
- 自动测试时,进行大规模随机测试与对拍即可
- 尤其有助于发现TLE、爆栈等规模较大的问题
- 手动测试时,注意进行有针对性的测试
- 指令的响应情况(是否响应、是否结果正确、是否分支正确、...)
- 异常的抛出情况(是否抛出、是否顺序错误、是否计数错误、...)
- 边界数据的使用(+、-、0、MAX、MIN、特殊数据、...)
- 边界情况的处理(
int
的除法精度、group人数上限、...)
1.2 架构设计
1. my.exceptions
:自定义异常与抛出计数
// 建立计数器如下
public class ExceptionCounter {
private final HashMap<Integer, Integer> counter = new HashMap<>();
private int count = 0;
public int count() { return count; }
public int get(int id) {...}
public void increase(int id) {...}
public void increase(int id1, int id2) {...}
}
// 在每个异常类里添加静态的计数器,并在创建和打印时进行方法调用和属性更新即可,e.g.
public class MyRelationNotFoundException extends RelationNotFoundException {
private static final ExceptionCounter RNF_COUNTER = new ExceptionCounter();
...
}
2. my.network
: 主要架构与相关指令
全部遵从jml
/** 鉴于 id 的唯一性,应当建立 id -> Element 的映射,以便进行低复杂度查询 **/
// MyNetwork.java
private final HashMap<Integer, Person> people = new HashMap<>();
private final HashMap<Integer, Group> groups = new HashMap<>();
private final HashMap<Integer, Message> messages = new HashMap<>();
private final HashMap<Integer, Integer> emojiHeat = new HashMap<>();
private final FindUnionSet<Person> block = new FindUnionSet<>();
public int kruskal(Person root) {...}
public int spfa(MyPerson root, MyPerson leaf) {...}
// MyGroup.java
private final HashMap<Integer, Person> people = new HashMap<>();
// MyPerson.java
private final HashMap<Integer, Integer> acquaintValue = new HashMap<>();
private final ArrayList<Message> messages = new ArrayList<>();
3. my.utility
: 数据结构与算法(细节见后文)
// for HW9 && 10: 记录连通块
/** 路径压缩并查集 **/
public class FindUnionSet<T> {
private final HashMap<T, T> father = new HashMap<>();
private final HashMap<T, Integer> depth = new HashMap<>();
public void merge(T p1, T p2) {...}
public T find(T person) {...}
...
}
// for HW10: 记录边
/** 关键字顺序:value, p1, p2 **/
public class MyEdge<T> implements Comparable<MyEdge<T>> {
private final T p1;
private final T p2;
private final int value;
public int compareTo(MyEdge e) {...}
...
}
// for HW11: 记录数据对,便于排序
public class Pair<T1, T2> {
private final T1 a1;
private final T2 a2;
...
}
1.3 算法与性能
1. MyGroup
:针对反复查询的维护
// MyGroup类中,部分高复杂度方法经常被调用而导致TLE,因此我们将其结果记为属性
public class MyGroup implements Group {
private int valueSum = 0;
private long ageSum = 0;
private long ageSqrSum = 0;
...
// 群组人员添加/删除时 以及 网络关系添加时 进行更新,
/** e.g. 人员添加时(删除同理)**/
public void addPerson(Person person) {
...
IntStream.range(0, p.size()).filter(i -> hasPerson(p.get(i))).
forEach(i -> valueSum += 2 * p.getValue(i));
ageSum += person.getAge();
ageSqrSum += (long) person.getAge() * person.getAge();
...
}
/** e.g. 网络关系添加时(算两次)**/
public void updateValueSum(Person p1, Person p2, int value) {
valueSum += (hasPerson(p1) && hasPerson(p2)) ? 2 * value : 0;
}
...
// 使用数据时应 **完全按照jml方法**,注意除法导致的 int 精度问题
/** e.g. 方差计算 **/
public int getAgeVar() {
int mean = getAgeMean();
return getSize() == 0 ? 0 : (int) ((ageSqrSum - 2 * ageSum * mean + mean * mean * getSize()) / getSize());
}
}
2. MyNetwork
:图的构建与维护
三次作业的复杂指令
isCircle
和queryBlockSum
: 连通性
private final FindUnionSet<Person> block = new FindUnionSet();
// in addPerson()
block.add(person);
// in addRelation()
block.merge(p1, p2);
// in isCircle()
return block.find(p1).equals(block.find(p2));
// in queryBlockSum()
return (int) people.stream().filter(person -> block.find(person).equals(person)).count();
queryLeastConnection
: MST
public int kruskal(Person root) {
FindUnionSet<Person> tree = new FindUnionSet<>();
ArrayList<MyEdge<Integer>> treeEdges = new ArrayList<>();
/** 选取root相关边集,排序 **/
people.stream().filter(person -> block.isRelated(person, root)).forEach(tree::add);
for (Person person : tree.getPeople()) {
for (Person acquaint : ((MyPerson) person).getAcquaint()) {
treeEdges.add(new MyEdge(person, acquaint, person.queryValue(acquaint)));
}
}
Collections.sort(treeEdges);
/** plain_old_kruskal **/
...
}
sendIndirectMessage
: 最短路
public int spfa(MyPerson root, MyPerson leaf) {
HashMap<Person, Integer> mapDist = new HashMap<>();
PriorityQueue<Pair<Integer, Person>> mapQueue = new PriorityQueue<>(Comparator.comparing(Pair::get1));
/** 初始化距离图 **/
people.forEach(value -> mapDist.put(value, 0x3f3f3f3f));
mapDist.put(root, 0);
mapQueue.add(new Pair<>(mapDist.get(root), root));
/** plain_old_shortest_path **/
...
return mapDist.get(leaf);
}
1.4 bug修复
重要问题的解决方法都在前文有所叙述
- TYPO(sad)
- 算法问题(very sad)
qgav
未维护导致TLE- 未完全按照
jml
编写:int
在计算方差、群发红包时产生误差等等
2. Network扩展
2.1 题目
-
假设出现了几种不同的Person
- Advertiser:持续向外发送产品广告
- Producer:产品生产商,通过Advertiser来销售产品
- Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买
-- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息 - Person:吃瓜群众,不发广告,不买东西,不卖东西
如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等
请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格(借鉴所总结的JML规格模式)
2.2 实现
-
Advertiser,Producer,Customer可作为Person的子类,
AdvertiseMessage
和PurchaseMessage
可作为Message的子类 -
题意是创建一个生产推动而非需求拉动的市场,因此主要这样实现
- Producer生产物品,在Advertiser中添加生产者id
- Advertiser对所有物品发送广告
- Customer消费物品,向Advertiser发出购买消息并使Producer库存减少
-
生产物品:
/*@ public normal_behavior @ requires contains(producerId) && (getPerson(producerId) instanceof Producer); @ assignable getProducer(producerId).productCount; @ ensures getProducer(producerId).getProductCount(productId) == @ \old(getProducer(producerId).getProductCount(productId)) + 1; @ also @ public exceptional_behavior @ signals (PersonIdNotFoundException e) !contains(producerId); @ signals (PersonTypeException e) contains(producerId) && !(getPerson(producerId) instanceof Producer); @*/ public void produce(int producerId, int productId) throws ProducerIdNotFoundException, PersonTypeException;
-
发送广告:
/*@ public normal_behavior @ requires containsMessage(id) && (getMessage(id) instanceof AdvertiseMessage); @ assignable messages; @ assignable people[*].messages; @ 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); @ 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())); @ also @ public exceptional_behavior @ signals (MessageIdNotFoundException e) !containsMessage(id); @ signals (MessageTypeException e) containsMessage(id) && !(getMessage(id) instanceof AdvertiseMessage); @*/ public void advertise(int id) throws MessageIdNotFoundException, MessageTypeException;
-
查询生产商:
/*@ ensures (\forall int i; 0 <= i && i < \result.size(); @ \result[i] instanceof Producer && ((Producer) \result[i]).containsProduct(productId)); @*/ public /*@ pure @*/ List<Producer> queryProducers(int productId);
3. 心得体会
- 本单元比起之前友好了不少。主要任务在于了解
jml
语言,了解规格化设计,并对给出的规格进行分析 - 完成任务时,对规格的理解和测试不仅有助于完成代码协作,也会对规格化设计的思想有所帮助
- 应当记得巩固图论知识和学习相关算法,避免知识储备在关键时刻出现问题