CMU 15-445 Project 0 實現字典樹

来源:https://www.cnblogs.com/suqinglee/archive/2022/09/11/16684055.html
-Advertisement-
Play Games

原文鏈接:https://juejin.cn/post/7139572163371073543 項目準備 代碼、手冊 本文對應 2022 年的課程,Project 0 已經更新為實現字典樹了。C++17 的開發環境建議直接下載 CLion,不建議自己瞎折騰。 測試 $ mkdir build && ...


原文鏈接:https://juejin.cn/post/7139572163371073543

項目準備

代碼手冊

本文對應 2022 年的課程,Project 0 已經更新為實現字典樹了。C++17 的開發環境建議直接下載 CLion,不建議自己瞎折騰。

測試

$ mkdir build && cd build
$ cmake -DCMAKE_BUILD_TYPE=DEBUG ..
$ make starter_trie_test 
$ ./test/starter_trie_test

運行上面的指令,你會得到如下輸出,這不表示該項目的 5 個測試用例沒過,而是沒有執行。

[==========] Running 0 tests from 0 test suites.
[==========] 0 tests from 0 test suites ran. (0 ms total)
[ PASSED ] 0 tests.

YOU HAVE 5 DISABLED TESTS

需要修改 test/primer/starter_trie_test.cpp 文件,移除測試名 DISABLED_ 首碼。

// TEST(StarterTest, DISABLED_TrieNodeInsertTest)
TEST(StarterTest, TrieNodeInsertTest)

格式化

$ make format 
$ make check-lint 
$ make check-clang-tidy-p0

調試日誌

LOG_INFO("# Pages: %d", num_pages);
LOG_DEBUG("Fetching page %d", page_id);

項目介紹

使用支持併發的字典樹實現一個鍵值存儲,字典樹是一種高效的排序樹,用於檢索指定鍵值。這裡假設鍵都是非空的可變長度字元串,但事實上鍵可以是任意類型。

image.png

上圖所示字典樹中存儲了:HAT、HAVE、HELLO 三個鍵。

image.png

上圖所示字典樹存儲了:ab=1、ac="val" 兩組鍵值。

項目實現

只需要修改一個文件:src/include/primer/p0_trie.h

項目已經定義好了類和成員變數,以及需要實現的成員函數的簽名,可以添加輔助變數或函數,但不能修改已有變數和函數簽名。

任務一

文件中定義了 Trie、TrieNode、TrieNodeWithValue 三個類,建議先實現單線程版本,再改併發版本,類中的成員變數和成員函數都有註釋。

任務二

實現併發安全的字典樹,可以使用 BusTub 的 RwLatch 或 C++ 的 std::shared_mutex

一些 C++ 的基操

一年多沒寫 C++ 了,用慣了 Go,突然用 C++ 寫的腦殼疼,沒用過 C++ 的小伙伴可能想編譯通過都費勁,這裡貼一下需要瞭解的 C++ 特性。

移動語義

std::unique_ptr<T> 表示唯一的指向類型為 T 的對象的指針,因此需要使用移動語義 std::move,代碼中 children_ 的類型嵌套了 std::unique_ptr<T>,也需要使用移動語義。

TrieNode(TrieNode &&other_trie_node) noexcept {
    key_char_ = other_trie_node.key_char_;
    is_end_ = other_trie_node.is_end_;
    children_ = std::move(other_trie_node.children_);
}

構造父類

TrieNodeWithValueTrieNode 的子類,構造子類時,如果沒有手動在子類的構造函數中構造父類,就會調用父類的預設構造函數,而代碼中父類是沒有預設構造函數的,所以需要手動在子類中構造父類。

需要使用 std::forward<TrieNode> 轉發右值引用。

TrieNodeWithValue(TrieNode &&trieNode, T value) : TrieNode(std::forward<TrieNode>(trieNode)) {
    this->value_ = value;
    this->is_end_ = true;
}

