Skip to content
Merged
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
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ find_package(fmt CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
find_package(Boost 1.74.0 REQUIRED CONFIG COMPONENTS system)

# --- Tests (enables ctest for any subproject that calls add_test) ---
enable_testing()

# --- Subprojects ---
# Each subdirectory defines its own targets and exposes them by name.
# Order matters only when one subdir consumes targets from another:
# bin/ links pg_lib and matching_engine_lib, so libraries come first.
add_subdirectory(projects/matching_engine)
add_subdirectory(projects/ds)
add_subdirectory(pg)
add_subdirectory(bin)
1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
boost
fmt
nlohmann_json
doctest
];

shellHook = ''
Expand Down
26 changes: 26 additions & 0 deletions projects/ds/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# =============================================================================
# ds — header-only 資料結構練習場 (對應 rust 的 src/ds)。
#
# - ds_lib 是 INTERFACE library,public headers 放在 include/ds/...,
# 消費端寫 #include <ds/tree/tree.hpp>,include 路徑停在 include/。
# - 每個結構旁邊在 tests/ 放一支 *_test.cpp,全部 GLOB 進單一 ds_tests
# 執行檔(doctest),掛上 ctest → `ctest` 就能一鍵全跑 (≈ cargo test)。
# - doctest 由 nix flake 提供 (find_package),與其他相依一致。
# =============================================================================
cmake_minimum_required(VERSION 3.24)
project(ds)

add_library(ds_lib INTERFACE)
target_include_directories(ds_lib INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_compile_features(ds_lib INTERFACE cxx_std_20)

find_package(doctest CONFIG REQUIRED)

# tests/ 下的 *_test.cpp + 唯一的 doctest main TU → 單一 ds_tests。
file(GLOB_RECURSE DS_TEST_FILES CONFIGURE_DEPENDS tests/*_test.cpp)

if(DS_TEST_FILES)
add_executable(ds_tests tests/ds_test_main.cpp ${DS_TEST_FILES})
target_link_libraries(ds_tests PRIVATE ds_lib doctest::doctest)
add_test(NAME ds_tests COMMAND ds_tests)
endif()
67 changes: 67 additions & 0 deletions projects/ds/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# ds — 資料結構練習場

對應 Rust playground 的 `src/ds/`。header-only 實作 + doctest 測試,
`ctest` 一鍵全跑(≈ `cargo test`)。與 `projects/matching_engine` 同構:
public headers 在 `include/ds/...`,測試在 `tests/`。

## 結構

```
projects/ds/
├── CMakeLists.txt
├── include/ds/
│ ├── ds.hpp # umbrella header(≈ rust 的 ds/mod.rs)
│ ├── tree/tree.hpp # ✅ BinarySearchTree —— 範例,已實作
│ ├── graph/graph.hpp # 🚧 骨架,待實作
│ ├── trie/trie.hpp # 🚧 骨架,待實作
│ └── list/list.hpp # 🚧 骨架,待實作
└── tests/
├── ds_test_main.cpp # 唯一定義 doctest main 的 TU,別動
├── tree_test.cpp # ✅ 對應 BST 的測試
├── graph_test.cpp # 🚧 skip() 跳過,待填
├── trie_test.cpp # 🚧
└── list_test.cpp # 🚧
```

消費端一律寫 `#include <ds/tree/tree.hpp>`(include 路徑停在 `include/`)。

## 跑測試

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

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

只跑某個結構:

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

## 新增一個資料結構

1. 寫 `include/ds/xxx/xxx.hpp`(header-only 實作)。
2. 在 `tests/xxx_test.cpp` 用 doctest 寫 `TEST_CASE` / `CHECK` / `REQUIRE`:

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

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

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

## 骨架說明

`graph/` `trie/` `list/` 目前是空殼:header 只有 class 外形 + 預期 API 的 TODO,
測試是 `TEST_CASE(... * doctest::skip())` 先被跳過(所以 ctest 仍是綠的)。
動手時把實作補進 header、把測試的 `* doctest::skip()` 拿掉再填內容即可。
10 changes: 10 additions & 0 deletions projects/ds/include/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/ds.hpp>
// 就能拿到全部。測試檔則各自 include 自己那一個即可。

#include <ds/graph/graph.hpp>
#include <ds/list/list.hpp>
#include <ds/tree/tree.hpp>
#include <ds/trie/trie.hpp>
24 changes: 24 additions & 0 deletions projects/ds/include/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
26 changes: 26 additions & 0 deletions projects/ds/include/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
72 changes: 72 additions & 0 deletions projects/ds/include/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 的範例結構。
// 重點不在效能,而在「實作 + tests/ 下的 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
23 changes: 23 additions & 0 deletions projects/ds/include/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
4 changes: 4 additions & 0 deletions projects/ds/tests/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>
8 changes: 8 additions & 0 deletions projects/ds/tests/graph_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <doctest/doctest.h>

#include <ds/graph/graph.hpp>

// 拿掉 doctest::skip() 之後就會被 ctest 跑到。先把實作寫好,再回來填測試。
TEST_CASE("graph: add_edge / has_edge" * doctest::skip()) {
// TODO: 你的測試
}
8 changes: 8 additions & 0 deletions projects/ds/tests/list_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <doctest/doctest.h>

#include <ds/list/list.hpp>

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

#include <vector>

#include <ds/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);
}
8 changes: 8 additions & 0 deletions projects/ds/tests/trie_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <doctest/doctest.h>

#include <ds/trie/trie.hpp>

// 拿掉 doctest::skip() 之後就會被 ctest 跑到。先把實作寫好,再回來填測試。
TEST_CASE("trie: insert / contains / starts_with" * doctest::skip()) {
// TODO: 你的測試
}
3 changes: 3 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import orderbook_py
print(orderbook_py.OrderBook())