1. 程式人生 > 其它 >2022年北航OO第三單元總結

2022年北航OO第三單元總結

2022年北航OO第三單元總結

單元學習概要

本單元的主要學習內容是學習 JML 規格的相關知識,並根據給出的 JML 規格實現一個社交網路的模型,該模型中包括成員、群組以及相互之間的關係和流通的訊息,並支援其中各種各樣的功能和各種各樣的異常。在完成實際規格要求的同時,還需要了解並使用圖有關的相關演算法,比如並查集、最小生成樹演算法和最短路徑演算法。

架構設計

由於 MyNetwork 具有全域性唯一性,且所有的資訊都在其中增添與流轉,因此將所有資訊,儲存其中:

考慮到人員與群組 ID 的唯一性且其需要頻繁查詢,因此使用HashMap進行儲存:

    private final HashMap<Integer, MyPerson> myPeople;
    private final HashMap<Integer, MyGroup> myGroups;

考慮到訊息列表與表情列表的頻繁刪除性,而且 JAVA 中的容器沒有完全實現的連結串列,因此自定義泛型連結串列 MyList<> 來進行對此兩類資料的儲存,同時考慮到其特性,在 MyList<> 周圍又增添了其他成員,封裝成完全的的個性化類:

    private final MessageList messages;
    private final EmojisList emojis;

考慮到演算法本身是實現功能的方式,因此將所有演算法相關操作進行解耦,完全粉裝到特定類中去:

    private final AlgorithmCore algorithmCore;

其實,此類維護了 MyNetwork 中所有人員關係,即全域性圖。

其次,MyPerson 類與 Emoji 類中實現採取了細節上的封裝:考慮到相識的人和相識值是成對出現的,表情 ID 列表與表情熱度是一一對應的,因此再獨立封裝出兩個類,分別為: emoji 與 AcqToValue;且兩類都在 MyPerson 類構造並使用,實現對外隱藏;

異常處理方式就是簡單地使用靜態的 HashMap 進行處理即可。

AlgorithmCore 中對圖的構建以及維護

自定義類 Net,實質上為一個最大連通子圖,整個 AlgorithmCore 是一個單個元素為 Net 的 ArrayList。
在 Net 中,節點使用 整型 ArrayList 即可維護,而這裡再自定義 Edge 類,當 MyNetwork 進行人員屬性相關操作時,AlgorithmCore 捕獲這些資訊並同步化具體的圖操作,以此,構建出與 MyNetwork 中一致的圖。

效能問題及優化
  1. query_circle, query_block_sum
    這兩條指令是第一次作業中最涉及到演算法的部分,通過對整個框架的理解,query_circle 是查詢兩個結點是否處於同一個連通子圖中,query_block_sum 是進行對全圖中的最大連通子圖數量。而 AlgorithmCore 中維護的 ArrayList 就是每個極大連圖子圖的 Net 的集合,因此只需要增添結點對 Net 的對映關係即可;

  2. query_least_connectio
    這條指令是在第二次作業中涉及到最小生成樹的部分,我使用的是 Kruskal 演算法來進行計算,通過手寫鏈條實現加邊時的邊的排序處理,之後正常按照演算法進行計算即可,為了避免連續重複的查詢,因此額外設定髒位進行對效能的優化。

  3. query_group_value_sum
    這條指令是求出同一組內所有結點之間的邊的權值和,為了防止資料過大導致該指令超時,在組中維護邊集合,即在往組中新增和刪除節點時,同時進行對其中相關聯的邊集合維護,同時修改其中記錄的邊權值總和。這樣的處理,可以直接使該指令的複雜度為 O(1)。

  4. send_indirect_message
    第三次作業的這條指令要求我們實現對最短路徑的查詢。在使用 Dijkstra 演算法的時候,注意到每次遍歷未查詢節點的時候可能會導致超時,因此這裡處理是改善效能的關鍵。強測第六個點也是在卡是否進行了優化。

Bug分析

本單元作業唯一出現的一個 BUG 在於在計算群組年齡方差時由於將人員大小放於分母,導致第二次作業的三個點爆出除0異常。十分痛心!