父子指針轉換

TrieNodeWithValue 是模板類,沒法辦使用多態,Trie::GetValue 需要將 unique_ptr<TrieNode> 轉換為 TrieNodeWithValue<T>* 後調用 TrieNodeWithValue::GetValue

std::unique_ptr<TrieNode> uptr = std::make_unique<TrieNodeWithValue<T>>('a', T());
dynamic_cast<TrieNodeWithValue<T>*>(&(*uptr))->GetValue();

可能有更優雅的辦法,但我實在是想不出來了,C++ 可真難寫啊。

所有權規避

std::unique_ptr<T> 的傳遞一定是使用移動語義轉移 T 對象地址的所有權,但也可以不獲取所有權訪問 T 對象的地址,代碼里的註釋也引導我們使用這種方式。

std::unique_ptr<int> uptr(new int(1));
std::cout << *uptr << std::endl; // 1

auto *p = &uptr;
*(*p) = 2;
std::cout << *uptr << std::endl; // 2

代碼實現

大部分代碼按照註釋一步一步來就沒問題,課程規定不公開代碼,所以這裡只列一些難點。

迴圈迭代

這裡給出一個模版,對於尾節點和非尾節點,一般需要不同的操作。

auto curr = &root_;
size_t i = 0;

while (i + 1 < key.size()) {
    curr = (*curr)->GetChildNode(key[i]);
    i++;
}
// end_node
curr = (*curr)->GetChildNode(key[i]);

節點轉換

在插入流程中,當迭代到最後一個字元時,發現已經有了一個 TrieNode 類型的節點,需要轉換為 TrieNodeWithValue 類型。

* When you reach the ending character of a key:
* 2. If the terminal node is a TrieNode, then convert it into TrieNodeWithValue by
* invoking the appropriate constructor.

不熟悉 C++ 的話這個操作可能有點困難,這裡給出代碼,這一層又一層的括弧和解引用確實不夠優雅,但一時間也想不到其他好辦法。

(*curr) = std::make_unique<TrieNodeWithValue<T>>(std::move(*(*curr)), value);

節點刪除

根據代碼註釋的提示,Remove 函數是要遞歸刪除的,這裡給出遞歸的框架。

bool remove_inner(const std::string &key, size_t i, std::unique_ptr<TrieNode> *curr, bool *success) {
  if (i == key.size()) {
    *success = true; // Remove 的返回值,表示成功刪除
    (*curr)->SetEndNode(false);
    return !(*curr)->HasChildren() && !(*curr)->IsEndNode();
  }

  bool can_remove = remove_inner(key, i + 1, (*curr)->GetChildNode(key[i]), success);

  if (can_remove) {
    (*curr)->RemoveChildNode(key[i]);
  }
  return !(*curr)->HasChildren() && !(*curr)->IsEndNode();
}

remove_innner 的返回值表示傳入節點是否可以刪除,可以刪除的條件是該節點無子節點且非尾節點。函數內判斷當前節點的子節點是否可以刪除,並返回當前節點是否可以刪除。出口是遞歸到了傳入 key 的尾節點,取消尾節點標記,並返回是否可以刪除。該函數調用前還需要判斷一下 key 是否存在。

空模板變數值

GetValue 的返回值是一個模板類型,錯誤的時候直接 return T(),正常情況下,需要轉換 TrieNodeTrieNodeWithValue 中獲取值,上文提過該父子類轉換的辦法。

template <typename T>
T GetValue(const std::string &key, bool *success) {
    return T();
    return dynamic_cast<TrieNodeWithValue<T> *>(&(*(*curr)))->GetValue();
}

併發安全

這個其實很簡單,前 4 個測試都通過了,InsertRemoveGetValue 三個函數開始和結束位置加鎖和解鎖就可以了,需要註意的是這三個函數如果彼此調用會死鎖,比如 Insert 裡面調用 GetValue 判斷 key 是否存在。

