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:图的构建与维护

三次作业的复杂指令

  • isCirclequeryBlockSum: 连通性
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 实现

  • AdvertiserProducerCustomer可作为Person的子类,AdvertiseMessagePurchaseMessage可作为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语言,了解规格化设计,并对给出的规格进行分析
  • 完成任务时,对规格的理解和测试不仅有助于完成代码协作,也会对规格化设计的思想有所帮助
  • 应当记得巩固图论知识和学习相关算法,避免知识储备在关键时刻出现问题
posted @ 2022-06-06 11:53  Ph_D  阅读(30)  评论(1编辑  收藏  举报