測試資料

  1. 嘗試使用 Junit 對程式碼進行了單元測試,尤其是上述提到的和演算法有關的相關指令。並初步學習瞭解了 Junit 的使用。

  2. 自動化測試:通過大量隨機生成與專門針對圖的稠密度的資料,對程式進行攻擊並檢測 CPU 所用時間,同時使用 assert 寫出評測機對執行結果的正確性進行校驗。

Network 擴充套件

題目:
假設出現了幾種不同的Person

Advertiser:持續向外傳送產品廣告

Producer:產品生產商,通過Advertiser來銷售產品

Customer:消費者,會關注廣告並選擇和自己偏好匹配的產品來購買

所謂購買,就是直接通過Advertiser給相應Producer發一個購買訊息
Person:吃瓜群眾,不發廣告,不買東西,不賣東西

如此Network可以支援市場營銷,並能查詢某種商品的銷售額和銷售路徑等

請討論如何對Network擴充套件,給出相關介面方法,並選擇3個核心業務功能的介面方法撰寫JML規格(借鑑所總結的JML規格模式)

擴充套件
通過對需求的分析,Advertiser、Producer 和 Customer 可以繼承 Person 的介面,Advertisement 和 BuyMessage 可以繼承 Message 的介面。

傳送廣告:

/*@ public normal_behavior
  @ requires containsMessage(id) && (getMessage(id) instanceof Advertisement);
  @ assignable messages;
  @ assignable 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);  
  @ also  
  @ public exceptional_behavior  
  @ signals (AdvertisementIdNotFoundException e) containsMessage(id) && !  (getMessage(id) instanceof Advertisement);  
  @*/
public void sendAdvertisement(int id) throws
        MessageIdNotFoundException, AdvertisementIdNotFoundException;

生產商生產產品:

/*@ 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);  
  @ also
  @ public exceptional_behavior  
  @ signals (ProducerIdNotFoundException e) contains(producerId) && !(getPerson(producerId) instanceof Producer);
  @*/
public void produceProduct(int producerId, int productId) throws
        PersonIdNotFoundException, ProducerIdNotFoundException;

傳送購買訊息:

/*@ public normal_behavior
  @ requires containsMessage(id) && (getMessage(id) instanceof BuyMessage);
  @ requires (getMessage(id).getPerson1() instanceof Customer) && (getMessage(id).getPerson2() instanceof Advertiser);
  @ assignable messages;
  @ assignable getMessage(id).getPerson1().money;
  @ assignable getMessage(id).getPerson2().messages, getMessage(id).getPerson2().money;
  @ 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 < \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;
  @ ensures (\old(getMessage(id)).getPerson1().getMoney() ==
  @         \old(getMessage(id).getPerson1().getMoney()) - ((BuyMessage)\old(getMessage(id))).getMoney() &&
  @         \old(getMessage(id)).getPerson2().getMoney() ==
  @         \old(getMessage(id).getPerson2().getMoney()) + ((BuyMessage)\old(getMessage(id))).getMoney());
  @ also
  @ public exceptional_behavior
  @ signals (MessageIdNotFoundException e) !containsMessage(id);  
  @ also
  @ public exceptional_behavior
  @ signals (NotBuyMessageException e) containsMessage(id) && !(getMessage(id) instanceof BuyMessage);  
  @ also
  @ public exceptional_behavior  
  @ signals (NotCustomerException e) containsMessage(id) && (getMessage(id) instanceof BuyMessage) && !(getMessage(id).getPerson1() instanceof Customer);   
  @ also  
  @ public exceptional_behavior   
  @ signals (NotAdvertiserException e) (NotCustomerException e) containsMessage(id) && (getMessage(id) instanceof BuyMessage) && (getMessage(id).getPerson1() instanceof Customer) && !(getMessage(id).getPerson2() instanceof Advertiser);
  @*/
public void sendBuyMessage(int id) throws
        MessageIdNotFoundException, NotBuyMessageException, NotCustomerException, NotAdvertiserException;

學習體會

本單元的主要知識點就是 JML 規格,通過本單元的學習,對規格設計有了初步的認識,同時,也回顧了好幾種和圖有關的演算法。
規格化設計的整合單元要求在程式碼書寫過程中就可以在一定程度上保證程式碼的正確性,因此對 JML 的學習是十分有價值和必要的。
當然,在實現規格時,也要理解規格的本身邏輯,進而才能寫出效率更高的程式碼。