這裡提供一個 Go 語言中 defer 解鎖的簡易實現,如果你不知道 defer 就在每個返回的地方都解鎖就可以。

class RLock {
  ReaderWriterLatch *latch_;

 public:
  RLock(ReaderWriterLatch *latch) : latch_(latch) { latch_->RLock(); }
  ~RLock() { latch_->RUnlock(); }
};

class WLock {
  ReaderWriterLatch *latch_;

 public:
  WLock(ReaderWriterLatch *latch) : latch_(latch) { latch_->WLock(); }
  ~WLock() { latch_->WUnlock(); }
};

使用方式如下,以 Remove 為例。

bool Remove(const std::string &key) {
  WLock w(&latch_);

  if (!Exist(key)) {
    return false;
  }
  bool success = false;
  remove_inner(key, 0, &root_, &success);
  return success;
}

寫在最後

動手開始項目前,可以先去 leetcode 過一遍 實現 Trie (首碼樹),先能寫出簡單的字典樹再動手。如果需要代碼的話可以私信我。如果想做資料庫的工作歡迎找我內推(恰點內推獎金)。

image.png


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • SpringMVC(精簡) 一、SpringMVC介紹 1.什麼是MVC 是一種軟體架構的思想,將軟體按照模型、視圖、控制器來劃分 **M: **Model,模型層,指工程中的JavaBean,作用是處理數據 JavaBean 一類稱為實體類Bean:專門存儲業務數據的,如 Student、User ...
  • 這幾天想搞到一個三階魔方排行榜的數據,官網居然不能導出Excel文件,剛好這幾天學了個爬蟲,於是爬著玩玩(應該不會進去)。 1 目標網站: https://www.worldcubeassociation.org/results/rankings/333/average 2 準備庫 ## 準備的庫 ...
  • 使用Thymeleaf 三大理由: 簡潔漂亮 容易理解 完美支持HTML5 使用瀏覽器直接打開頁面 不新增標簽 只需增強屬性 學習目標 快速掌握Thymeleaf的基本使用:五大基礎語法,常用內置對象 快速查閱 源碼下載:springboot-web-thymeleaf-enhance — Hey ...
  • Fast Framework 作者 Mr-zhong 開源項目地址 https://github.com/China-Mr-zhong/Fast.Framework QQ交流群 954866406 歡迎小伙伴加入交流探討技術 一、前言 Fast Framework 是一個基於NET6.0 封裝的輕量 ...
  • 在實際業務中,當後臺數據發生變化,客戶端能夠實時的收到通知,而不是由用戶主動的進行頁面刷新才能查看,這將是一個非常人性化的設計。有沒有那麼一種場景,後臺數據明明已經發生變化了,前臺卻因為沒有及時刷新,而導致頁面顯示的數據與實際存在差異,從而造成錯誤的判斷。那麼如何才能在後臺數據變更時及時通知客戶端呢... ...
  • 0. 前言 可以臨時設置,也可以修改配置文件 1. 修改配置文件 # 打開 配置IP的文件 路徑如下 sudo vi /etc/netplan/01-network-manager-all.yaml 1.1 輸入(修改)以下內容 # This is the network config writte ...
  • MySQL架構: 採用C/S架構,即客戶端/伺服器。客戶端和伺服器區分開,通過客戶端發送請求來和伺服器交互。 過程: 用戶通過開發的應用程式來訪問資料庫(C/S),應用程式通過連接器(connecter)連接到資料庫。 連接器包含了各種開發語言的介面,連接完成後MySQL會分配一個線程提供服務,執行 ...
  • MySQL異常sql_mode=only_full_group_by 原因:在MySQL 5.7後MySQL預設開啟了SQL_MODE嚴格模式,對數據進行嚴格校驗。會報sql_mode=only_full_group_by錯誤說明寫的SQL語句不嚴謹,對於group by聚合操作,select中的列 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...