Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ set(JSON_BuildTests
CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(json)

# doctest 只需要一個 header;用 SOURCE_SUBDIR 指向不存在的目錄,讓 FetchContent
# 只下載原始碼、不去跑它過舊的 CMakeLists (會被新版 CMake 拒絕)。我們自己建 target。
FetchContent_Declare(
doctest
GIT_REPOSITORY https://github.com/doctest/doctest.git
GIT_TAG v2.4.11
SOURCE_SUBDIR do-not-build)
FetchContent_MakeAvailable(doctest)

add_library(doctest_header INTERFACE)
target_include_directories(doctest_header INTERFACE ${doctest_SOURCE_DIR})

find_package(Boost 1.74.0 REQUIRED CONFIG COMPONENTS system)

# --- 建立函式庫和 header files link ---
Expand Down Expand Up @@ -98,6 +110,28 @@ target_link_libraries(
# Ensure C++20 for coroutines
target_compile_features(matching_engine_lib INTERFACE cxx_std_20)

# --- Data Structure Playground (Header-Only) ---
# ds/ 是 header-only 的資料結構練習場,對應 rust 的 src/ds/。
# 每個結構一個資料夾 (tree/, graph/, ...),src/ds/ds.hpp 是 umbrella header。
add_library(ds_lib INTERFACE)

target_include_directories(ds_lib INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/src/ds)

target_compile_features(ds_lib INTERFACE cxx_std_${CMAKE_CXX_STANDARD})

# 把每個結構旁邊的 *_test.cpp 全部 GLOB 進一個 ds_tests 執行檔,
# 用 doctest 提供的 main,掛上 ctest → `ctest` 就能一鍵全跑 (= cargo test)。
enable_testing()

file(GLOB_RECURSE DS_TEST_FILES CONFIGURE_DEPENDS src/ds/*_test.cpp)

if(DS_TEST_FILES)
add_executable(ds_tests src/ds/ds_test_main.cpp ${DS_TEST_FILES})
target_link_libraries(ds_tests PRIVATE ds_lib doctest_header fmt::fmt)
add_test(NAME ds_tests COMMAND ds_tests)
endif()

# --- 建立執行檔與連結函式庫 ---
file(GLOB BIN_FILES src/bin/*.cpp)

Expand Down
60 changes: 60 additions & 0 deletions src/ds/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# ds — 資料結構練習場

對應 Rust playground 的 `src/ds/`。每個資料結構一個資料夾,header-only 實作 +
旁邊的 doctest 測試,`ctest` 一鍵全跑(≈ `cargo test`)。

## 結構

```
src/ds/
├── ds.hpp # umbrella header(≈ rust 的 ds/mod.rs),#include 全部結構
├── ds_test_main.cpp # 唯一定義 doctest main 的 TU,別動
├── tree/ # ✅ BinarySearchTree —— 範例,已實作 + 測試
│ ├── tree.hpp
│ └── tree_test.cpp
├── graph/ # 🚧 骨架,待實作
├── trie/ # 🚧 骨架,待實作
└── list/ # 🚧 骨架,待實作
```

## 跑測試

一定要在 nix 環境裡(`direnv` 進目錄會自動載入,或前綴 `direnv exec .`):

```bash
cmake -S . -B build # 第一次 / CMakeLists 改過才需要
cmake --build build --target ds_tests
ctest --test-dir build --output-on-failure
```

只跑某個結構:

```bash
./build/ds_tests --test-case="*BST*" # doctest 的過濾語法
```

## 新增一個資料結構

1. 開資料夾,寫 `xxx/xxx.hpp`(header-only 實作)。
2. 旁邊寫 `xxx/xxx_test.cpp`,用 doctest 的 `TEST_CASE` / `CHECK` / `REQUIRE`:

```cpp
#include <doctest/doctest.h>
#include "xxx/xxx.hpp"

TEST_CASE("xxx: 做了什麼") {
ds::Xxx<int> x;
CHECK(x.empty());
}
```

3. (可選)在 `ds.hpp` 加一行 `#include "xxx/xxx.hpp"`。
4. 直接 build + ctest。`CMakeLists.txt` 用 `GLOB_RECURSE ... CONFIGURE_DEPENDS`
自動收 `*_test.cpp`,不用改 CMake。

## 骨架說明

`graph/` `trie/` `list/` 目前是空殼:header 只有 class 外形 + 預期 API 的 TODO,
測試是 `TEST_CASE(... * doctest::skip())` 先被跳過(所以 ctest 仍是綠的)。
動手時把實作補進 header、把測試的 `* doctest::skip()` 拿掉再填內容即可。
```
10 changes: 10 additions & 0 deletions src/ds/ds.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

// Umbrella header for the data-structure playground (對應 rust 的 ds/mod.rs)。
// 每新增一個資料結構,就在這裡 #include 它的 header,外部只要 #include "ds.hpp"
// 就能拿到全部。測試檔則各自 include 自己那一個即可。

#include "graph/graph.hpp"
#include "list/list.hpp"
#include "tree/tree.hpp"
#include "trie/trie.hpp"
4 changes: 4 additions & 0 deletions src/ds/ds_test_main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// 唯一一個定義 doctest main 的 translation unit。其餘 *_test.cpp 只 include
// <doctest/doctest.h> 寫 TEST_CASE,連結到一起就組成單一的 ds_tests 執行檔。
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest/doctest.h>
24 changes: 24 additions & 0 deletions src/ds/graph/graph.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#pragma once

// 無向圖 (adjacency list)。骨架而已,實作留給你。
//
// 建議的 API(自己增刪):
// void add_edge(const T& a, const T& b);
// bool has_edge(const T& a, const T& b) const;
// std::vector<T> neighbors(const T& v) const;
// std::size_t num_vertices() const;
//
// 建議的儲存結構:std::unordered_map<T, std::vector<T>> adj_;

namespace ds {

template <typename T>
class Graph {
public:
// TODO: 你的 public API

private:
// TODO: 你的儲存結構
};

} // namespace ds
8 changes: 8 additions & 0 deletions src/ds/graph/graph_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <doctest/doctest.h>

#include "graph/graph.hpp"

// 拿掉 doctest::skip() 之後就會被 ctest 跑到。先把實作寫好,再回來填測試。
TEST_CASE("graph: add_edge / has_edge" * doctest::skip()) {
// TODO: 你的測試
}
26 changes: 26 additions & 0 deletions src/ds/list/list.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

// 單向鏈結串列 (singly linked list)。骨架而已,實作留給你。
//
// 建議的 API(自己增刪):
// void push_front(const T& value);
// void push_back(const T& value);
// bool empty() const;
// std::size_t size() const;
// std::vector<T> to_vector() const; // 方便寫測試比對
//
// 建議的節點:struct Node { T value; std::unique_ptr<Node> next; };
// head_ 持有第一個節點,注意 unique_ptr 的所有權轉移。

namespace ds {

template <typename T>
class List {
public:
// TODO: 你的 public API

private:
// TODO: 你的 Node 結構與 head_ / size_
};

} // namespace ds
8 changes: 8 additions & 0 deletions src/ds/list/list_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <doctest/doctest.h>

#include "list/list.hpp"

// 拿掉 doctest::skip() 之後就會被 ctest 跑到。先把實作寫好,再回來填測試。
TEST_CASE("list: push_front / push_back / to_vector" * doctest::skip()) {
// TODO: 你的測試
}
72 changes: 72 additions & 0 deletions src/ds/tree/tree.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#pragma once

#include <memory>
#include <vector>

namespace ds {

// 一個最小的二元搜尋樹 (BST),header-only,當作 ds/ playground 的範例結構。
// 重點不在效能,而在「實作 + 旁邊 tree_test.cpp 測試 + ctest 一鍵跑」的流程。
template <typename T>
class BinarySearchTree {
public:
void insert(const T& value) { root_ = insert(std::move(root_), value); }

bool contains(const T& value) const {
const Node* cur = root_.get();
while (cur) {
if (value < cur->value) {
cur = cur->left.get();
} else if (cur->value < value) {
cur = cur->right.get();
} else {
return true;
}
}
return false;
}

// 中序走訪 → 由小到大的排序序列。
std::vector<T> inorder() const {
std::vector<T> out;
inorder(root_.get(), out);
return out;
}

std::size_t size() const { return size_; }
bool empty() const { return size_ == 0; }

private:
struct Node {
explicit Node(T v) : value(std::move(v)) {}
T value;
std::unique_ptr<Node> left;
std::unique_ptr<Node> right;
};

std::unique_ptr<Node> insert(std::unique_ptr<Node> node, const T& value) {
if (!node) {
++size_;
return std::make_unique<Node>(value);
}
if (value < node->value) {
node->left = insert(std::move(node->left), value);
} else if (node->value < value) {
node->right = insert(std::move(node->right), value);
}
// 相等 → 視為已存在,不重複插入。
return node;
}

void inorder(const Node* node, std::vector<T>& out) const {
if (!node) return;
inorder(node->left.get(), out);
out.push_back(node->value);
inorder(node->right.get(), out);
}

std::unique_ptr<Node> root_;
std::size_t size_ = 0;
};

} // namespace ds
36 changes: 36 additions & 0 deletions src/ds/tree/tree_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include <doctest/doctest.h>

#include <vector>

#include "tree/tree.hpp"

TEST_CASE("BST: insert 後 contains 找得到") {
ds::BinarySearchTree<int> bst;
CHECK(bst.empty());

bst.insert(5);
bst.insert(3);
bst.insert(8);

CHECK(bst.size() == 3);
CHECK(bst.contains(5));
CHECK(bst.contains(3));
CHECK(bst.contains(8));
CHECK_FALSE(bst.contains(42));
}

TEST_CASE("BST: 重複插入不會增加 size") {
ds::BinarySearchTree<int> bst;
bst.insert(1);
bst.insert(1);
bst.insert(1);
CHECK(bst.size() == 1);
}

TEST_CASE("BST: 中序走訪是排序好的") {
ds::BinarySearchTree<int> bst;
for (int v : {5, 3, 8, 1, 4, 7, 9}) bst.insert(v);

std::vector<int> expected{1, 3, 4, 5, 7, 8, 9};
CHECK(bst.inorder() == expected);
}
23 changes: 23 additions & 0 deletions src/ds/trie/trie.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

// 前綴樹 (Trie),存字串。骨架而已,實作留給你。
//
// 建議的 API(自己增刪):
// void insert(const std::string& word);
// bool contains(const std::string& word) const; // 完整單字
// bool starts_with(const std::string& prefix) const; // 任意前綴
//
// 建議的節點:每個 Node 有 children (例如 std::array<unique_ptr<Node>, 26>
// 或 std::unordered_map<char, unique_ptr<Node>>) 與 is_end 旗標。

namespace ds {

class Trie {
public:
// TODO: 你的 public API

private:
// TODO: 你的 Node 結構與 root_
};

} // namespace ds
8 changes: 8 additions & 0 deletions src/ds/trie/trie_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <doctest/doctest.h>

#include "trie/trie.hpp"

// 拿掉 doctest::skip() 之後就會被 ctest 跑到。先把實作寫好,再回來填測試。
TEST_CASE("trie: insert / contains / starts_with" * doctest::skip()) {
// TODO: 你的測試
}