From 39a1c32a249bdb52c9ff847b10636aeb0394b890 Mon Sep 17 00:00:00 2001 From: Dave Amiana Date: Sun, 12 Jul 2026 23:35:23 +0800 Subject: [PATCH 01/13] adding semgrep rules and conf for scanning memory vulnerability and common anti-patterns --- .semgrep/.semgrepignore | 6 +++ .semgrep/rules/correctness.yml | 74 ++++++++++++++++++++++++++++++++ .semgrep/rules/memory-safety.yml | 54 +++++++++++++++++++++++ .semgrep/rules/security.yml | 62 ++++++++++++++++++++++++++ 4 files changed, 196 insertions(+) create mode 100644 .semgrep/.semgrepignore create mode 100644 .semgrep/rules/correctness.yml create mode 100644 .semgrep/rules/memory-safety.yml create mode 100644 .semgrep/rules/security.yml diff --git a/.semgrep/.semgrepignore b/.semgrep/.semgrepignore new file mode 100644 index 0000000..9e034d0 --- /dev/null +++ b/.semgrep/.semgrepignore @@ -0,0 +1,6 @@ +build/ +lib/ +docs_sphinx/_build/ +docs_doxy/ +.git/ +.vscode/ diff --git a/.semgrep/rules/correctness.yml b/.semgrep/rules/correctness.yml new file mode 100644 index 0000000..77cb895 --- /dev/null +++ b/.semgrep/rules/correctness.yml @@ -0,0 +1,74 @@ +rules: + - id: cpp-double-assignment + patterns: + - pattern: | + $VAR = $VAL1; + ... + $VAR = $VAL2; + - pattern-not: | + $VAR = $VAL1; + ... + $VAR = $VAL1; + message: | + Consecutive assignments to '$VAR' detected. The second assignment + overwrites the first, which is almost certainly a bug. Review the + logic to determine if one of the assignments should be removed or + if a different variable was intended. + languages: [cpp] + severity: ERROR + metadata: + category: correctness + cwe: "CWE-563: Assignment to Variable without Use" + confidence: HIGH + + - id: cpp-wrong-branch-condition + patterns: + - pattern: | + while ($NODE->left != $NIL) + $NODE = $NODE->right; + - pattern-not: | + while ($NODE->right != $NIL) + $NODE = $NODE->right; + message: | + Loop condition checks '$NODE->left' but the loop body advances + '$NODE->right'. This is likely a bug — the condition should + probably check '$NODE->right' instead. + languages: [cpp] + severity: ERROR + metadata: + category: correctness + cwe: "CWE-670: Always-Incorrect Control Flow Implementation" + confidence: HIGH + + - id: cpp-constructor-self-assign + pattern: | + $PTR = this; + message: | + Assigning 'this' to a raw pointer inside a constructor body. If + this pointer is later overwritten, the self-assignment is dead code + and likely indicates a logic error. Consider using the member + initializer list instead. + languages: [cpp] + severity: ERROR + metadata: + category: correctness + cwe: "CWE-563: Assignment to Variable without Use" + confidence: MEDIUM + + - id: cpp-nil-parent-set-nullptr + patterns: + - pattern: | + $NIL->parent = nullptr; + - metavariable-comparison: + metavariable: $NIL + comparison: $NIL == "NIL" + message: | + Setting NIL->parent to nullptr may break invariants if other code + checks 'parent == NIL'. This can cause null pointer dereferences + when traversing parent pointers. + languages: [cpp] + severity: ERROR + metadata: + category: correctness + cwe: "CWE-476: NULL Pointer Dereference" + confidence: MEDIUM diff --git a/.semgrep/rules/memory-safety.yml b/.semgrep/rules/memory-safety.yml new file mode 100644 index 0000000..737bcac --- /dev/null +++ b/.semgrep/rules/memory-safety.yml @@ -0,0 +1,54 @@ +rules: + - id: cpp-raw-new-without-raii + patterns: + - pattern: | + $TYPE * $VAR = new $T(...); + - pattern-not-inside: | + std::make_unique<...>(...) + - pattern-not-inside: | + std::make_shared<...>(...) + message: | + Raw 'new' without a RAII wrapper (std::unique_ptr or + std::shared_ptr). Raw owning pointers are error-prone and can + cause memory leaks. Prefer std::make_unique or std::make_shared. + languages: [cpp] + severity: WARNING + metadata: + category: memory-safety + cwe: "CWE-401: Memory Leak" + confidence: MEDIUM + references: + - https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#r3-prefer-smart-pointers-to-raw-pointers + + - id: cpp-deref-after-delete + patterns: + - pattern: | + delete $PTR; + ... + ... $PTR ...; + - pattern: | + delete[] $PTR; + ... + ... $PTR ...; + message: | + Use of '$PTR' after delete. Accessing a pointer after it has been + freed leads to undefined behavior (use-after-free). + languages: [cpp] + severity: ERROR + metadata: + category: memory-safety + cwe: "CWE-416: Use After Free" + confidence: HIGH + + - id: cpp-return-local-address + pattern: return &$LOCAL; + message: | + Returning address of local variable '$LOCAL'. The pointer becomes + dangling as soon as the function returns, leading to undefined + behavior. + languages: [cpp] + severity: ERROR + metadata: + category: memory-safety + cwe: "CWE-562: Return of Stack Variable Address" + confidence: HIGH diff --git a/.semgrep/rules/security.yml b/.semgrep/rules/security.yml new file mode 100644 index 0000000..be7340f --- /dev/null +++ b/.semgrep/rules/security.yml @@ -0,0 +1,62 @@ +rules: + - id: cpp-exit-in-catch + pattern: | + catch (...) { + ... + exit(...); + } + message: | + Calling exit() inside a catch block terminates the process without + unwinding the stack or cleaning up resources. Prefer throwing an + exception or returning an error code. + languages: [cpp] + severity: WARNING + metadata: + category: security + cwe: "CWE-754: Improper Check for Unusual or Exceptional Conditions" + confidence: HIGH + + - id: cpp-exit-in-catch-specific + pattern: | + catch ($TYPE $E) { + ... + exit(...); + } + message: | + Calling exit() inside a catch block terminates the process without + unwinding the stack or cleaning up resources. Prefer throwing an + exception or returning an error code. + languages: [cpp] + severity: WARNING + metadata: + category: security + cwe: "CWE-754: Improper Check for Unusual or Exceptional Conditions" + confidence: HIGH + + - id: cpp-c-style-cast + pattern: ($TYPE) $EXPR + message: | + C-style cast detected. Prefer C++ casts (static_cast, const_cast, + reinterpret_cast) for type safety and better error diagnostics. + languages: [cpp] + severity: WARNING + metadata: + category: security + cwe: "CWE-704: Incorrect Type Conversion" + confidence: HIGH + references: + - https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es48-avoid-casts + + - id: cpp-exception-by-value + pattern: catch ($TYPE $E) { ... } + message: | + Catching exception by value may slice derived exceptions. Catch + by reference instead: catch (const $TYPE& $E). + languages: [cpp] + severity: WARNING + metadata: + category: security + cwe: "CWE-398: Code Quality" + confidence: MEDIUM + references: + - https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Re-catching From fb8cddeccf164752a5be04eb32f8c5270b95e4bd Mon Sep 17 00:00:00 2001 From: Dave Amiana Date: Sun, 12 Jul 2026 23:37:54 +0800 Subject: [PATCH 02/13] adding semgrep build routine on ci workflows and fixes on docs build conf --- .github/workflows/cpp.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index 858e34c..4799d61 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -10,7 +10,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: git run: git --version - name: configure @@ -20,6 +20,7 @@ jobs: pip3 install breathe pip3 install pydata-sphinx-theme pip3 install sphinx-sitemap + pip3 install semgrep - name: gtest run: | sudo apt-get install libgtest-dev @@ -43,12 +44,12 @@ jobs: run: | cd ${{github.workspace}}/build ctest --output-on-failure - - name: checkout repo - uses: actions/checkout@1.0.0 + - name: semgrep + run: | + semgrep scan --config .semgrep/rules/ src/ include/ test/ - name: build docs run: | cd docs_sphinx - doxygen Doxyfile.in make html cd _build/html touch .nojekyll From cbf5cbe6428e9153a9066fcd05c219f99923aaf6 Mon Sep 17 00:00:00 2001 From: Dave Amiana Date: Sun, 12 Jul 2026 23:38:15 +0800 Subject: [PATCH 03/13] fixing docs issue to render apidoc --- docs_sphinx/api/cpp_doxygen_sphinx.rst | 3 ++- docs_sphinx/api/index.rst | 1 - docs_sphinx/conf.py | 2 -- docs_sphinx/index.rst | 2 ++ 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs_sphinx/api/cpp_doxygen_sphinx.rst b/docs_sphinx/api/cpp_doxygen_sphinx.rst index e7b1310..d3ef9f6 100644 --- a/docs_sphinx/api/cpp_doxygen_sphinx.rst +++ b/docs_sphinx/api/cpp_doxygen_sphinx.rst @@ -2,5 +2,6 @@ TreeSet API ============= + .. doxygenfile:: TreeSet.hpp - :project: C++ Sphinx Doxygen Breathe + :project: C++ Sphinx Doxygen Breathe diff --git a/docs_sphinx/api/index.rst b/docs_sphinx/api/index.rst index 154e5d7..6cb5599 100644 --- a/docs_sphinx/api/index.rst +++ b/docs_sphinx/api/index.rst @@ -4,7 +4,6 @@ API === .. toctree:: - :maxdepth: 2 :glob: diff --git a/docs_sphinx/conf.py b/docs_sphinx/conf.py index b0f46a2..16b64ba 100644 --- a/docs_sphinx/conf.py +++ b/docs_sphinx/conf.py @@ -14,9 +14,7 @@ # import os # import sys # sys.path.insert(0, os.path.abspath('.')) -from sphinx.builders.html import StandaloneHTMLBuilder import subprocess -import os # Doxygen subprocess.call('doxygen Doxyfile.in', shell=True) diff --git a/docs_sphinx/index.rst b/docs_sphinx/index.rst index 2a0f256..a8a2478 100644 --- a/docs_sphinx/index.rst +++ b/docs_sphinx/index.rst @@ -10,6 +10,8 @@ Welcome to TreeSet's documentation! :maxdepth: 2 :caption: Contents: + api/index + .. image:: ../img/TreeSetLogo.png :alt: logo :align: center From 86d0a79666b34ead52a81bad15041ee9ea7a8001 Mon Sep 17 00:00:00 2001 From: Dave Amiana Date: Mon, 13 Jul 2026 00:40:26 +0800 Subject: [PATCH 04/13] moved back src impl to headerfiles for simplicity and test against defiend blackbox scenarios for helper functions and rbt correctness --- include/TreeSet.hpp | 835 ++++++++++++++++++++++++++++++++++++++++++-- src/TreeSet.cpp | 355 ------------------- test/unittest.cpp | 581 ++++++++++++++++++++++++++++++ 3 files changed, 1393 insertions(+), 378 deletions(-) diff --git a/include/TreeSet.hpp b/include/TreeSet.hpp index dc74237..06817c7 100644 --- a/include/TreeSet.hpp +++ b/include/TreeSet.hpp @@ -1,8 +1,10 @@ -#ifndef __TREESET_H__ -#define __TREESET_H__ +#ifndef TREESET_HPP +#define TREESET_HPP #include -#include +#include +#include +#include enum Color : bool { Red = true, @@ -13,23 +15,250 @@ template struct Node { Node *parent{nullptr}; Node *left{nullptr}; Node *right{nullptr}; - - Color color = Color::Red; + Color color{Color::Red}; T value{}; + Node() = default; + Node(const T &t_value, Node *t_parent) : parent(t_parent), value(t_value) {} + Node(const T &t_value, Color t_color) : color(t_color), value(t_value) {} + Node(const T &t_value, Color t_color, Node *t_parent, Node *t_left, + Node *t_right) + : parent(t_parent), left(t_left), right(t_right), color(t_color), + value(t_value) {} +}; + +struct inorder_t {}; +struct preorder_t {}; +struct postorder_t {}; +inline constexpr inorder_t inorder{}; +inline constexpr preorder_t preorder{}; +inline constexpr postorder_t postorder{}; + +template class TreeSet; + +template +class TreeSetIterator { public: - Node(const T &t_value, Node *t_parent) : value(t_value), parent(t_parent){}; - Node(const T &t_value, Color t_color) : value(t_value), color(t_color){}; - Node(const T &t_value, Color t_color, Node *t_parent, Node *t_left, - Node *t_right); + using value_type = T; + using reference = std::conditional_t &; + using pointer = std::conditional_t *; + using difference_type = std::ptrdiff_t; + using iterator_category = std::bidirectional_iterator_tag; + + TreeSetIterator() = default; + TreeSetIterator(Node *t_current, const TreeSet *t_tree, bool t_reverse) + : m_current(t_current), m_tree(t_tree), m_reverse(t_reverse) {} + + auto operator*() const -> reference { return m_current->value; } + auto operator->() const -> pointer { return &m_current->value; } + + auto operator++() -> TreeSetIterator & { + if (m_reverse) { + advance_backward(); + } else { + advance_forward(); + } + return *this; + } + + auto operator++(int) -> TreeSetIterator { + auto tmp = *this; + ++(*this); + return tmp; + } + + auto operator--() -> TreeSetIterator & { + if (m_reverse) { + advance_forward(); + } else { + advance_backward(); + } + return *this; + } + + auto operator--(int) -> TreeSetIterator { + auto tmp = *this; + --(*this); + return tmp; + } + + auto operator==(const TreeSetIterator &t_other) const -> bool { + return m_current == t_other.m_current; + } + + auto operator!=(const TreeSetIterator &t_other) const -> bool { + return m_current != t_other.m_current; + } + +private: + Node *m_current{nullptr}; + const TreeSet *m_tree{nullptr}; + bool m_reverse{false}; + + template friend class TreeSet; + + auto advance_forward() -> void; + auto advance_backward() -> void; }; -// RBT implementation +template +auto TreeSetIterator::advance_forward() -> void { + if constexpr (std::is_same_v) { + if (m_current->right != m_tree->m_nil) { + m_current = m_current->right; + while (m_current->left != m_tree->m_nil) { + m_current = m_current->left; + } + } else { + auto parent = m_current->parent; + while (parent != m_tree->m_nil && m_current == parent->right) { + m_current = parent; + parent = parent->parent; + } + m_current = parent; + } + } else if constexpr (std::is_same_v) { + if (m_current->left != m_tree->m_nil) { + m_current = m_current->left; + } else if (m_current->right != m_tree->m_nil) { + m_current = m_current->right; + } else { + auto parent = m_current->parent; + while (parent != m_tree->m_nil) { + if (m_current == parent->left && parent->right != m_tree->m_nil) { + m_current = parent->right; + return; + } + m_current = parent; + parent = parent->parent; + } + m_current = m_tree->m_nil; + } + } else if constexpr (std::is_same_v) { + auto parent = m_current->parent; + if (parent == m_tree->m_nil) { + m_current = m_tree->m_nil; + return; + } + if (m_current == parent->left && parent->right != m_tree->m_nil) { + m_current = parent->right; + while (true) { + if (m_current->left != m_tree->m_nil) { + m_current = m_current->left; + } else if (m_current->right != m_tree->m_nil) { + m_current = m_current->right; + } else { + break; + } + } + } else if (m_current == parent->left) { + m_current = parent; + } else { + m_current = parent; + } + } +} + +template +auto TreeSetIterator::advance_backward() -> void { + if constexpr (std::is_same_v) { + if (m_current == m_tree->m_nil) { + m_current = m_tree->m_root; + if (m_current != m_tree->m_nil) { + while (m_current->right != m_tree->m_nil) { + m_current = m_current->right; + } + } + } else if (m_current->left != m_tree->m_nil) { + m_current = m_current->left; + while (m_current->right != m_tree->m_nil) { + m_current = m_current->right; + } + } else { + auto parent = m_current->parent; + while (parent != m_tree->m_nil && m_current == parent->left) { + m_current = parent; + parent = parent->parent; + } + m_current = parent; + } + } else if constexpr (std::is_same_v) { + if (m_current == m_tree->m_nil) { + m_current = m_tree->m_root; + if (m_current == m_tree->m_nil) { + return; + } + while (true) { + if (m_current->right != m_tree->m_nil) { + m_current = m_current->right; + } else if (m_current->left != m_tree->m_nil) { + m_current = m_current->left; + } else { + break; + } + } + } else { + auto parent = m_current->parent; + if (parent == m_tree->m_nil) { + m_current = m_tree->m_nil; + return; + } + if (m_current == parent->left) { + m_current = parent; + } else { + if (parent->left != m_tree->m_nil) { + m_current = parent->left; + while (true) { + if (m_current->right != m_tree->m_nil) { + m_current = m_current->right; + } else if (m_current->left != m_tree->m_nil) { + m_current = m_current->left; + } else { + break; + } + } + } else { + m_current = parent; + } + } + } + } else if constexpr (std::is_same_v) { + if (m_current == m_tree->m_nil) { + m_current = m_tree->m_root; + return; + } + if (m_current->right != m_tree->m_nil) { + m_current = m_current->right; + return; + } + if (m_current->left != m_tree->m_nil) { + m_current = m_current->left; + return; + } + while (true) { + auto parent = m_current->parent; + if (parent == m_tree->m_nil) { + m_current = m_tree->m_nil; + return; + } + if (m_current == parent->right && parent->left != m_tree->m_nil) { + m_current = parent->left; + return; + } + m_current = parent; + } + } +} + template class TreeSet { public: TreeSet(); + ~TreeSet(); - auto insert(const T &t_value = T{}) -> bool; + TreeSet(const TreeSet &) = delete; + auto operator=(const TreeSet &) -> TreeSet & = delete; + + auto insert(const T &t_value) -> bool; auto remove(const T &t_value) -> bool; auto size() const noexcept -> std::size_t; @@ -37,12 +266,37 @@ template class TreeSet { auto is_empty() const noexcept -> bool; auto is_element(const T &t_value) const noexcept -> bool; - auto root() const noexcept -> Node; - auto min(Node *t_node) const noexcept -> Node; - auto max(Node *t_node) const noexcept -> Node; + auto root() noexcept -> Node *; + auto min(Node *t_node) noexcept -> Node *; + auto max(Node *t_node) noexcept -> Node *; + + auto begin() noexcept -> TreeSetIterator; + auto end() noexcept -> TreeSetIterator; + auto rbegin() noexcept -> TreeSetIterator; + auto rend() noexcept -> TreeSetIterator; + + auto cbegin() const noexcept -> TreeSetIterator; + auto cend() const noexcept -> TreeSetIterator; + auto crbegin() const noexcept -> TreeSetIterator; + auto crend() const noexcept -> TreeSetIterator; - template - friend auto operator<<(std::ostream &os, const Node *t_root); + template + auto begin() noexcept -> TreeSetIterator; + template + auto end() noexcept -> TreeSetIterator; + template + auto rbegin() noexcept -> TreeSetIterator; + template + auto rend() noexcept -> TreeSetIterator; + + template + auto cbegin() const noexcept -> TreeSetIterator; + template + auto cend() const noexcept -> TreeSetIterator; + template + auto crbegin() const noexcept -> TreeSetIterator; + template + auto crend() const noexcept -> TreeSetIterator; private: auto fix_insertion_at(Node *t_node) -> void; @@ -51,18 +305,553 @@ template class TreeSet { auto fix_delete(Node *t_node) -> void; auto successor(Node *t_node) -> Node *; - auto search(const T &t_value, Node *t_node) -> Node *; - auto height(Node *t_node) -> std::size_t; + auto predecessor(Node *t_node) -> Node *; + auto search(const T &t_value, Node *t_node) const -> Node *; + auto height(Node *t_node) const -> std::size_t; - auto transplant(Node *u, Node *v) -> void; - auto swap_color(Node *a, Node *b) -> void; + auto transplant(Node *t_u, Node *t_v) -> void; + auto swap_color(Node *t_a, Node *t_b) -> void; auto update_parent_child_link(Node *t_parent, Node *t_old_child, Node *t_new_child) -> void; + auto destroy(Node *t_node) -> void; + auto leftmost() const -> Node *; + auto rightmost() const -> Node *; + auto first_postorder() const -> Node *; + auto last_preorder() const -> Node *; private: - Node *m_root{nullptr}; // should I change this? - std::unique_ptr> NIL{nullptr}; + Node *m_nil{nullptr}; + Node *m_root{nullptr}; std::size_t node_count{0}; + + template + friend class TreeSetIterator; }; -#endif // __TREESET_H__ \ No newline at end of file +template +TreeSet::TreeSet() + : m_nil(new Node(T{}, Color::Black, nullptr, nullptr, nullptr)), + m_root(m_nil) { + m_nil->left = m_nil->right = m_nil->parent = m_nil; +} + +template TreeSet::~TreeSet() { + destroy(m_root); + delete m_nil; +} + +template auto TreeSet::destroy(Node *t_node) -> void { + if (t_node != m_nil) { + destroy(t_node->left); + destroy(t_node->right); + delete t_node; + } +} + +template auto TreeSet::insert(const T &t_value) -> bool { + auto *z = new Node(t_value, Color::Red, m_nil, m_nil, m_nil); + auto *y = m_nil; + auto *x = m_root; + + while (x != m_nil) { + y = x; + if (t_value < x->value) { + x = x->left; + } else if (x->value < t_value) { + x = x->right; + } else { + delete z; + return false; + } + } + + z->parent = y; + if (y == m_nil) { + m_root = z; + } else if (t_value < y->value) { + y->left = z; + } else { + y->right = z; + } + + fix_insertion_at(z); + ++node_count; + return true; +} + +template auto TreeSet::remove(const T &t_value) -> bool { + auto *z = search(t_value, m_root); + if (z == m_nil) { + return false; + } + + auto *y = z; + auto y_original_color = y->color; + Node *x = nullptr; + + if (z->left == m_nil) { + x = z->right; + transplant(z, z->right); + } else if (z->right == m_nil) { + x = z->left; + transplant(z, z->left); + } else { + y = z->right; + while (y->left != m_nil) { + y = y->left; + } + y_original_color = y->color; + x = y->right; + if (y->parent == z) { + x->parent = y; + } else { + transplant(y, y->right); + y->right = z->right; + y->right->parent = y; + } + transplant(z, y); + y->left = z->left; + y->left->parent = y; + y->color = z->color; + } + + delete z; + --node_count; + + if (y_original_color == Color::Black) { + fix_delete(x); + } + return true; +} + +template auto TreeSet::successor(Node *t_node) -> Node * { + if (t_node->right != m_nil) { + auto *node = t_node->right; + while (node->left != m_nil) { + node = node->left; + } + return node; + } + auto *parent = t_node->parent; + while (parent != m_nil && t_node == parent->right) { + t_node = parent; + parent = parent->parent; + } + return parent; +} + +template +auto TreeSet::predecessor(Node *t_node) -> Node * { + if (t_node->left != m_nil) { + auto *node = t_node->left; + while (node->right != m_nil) { + node = node->right; + } + return node; + } + auto *parent = t_node->parent; + while (parent != m_nil && t_node == parent->left) { + t_node = parent; + parent = parent->parent; + } + return parent; +} + +template +auto TreeSet::search(const T &t_value, Node *t_node) const -> Node * { + if (t_node == m_nil) { + return m_nil; + } + if (t_value == t_node->value) { + return t_node; + } + if (t_value < t_node->value) { + return search(t_value, t_node->left); + } + return search(t_value, t_node->right); +} + +template +auto TreeSet::height(Node *t_node) const -> std::size_t { + if (t_node == m_nil) { + return 0; + } + if (t_node->left == m_nil && t_node->right == m_nil) { + return 1; + } + auto left_h = height(t_node->left); + auto right_h = height(t_node->right); + return (left_h > right_h ? left_h : right_h) + 1; +} + +template +auto TreeSet::fix_insertion_at(Node *t_node) -> void { + while (t_node->parent->color == Color::Red) { + if (t_node->parent == t_node->parent->parent->left) { + auto *uncle = t_node->parent->parent->right; + if (uncle->color == Color::Red) { + t_node->parent->color = Color::Black; + uncle->color = Color::Black; + t_node->parent->parent->color = Color::Red; + t_node = t_node->parent->parent; + } else { + if (t_node == t_node->parent->right) { + t_node = t_node->parent; + left_rotate(t_node); + } + t_node->parent->color = Color::Black; + t_node->parent->parent->color = Color::Red; + right_rotate(t_node->parent->parent); + } + } else { + auto *uncle = t_node->parent->parent->left; + if (uncle->color == Color::Red) { + t_node->parent->color = Color::Black; + uncle->color = Color::Black; + t_node->parent->parent->color = Color::Red; + t_node = t_node->parent->parent; + } else { + if (t_node == t_node->parent->left) { + t_node = t_node->parent; + right_rotate(t_node); + } + t_node->parent->color = Color::Black; + t_node->parent->parent->color = Color::Red; + left_rotate(t_node->parent->parent); + } + } + } + m_root->color = Color::Black; +} + +template auto TreeSet::left_rotate(Node *t_node) -> void { + auto *y = t_node->right; + t_node->right = y->left; + if (y->left != m_nil) { + y->left->parent = t_node; + } + y->parent = t_node->parent; + if (t_node->parent == m_nil) { + m_root = y; + } else if (t_node == t_node->parent->left) { + t_node->parent->left = y; + } else { + t_node->parent->right = y; + } + y->left = t_node; + t_node->parent = y; +} + +template auto TreeSet::right_rotate(Node *t_node) -> void { + auto *y = t_node->left; + t_node->left = y->right; + if (y->right != m_nil) { + y->right->parent = t_node; + } + y->parent = t_node->parent; + if (t_node->parent == m_nil) { + m_root = y; + } else if (t_node == t_node->parent->right) { + t_node->parent->right = y; + } else { + t_node->parent->left = y; + } + y->right = t_node; + t_node->parent = y; +} + +template +auto TreeSet::transplant(Node *t_u, Node *t_v) -> void { + if (t_u->parent == m_nil) { + m_root = t_v; + } else if (t_u == t_u->parent->left) { + t_u->parent->left = t_v; + } else { + t_u->parent->right = t_v; + } + t_v->parent = t_u->parent; +} + +template +auto TreeSet::swap_color(Node *t_a, Node *t_b) -> void { + std::swap(t_a->color, t_b->color); +} + +template +auto TreeSet::update_parent_child_link(Node *t_parent, + Node *t_old_child, + Node *t_new_child) -> void { + if (t_parent != m_nil) { + if (t_parent->left == t_old_child) { + t_parent->left = t_new_child; + } else { + t_parent->right = t_new_child; + } + } +} + +template auto TreeSet::fix_delete(Node *t_node) -> void { + while (t_node != m_root && t_node->color == Color::Black) { + if (t_node == t_node->parent->left) { + auto *w = t_node->parent->right; + if (w->color == Color::Red) { + w->color = Color::Black; + t_node->parent->color = Color::Red; + left_rotate(t_node->parent); + w = t_node->parent->right; + } + if (w->left->color == Color::Black && w->right->color == Color::Black) { + w->color = Color::Red; + t_node = t_node->parent; + } else { + if (w->right->color == Color::Black) { + w->left->color = Color::Black; + w->color = Color::Red; + right_rotate(w); + w = t_node->parent->right; + } + w->color = t_node->parent->color; + t_node->parent->color = Color::Black; + w->right->color = Color::Black; + left_rotate(t_node->parent); + t_node = m_root; + } + } else { + auto *w = t_node->parent->left; + if (w->color == Color::Red) { + w->color = Color::Black; + t_node->parent->color = Color::Red; + right_rotate(t_node->parent); + w = t_node->parent->left; + } + if (w->right->color == Color::Black && w->left->color == Color::Black) { + w->color = Color::Red; + t_node = t_node->parent; + } else { + if (w->left->color == Color::Black) { + w->right->color = Color::Black; + w->color = Color::Red; + left_rotate(w); + w = t_node->parent->left; + } + w->color = t_node->parent->color; + t_node->parent->color = Color::Black; + w->left->color = Color::Black; + right_rotate(t_node->parent); + t_node = m_root; + } + } + } + t_node->color = Color::Black; +} + +template auto TreeSet::leftmost() const -> Node * { + if (m_root == m_nil) { + return m_nil; + } + auto *node = m_root; + while (node->left != m_nil) { + node = node->left; + } + return node; +} + +template auto TreeSet::rightmost() const -> Node * { + if (m_root == m_nil) { + return m_nil; + } + auto *node = m_root; + while (node->right != m_nil) { + node = node->right; + } + return node; +} + +template auto TreeSet::first_postorder() const -> Node * { + if (m_root == m_nil) { + return m_nil; + } + auto *node = m_root; + while (true) { + if (node->left != m_nil) { + node = node->left; + } else if (node->right != m_nil) { + node = node->right; + } else { + break; + } + } + return node; +} + +template auto TreeSet::last_preorder() const -> Node * { + if (m_root == m_nil) { + return m_nil; + } + auto *node = m_root; + while (true) { + if (node->right != m_nil) { + node = node->right; + } else if (node->left != m_nil) { + node = node->left; + } else { + break; + } + } + return node; +} + +template auto TreeSet::size() const noexcept -> std::size_t { + return node_count; +} + +template auto TreeSet::height() const noexcept -> std::size_t { + return height(m_root); +} + +template auto TreeSet::is_empty() const noexcept -> bool { + return node_count == 0; +} + +template +auto TreeSet::is_element(const T &t_value) const noexcept -> bool { + return search(t_value, m_root) != m_nil; +} + +template auto TreeSet::root() noexcept -> Node * { + return m_root; +} + +template +auto TreeSet::min(Node *t_node) noexcept -> Node * { + while (t_node->left != m_nil) { + t_node = t_node->left; + } + return t_node; +} + +template +auto TreeSet::max(Node *t_node) noexcept -> Node * { + while (t_node->right != m_nil) { + t_node = t_node->right; + } + return t_node; +} + +template +auto TreeSet::begin() noexcept -> TreeSetIterator { + return TreeSetIterator(leftmost(), this, false); +} + +template +auto TreeSet::end() noexcept -> TreeSetIterator { + return TreeSetIterator(m_nil, this, false); +} + +template +auto TreeSet::rbegin() noexcept -> TreeSetIterator { + return TreeSetIterator(rightmost(), this, true); +} + +template +auto TreeSet::rend() noexcept -> TreeSetIterator { + return TreeSetIterator(m_nil, this, true); +} + +template +auto TreeSet::cbegin() const noexcept + -> TreeSetIterator { + return TreeSetIterator(leftmost(), this, false); +} + +template +auto TreeSet::cend() const noexcept -> TreeSetIterator { + return TreeSetIterator(m_nil, this, false); +} + +template +auto TreeSet::crbegin() const noexcept + -> TreeSetIterator { + return TreeSetIterator(rightmost(), this, true); +} + +template +auto TreeSet::crend() const noexcept -> TreeSetIterator { + return TreeSetIterator(m_nil, this, true); +} + +template +template +auto TreeSet::begin() noexcept -> TreeSetIterator { + if constexpr (std::is_same_v) { + return TreeSetIterator(m_root, this, false); + } else if constexpr (std::is_same_v) { + return TreeSetIterator(first_postorder(), this, false); + } else { + return TreeSetIterator(leftmost(), this, false); + } +} + +template +template +auto TreeSet::end() noexcept -> TreeSetIterator { + return TreeSetIterator(m_nil, this, false); +} + +template +template +auto TreeSet::rbegin() noexcept -> TreeSetIterator { + if constexpr (std::is_same_v) { + return TreeSetIterator(last_preorder(), this, true); + } else if constexpr (std::is_same_v) { + return TreeSetIterator(m_root, this, true); + } else { + return TreeSetIterator(rightmost(), this, true); + } +} + +template +template +auto TreeSet::rend() noexcept -> TreeSetIterator { + return TreeSetIterator(m_nil, this, true); +} + +template +template +auto TreeSet::cbegin() const noexcept + -> TreeSetIterator { + if constexpr (std::is_same_v) { + return TreeSetIterator(m_root, this, false); + } else if constexpr (std::is_same_v) { + return TreeSetIterator(first_postorder(), this, false); + } else { + return TreeSetIterator(leftmost(), this, false); + } +} + +template +template +auto TreeSet::cend() const noexcept -> TreeSetIterator { + return TreeSetIterator(m_nil, this, false); +} + +template +template +auto TreeSet::crbegin() const noexcept + -> TreeSetIterator { + if constexpr (std::is_same_v) { + return TreeSetIterator(last_preorder(), this, true); + } else if constexpr (std::is_same_v) { + return TreeSetIterator(m_root, this, true); + } else { + return TreeSetIterator(rightmost(), this, true); + } +} + +template +template +auto TreeSet::crend() const noexcept -> TreeSetIterator { + return TreeSetIterator(m_nil, this, true); +} + +#endif // TREESET_HPP diff --git a/src/TreeSet.cpp b/src/TreeSet.cpp index ccc4133..29df54f 100644 --- a/src/TreeSet.cpp +++ b/src/TreeSet.cpp @@ -1,356 +1 @@ #include "../include/TreeSet.hpp" - -#include -#include -#include - -template -Node::Node(const T &t_value, Color t_color, Node *t_parent, Node *t_left, - Node *t_right) - : value(t_value), color(t_color) { - if (parent == nullptr && left == nullptr && right == nullptr) { - parent = this; // see if this is correct - left = this; - right = this; - } - - parent = t_parent; - left = t_left; - right = t_right; -} - -template -TreeSet::TreeSet() : NIL(make_unique(Node(Color::Black, T{}))) { - NIL->left = NIL; - NIL->right = NIL; - NIL->parent = NIL; - - m_root = NIL; -} - -template auto TreeSet::insert(const T &t_value) -> bool { - try { - if (t_value == T{}) - throw std::exception(); - - Node *x = m_root; - Node *y = NIL; - - while (x != NIL) { - y = x; - - if (x->value > t_value) - x = x->left; - else if (x->value < t_value) - x = x->right; - else - return false; - } - - std::unique_ptr> z = - std::make_unique(Node(t_value, Color::Red, y, NIL, NIL)); - if (y == NIL) - m_root = z; - else if (z->value > y->value) - y->left = z; - else - y->right = z; - - fix_insertion_at(z); - node_count += 1; - return true; - - } catch (std::exception &ia) { - std::cerr << "invalid argument: RBT does not allow null values."; - exit(1); - } -} - -template auto TreeSet::remove(const T &t_value) -> bool { - Node *z = nullptr; - Node *y = nullptr; - Node *x = nullptr; - if (t_value == T{} || (z = (search(t_value, m_root))) == NIL) - return false; - - bool y_original_color = y->color; - if (z->left == NIL) { - x = z->right; - transplant(z, z->right); - } else if (z->right == NIL) { - x = z->left; - transplant(z, z->left); - } else { - y = successor(z->right); - y_original_color = y->color; - x = y->right; - - if (y->parent == z) - x->parent = y; - else { - transplant(y, y->right); - y->right = z->right; - y->right->parent = y; - } - transplant(z, y); - - y->left = z->left; - y->right->parent = y; - y->color = z->color; - } - - if (y_original_color == Color::Black) - fix_delete(x); - node_count -= 1; - return true; -} - -template auto TreeSet::size() const noexcept -> std::size_t { - return node_count; -} - -template auto TreeSet::height() const noexcept -> std::size_t { - return height(m_root); -} - -template auto TreeSet::is_empty() const noexcept -> bool { - return node_count == 0; -} - -template -auto TreeSet::is_element(const T &t_value) const noexcept -> bool { - Node *node = m_root; - - if (node == nullptr || t_value == 0) - return false; - while (node != nullptr) { - if (node->value > t_value) - node = node->left; - else if (node->value < t_value) - node = node->right; - else - return true; - } - return false; -} - -template auto TreeSet::root() const noexcept -> Node { - return m_root; -} - -template -auto TreeSet::min(Node *t_node) const noexcept -> Node { - while (t_node->left != NIL) - t_node = t_node->left; - return t_node; -} - -template -auto TreeSet::max(Node *t_node) const noexcept -> Node { - while (t_node->left != NIL) - t_node = t_node->right; - return t_node; -} - -template auto TreeSet::fix_insertion_at(Node *node) -> void { - Node *y{nullptr}; - - while (node->parent->color == Color::Red) { - if (node->parent == node->parent->parent->left) { - y = node->parent->parent->right; - if (y->color == Color::Red) { - node->parent->color = Color::Black; - y->color = Color::Black; - node->parent->parent->color = Color::Red; - node = node->parent->parent; - } else { - if (node == node->parent->right) { - node = node->parent; - left_rotate(node); - } - node->parent->color = Color::Black; - node->parent->color = Color::Red; - right_rotate(node->parent->parent); - } - } else { - y = node->parent->parent->left; - if (y->color == Color::Red) { - node->parent->color = Color::Black; - y->color = Color::Black; - node->parent->parent->color = Color::Red; - node = node->parent->parent; - } else { - if (node == node->parent->left) { - node = node->parent; - right_rotate(node); // define - } - node->parent->color = Color::Black; - node->parent->parent->color = Color::Red; - left_rotate(node->parent->parent); - } - } - } - m_root->color = Color::Black; - NIL->parent = nullptr; -} - -template auto TreeSet::left_rotate(Node *node) -> void { - Node *y = node->right; - node->right = y->left; - - if (y->left != NIL) - y->left->parent = node; - - y->parent = node->parent; - - if (node->parent == NIL) - m_root = y; - - if (node == node->parent->left) - node->parent->left = y; - else - node->parent->right = y; - - y->left = node; - node->parent = y; -} - -template auto TreeSet::right_rotate(Node *node) -> void { - Node *x = node; - node->left = x->right; - - if (x->right != NIL) - x->right->parent = node; - x->parent = node->parent; - - if (node->parent == NIL) - m_root = x; - if (node == node->parent->left) - node->parent->left = x; - else - node->parent->right = x; - x->right = node; - node->parent = x; -} - -template auto TreeSet::fix_delete(Node *x) -> void { - while (x != m_root && x->color == Color::Black) { - if (x == x->parent->left) { - Node *w = x->parent->right; - if (w->color == Color::Red) { - w->color = Color::Black; - x->parent->color = Color::Red; - left_rotate(x->parent); - w = x->parent->right; - } - - if (w->left->color == Color::Black && w->right->color == Color::Black) { - w->color = Color::Red; - x = x->parent; - continue; - } else if (w->right->color == Color::Black) { - w->left->color = Color::Red; - w->color = Color::Red; - right_rotate(w); - w = x->parent->right; - } - - if (w->right->color == Color::Red) { - w->color = x->parent->color; - x->parent->color = Color::Black; - w->right->color = Color::Black; - left_rotate(x->parent); - - x = m_root; - } - } else { - Node *w = x->parent->left; - if (w->color == Color::Red) { - w->color = Color::Black; - x->parent->color = Color::Red; - right_rotate(x->parent); - w = x->parent->left; - } - - if (w->right->color == Color::Black && w->left->color == Color::Black) { - w->color = Color::Red; - x = x->parent; - continue; - } else if (w->left->color == Color::Black) { - w->right->color = Color::Black; - w->color = Color::Red; - left_rotate(w); - w = x->parent->left; - } - - if (w->left->color == Color::Red) { - w->color = x->parent->color; - x->parent->color = Color::Black; - w->left->color = Color::Black; - right_rotate(x->parent); - x = m_root; - } - } - } - x->color = Color::Black; -} - -template auto TreeSet::successor(Node *node) -> Node * { - if (node == NIL || node->left == NIL) - return node; - return successor(node->left); -} - -template -auto TreeSet::search(const T &value, Node *node) -> Node * { - if (node == NIL) - return NIL; - else if (node->value == value) - return node; - else if (node->value < value) - return search(value, node->right); // review this one. - - return search(value, node->left); -} - -template auto TreeSet::height(Node *node) -> std::size_t { - if (node == NIL) - return 0; - - if (node->left == NIL && node->right == NIL) - return 1; - - std::size_t t_height = (height(node->left) > height(node->right)) - ? height(node->left) - : height(node->right); - return (t_height + 1); -} - -template -auto TreeSet::transplant(Node *u, Node *v) -> void { - if (u->parent == NIL) - m_root = v; - else if (u == u->parent->left) - u->parent->left = v; - else - u->parent->right = v; - - v->parent = u->parent; -} - -template -auto TreeSet::swap_color(Node *a, Node *b) -> void { - std::swap(a->color, b->color); -} - -template -auto TreeSet::update_parent_child_link(Node *t_parent, - Node *t_old_child, - Node *t_new_child) -> void { - if (t_parent != NIL) { - if (t_parent->left == t_old_child) - t_parent->left = t_new_child; - else - t_parent->right = t_new_child; - } -} \ No newline at end of file diff --git a/test/unittest.cpp b/test/unittest.cpp index e69de29..d0fc69d 100644 --- a/test/unittest.cpp +++ b/test/unittest.cpp @@ -0,0 +1,581 @@ +#include "../include/TreeSet.hpp" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include +#include +#include + +using ::testing::ElementsAre; + +class TreeSetTest : public ::testing::Test { +protected: + TreeSet tree; + + auto inorder_vec() -> std::vector { + std::vector result; + for (auto it = tree.begin(); it != tree.end(); ++it) + result.push_back(*it); + return result; + } + + auto preorder_vec() -> std::vector { + std::vector result; + for (auto it = tree.begin(); it != tree.end(); ++it) + result.push_back(*it); + return result; + } + + auto postorder_vec() -> std::vector { + std::vector result; + for (auto it = tree.begin(); it != tree.end(); + ++it) + result.push_back(*it); + return result; + } + + auto reverse_inorder_vec() -> std::vector { + std::vector result; + for (auto it = tree.rbegin(); it != tree.rend(); ++it) + result.push_back(*it); + return result; + } + + auto is_sorted_ascending(const std::vector &v) -> bool { + return std::is_sorted(v.begin(), v.end()); + } + + auto is_sorted_descending(const std::vector &v) -> bool { + return std::is_sorted(v.begin(), v.end(), std::greater{}); + } + + auto contains_all(const std::vector &v, + std::initializer_list expected) -> bool { + for (int val : expected) { + if (std::find(v.begin(), v.end(), val) == v.end()) + return false; + } + return true; + } +}; + +// ============================================================================ +// Group 1: Insert and Remove +// ============================================================================ + +TEST_F(TreeSetTest, InsertIntoEmptyTree) { + ASSERT_TRUE(tree.insert(5)); + EXPECT_EQ(tree.size(), 1u); + EXPECT_TRUE(tree.is_element(5)); +} + +TEST_F(TreeSetTest, InsertDuplicateReturnsFalse) { + ASSERT_TRUE(tree.insert(5)); + EXPECT_FALSE(tree.insert(5)); + EXPECT_EQ(tree.size(), 1u); +} + +TEST_F(TreeSetTest, InsertMultiplePreservesSize) { + ASSERT_TRUE(tree.insert(5)); + ASSERT_TRUE(tree.insert(3)); + ASSERT_TRUE(tree.insert(7)); + EXPECT_EQ(tree.size(), 3u); +} + +TEST_F(TreeSetTest, RemoveExistingReturnsTrue) { + ASSERT_TRUE(tree.insert(5)); + ASSERT_TRUE(tree.insert(3)); + ASSERT_TRUE(tree.insert(7)); + ASSERT_TRUE(tree.remove(3)); + EXPECT_EQ(tree.size(), 2u); + EXPECT_FALSE(tree.is_element(3)); +} + +TEST_F(TreeSetTest, RemoveNonexistentReturnsFalse) { + ASSERT_TRUE(tree.insert(5)); + EXPECT_FALSE(tree.remove(99)); + EXPECT_EQ(tree.size(), 1u); +} + +TEST_F(TreeSetTest, RemoveFromEmptyTreeReturnsFalse) { + EXPECT_FALSE(tree.remove(5)); +} + +TEST_F(TreeSetTest, RemoveRoot) { + ASSERT_TRUE(tree.insert(5)); + ASSERT_TRUE(tree.insert(3)); + ASSERT_TRUE(tree.insert(7)); + ASSERT_TRUE(tree.remove(5)); + EXPECT_EQ(tree.size(), 2u); + EXPECT_FALSE(tree.is_element(5)); + EXPECT_TRUE(is_sorted_ascending(inorder_vec())); +} + +TEST_F(TreeSetTest, InsertAndRemoveMultiple) { + for (int i = 1; i <= 10; ++i) + ASSERT_TRUE(tree.insert(i)); + EXPECT_EQ(tree.size(), 10u); + + ASSERT_TRUE(tree.remove(2)); + ASSERT_TRUE(tree.remove(5)); + ASSERT_TRUE(tree.remove(8)); + + EXPECT_EQ(tree.size(), 7u); + EXPECT_FALSE(tree.is_element(2)); + EXPECT_FALSE(tree.is_element(5)); + EXPECT_FALSE(tree.is_element(8)); + + auto in = inorder_vec(); + EXPECT_EQ(in.size(), 7u); + EXPECT_TRUE(is_sorted_ascending(in)); + EXPECT_THAT(in, ElementsAre(1, 3, 4, 6, 7, 9, 10)); +} + +// ============================================================================ +// Group 2: RBT Properties (observable via rotations) +// ============================================================================ + +TEST_F(TreeSetTest, InsertLeftRotation) { + ASSERT_TRUE(tree.insert(1)); + ASSERT_TRUE(tree.insert(2)); + ASSERT_TRUE(tree.insert(3)); + EXPECT_EQ(tree.root()->value, 2); + EXPECT_THAT(inorder_vec(), ElementsAre(1, 2, 3)); +} + +TEST_F(TreeSetTest, InsertRightRotation) { + ASSERT_TRUE(tree.insert(3)); + ASSERT_TRUE(tree.insert(2)); + ASSERT_TRUE(tree.insert(1)); + EXPECT_EQ(tree.root()->value, 2); + EXPECT_THAT(inorder_vec(), ElementsAre(1, 2, 3)); +} + +TEST_F(TreeSetTest, InsertLeftRightRotation) { + ASSERT_TRUE(tree.insert(3)); + ASSERT_TRUE(tree.insert(1)); + ASSERT_TRUE(tree.insert(2)); + EXPECT_EQ(tree.root()->value, 2); + EXPECT_THAT(inorder_vec(), ElementsAre(1, 2, 3)); +} + +TEST_F(TreeSetTest, InsertRightLeftRotation) { + ASSERT_TRUE(tree.insert(1)); + ASSERT_TRUE(tree.insert(3)); + ASSERT_TRUE(tree.insert(2)); + EXPECT_EQ(tree.root()->value, 2); + EXPECT_THAT(inorder_vec(), ElementsAre(1, 2, 3)); +} + +TEST_F(TreeSetTest, InsertRecoloringSequence) { + ASSERT_TRUE(tree.insert(10)); + ASSERT_TRUE(tree.insert(5)); + ASSERT_TRUE(tree.insert(15)); + ASSERT_TRUE(tree.insert(3)); + ASSERT_TRUE(tree.insert(7)); + ASSERT_TRUE(tree.insert(12)); + ASSERT_TRUE(tree.insert(20)); + ASSERT_TRUE(tree.insert(1)); + EXPECT_EQ(tree.size(), 8u); + EXPECT_THAT(inorder_vec(), ElementsAre(1, 3, 5, 7, 10, 12, 15, 20)); +} + +TEST_F(TreeSetTest, RootIsAlwaysBlack) { + for (int i = 1; i <= 7; ++i) + ASSERT_TRUE(tree.insert(i)); + EXPECT_EQ(tree.root()->color, Color::Black); +} + +TEST_F(TreeSetTest, RemoveMaintainsSortedOrder) { + ASSERT_TRUE(tree.insert(5)); + ASSERT_TRUE(tree.insert(3)); + ASSERT_TRUE(tree.insert(7)); + ASSERT_TRUE(tree.insert(1)); + ASSERT_TRUE(tree.insert(4)); + ASSERT_TRUE(tree.insert(6)); + ASSERT_TRUE(tree.insert(8)); + + ASSERT_TRUE(tree.remove(3)); + ASSERT_TRUE(tree.remove(5)); + ASSERT_TRUE(tree.remove(7)); + + auto in = inorder_vec(); + EXPECT_TRUE(is_sorted_ascending(in)); + EXPECT_THAT(in, ElementsAre(1, 4, 6, 8)); +} + +// ============================================================================ +// Group 3: In-order Traversal +// ============================================================================ + +TEST_F(TreeSetTest, InorderEmptyTree) { + EXPECT_TRUE(tree.begin() == tree.end()); + EXPECT_TRUE(inorder_vec().empty()); +} + +TEST_F(TreeSetTest, InorderSingleNode) { + ASSERT_TRUE(tree.insert(5)); + EXPECT_THAT(inorder_vec(), ElementsAre(5)); +} + +TEST_F(TreeSetTest, InorderBalancedTree) { + for (int v : {5, 3, 7, 1, 4, 6, 8}) + ASSERT_TRUE(tree.insert(v)); + EXPECT_THAT(inorder_vec(), ElementsAre(1, 3, 4, 5, 6, 7, 8)); +} + +TEST_F(TreeSetTest, InorderAfterRemovals) { + for (int v : {5, 3, 7, 1, 4, 6, 8}) + ASSERT_TRUE(tree.insert(v)); + ASSERT_TRUE(tree.remove(4)); + ASSERT_TRUE(tree.remove(7)); + EXPECT_THAT(inorder_vec(), ElementsAre(1, 3, 5, 6, 8)); +} + +// ============================================================================ +// Group 4: Pre-order Traversal +// ============================================================================ + +TEST_F(TreeSetTest, PreorderEmptyTree) { + EXPECT_TRUE(tree.begin() == tree.end()); + EXPECT_TRUE(preorder_vec().empty()); +} + +TEST_F(TreeSetTest, PreorderSingleNode) { + ASSERT_TRUE(tree.insert(5)); + EXPECT_THAT(preorder_vec(), ElementsAre(5)); +} + +TEST_F(TreeSetTest, PreorderBalancedTree) { + for (int v : {5, 3, 7, 1, 4, 6, 8}) + ASSERT_TRUE(tree.insert(v)); + + auto pre = preorder_vec(); + auto in = inorder_vec(); + + EXPECT_EQ(pre.size(), 7u); + EXPECT_THAT(in, ElementsAre(1, 3, 4, 5, 6, 7, 8)); + EXPECT_TRUE(contains_all(pre, {1, 3, 4, 5, 6, 7, 8})); + EXPECT_EQ(pre.front(), tree.root()->value); +} + +TEST_F(TreeSetTest, PreorderAfterRemovals) { + for (int v : {5, 3, 7, 1, 4, 6, 8}) + ASSERT_TRUE(tree.insert(v)); + ASSERT_TRUE(tree.remove(4)); + ASSERT_TRUE(tree.remove(7)); + + auto pre = preorder_vec(); + EXPECT_EQ(pre.size(), 5u); + EXPECT_TRUE(contains_all(pre, {1, 3, 5, 6, 8})); + EXPECT_EQ(pre.front(), tree.root()->value); +} + +// ============================================================================ +// Group 5: Post-order Traversal +// ============================================================================ + +TEST_F(TreeSetTest, PostorderEmptyTree) { + EXPECT_TRUE(tree.begin() == tree.end()); + EXPECT_TRUE(postorder_vec().empty()); +} + +TEST_F(TreeSetTest, PostorderSingleNode) { + ASSERT_TRUE(tree.insert(5)); + EXPECT_THAT(postorder_vec(), ElementsAre(5)); +} + +TEST_F(TreeSetTest, PostorderBalancedTree) { + for (int v : {5, 3, 7, 1, 4, 6, 8}) + ASSERT_TRUE(tree.insert(v)); + + auto post = postorder_vec(); + auto in = inorder_vec(); + + EXPECT_EQ(post.size(), 7u); + EXPECT_THAT(in, ElementsAre(1, 3, 4, 5, 6, 7, 8)); + EXPECT_TRUE(contains_all(post, {1, 3, 4, 5, 6, 7, 8})); + EXPECT_EQ(post.back(), tree.root()->value); +} + +TEST_F(TreeSetTest, PostorderAfterRemovals) { + for (int v : {5, 3, 7, 1, 4, 6, 8}) + ASSERT_TRUE(tree.insert(v)); + ASSERT_TRUE(tree.remove(4)); + ASSERT_TRUE(tree.remove(7)); + + auto post = postorder_vec(); + EXPECT_EQ(post.size(), 5u); + EXPECT_TRUE(contains_all(post, {1, 3, 5, 6, 8})); + EXPECT_EQ(post.back(), tree.root()->value); +} + +// ============================================================================ +// Group 6: Iterator Movement (in-order forward) +// ============================================================================ + +TEST_F(TreeSetTest, BeginPointsToSmallest) { + ASSERT_TRUE(tree.insert(5)); + ASSERT_TRUE(tree.insert(3)); + ASSERT_TRUE(tree.insert(7)); + EXPECT_EQ(*tree.begin(), 3); +} + +TEST_F(TreeSetTest, ForwardTraversalCollectsSortedOrder) { + for (int v : {5, 3, 7, 1, 4, 6, 8}) + ASSERT_TRUE(tree.insert(v)); + EXPECT_THAT(inorder_vec(), ElementsAre(1, 3, 4, 5, 6, 7, 8)); +} + +TEST_F(TreeSetTest, PostIncrementReturnsOldValue) { + ASSERT_TRUE(tree.insert(1)); + ASSERT_TRUE(tree.insert(2)); + auto it = tree.begin(); + auto old = it++; + EXPECT_EQ(*old, 1); + EXPECT_EQ(*it, 2); +} + +TEST_F(TreeSetTest, PreIncrementReturnsNewValue) { + ASSERT_TRUE(tree.insert(1)); + ASSERT_TRUE(tree.insert(2)); + auto it = tree.begin(); + auto &ref = ++it; + EXPECT_EQ(*ref, 2); + EXPECT_EQ(*it, 2); +} + +TEST_F(TreeSetTest, IncrementPastLastReachesEnd) { + ASSERT_TRUE(tree.insert(1)); + ASSERT_TRUE(tree.insert(2)); + ASSERT_TRUE(tree.insert(3)); + auto it = tree.begin(); + ++it; + ++it; + ++it; + EXPECT_TRUE(it == tree.end()); +} + +TEST_F(TreeSetTest, IteratorDistanceEqualsSize) { + for (int v : {5, 3, 7, 1, 4}) + ASSERT_TRUE(tree.insert(v)); + EXPECT_EQ(std::distance(tree.begin(), tree.end()), 5); +} + +TEST_F(TreeSetTest, RangeBasedForLoop) { + for (int v : {5, 3, 7, 1, 4, 6, 8}) + ASSERT_TRUE(tree.insert(v)); + std::vector collected; + for (const auto &v : tree) + collected.push_back(v); + EXPECT_THAT(collected, ElementsAre(1, 3, 4, 5, 6, 7, 8)); +} + +// ============================================================================ +// Group 7: Reverse Iterator Movement (in-order backward) +// ============================================================================ + +TEST_F(TreeSetTest, RbeginPointsToLargest) { + ASSERT_TRUE(tree.insert(5)); + ASSERT_TRUE(tree.insert(3)); + ASSERT_TRUE(tree.insert(7)); + EXPECT_EQ(*tree.rbegin(), 7); +} + +TEST_F(TreeSetTest, ReverseTraversalCollectsReverseSorted) { + for (int v : {5, 3, 7, 1, 4, 6, 8}) + ASSERT_TRUE(tree.insert(v)); + EXPECT_THAT(reverse_inorder_vec(), ElementsAre(8, 7, 6, 5, 4, 3, 1)); +} + +TEST_F(TreeSetTest, PostIncrementOnReverseReturnsOldValue) { + ASSERT_TRUE(tree.insert(1)); + ASSERT_TRUE(tree.insert(2)); + ASSERT_TRUE(tree.insert(3)); + auto it = tree.rbegin(); + auto old = it++; + EXPECT_EQ(*old, 3); + EXPECT_EQ(*it, 2); +} + +TEST_F(TreeSetTest, PreIncrementOnReverseReturnsNewValue) { + ASSERT_TRUE(tree.insert(1)); + ASSERT_TRUE(tree.insert(2)); + ASSERT_TRUE(tree.insert(3)); + auto it = tree.rbegin(); + auto &ref = ++it; + EXPECT_EQ(*ref, 2); + EXPECT_EQ(*it, 2); +} + +TEST_F(TreeSetTest, PostDecrementReturnsOldValue) { + for (int v : {1, 2, 3}) + ASSERT_TRUE(tree.insert(v)); + auto it = tree.end(); + --it; + auto old = it--; + EXPECT_EQ(*old, 3); + EXPECT_EQ(*it, 2); +} + +TEST_F(TreeSetTest, PreDecrementReturnsNewValue) { + for (int v : {1, 2, 3}) + ASSERT_TRUE(tree.insert(v)); + auto it = tree.end(); + --it; + auto &ref = --it; + EXPECT_EQ(*ref, 2); + EXPECT_EQ(*it, 2); +} + +// ============================================================================ +// Group 8: Const Iterator +// ============================================================================ + +TEST_F(TreeSetTest, CBeginCEndSameAsBeginEnd) { + for (int v : {5, 3, 7}) + ASSERT_TRUE(tree.insert(v)); + + std::vector from_begin, from_cbegin; + for (auto it = tree.begin(); it != tree.end(); ++it) + from_begin.push_back(*it); + for (auto it = tree.cbegin(); it != tree.cend(); ++it) + from_cbegin.push_back(*it); + + EXPECT_EQ(from_begin, from_cbegin); +} + +TEST_F(TreeSetTest, CRbeginCREndSameAsRbeginRend) { + for (int v : {5, 3, 7}) + ASSERT_TRUE(tree.insert(v)); + + std::vector from_rbegin, from_crbegin; + for (auto it = tree.rbegin(); it != tree.rend(); ++it) + from_rbegin.push_back(*it); + for (auto it = tree.crbegin(); it != tree.crend(); ++it) + from_crbegin.push_back(*it); + + EXPECT_EQ(from_rbegin, from_crbegin); +} + +// ============================================================================ +// Group 9: Tag-dispatched Iterators +// ============================================================================ + +TEST_F(TreeSetTest, InorderTagGivesSameAsDefault) { + for (int v : {5, 3, 7, 1, 4, 6, 8}) + ASSERT_TRUE(tree.insert(v)); + + std::vector default_order, tag_order; + for (auto it = tree.begin(); it != tree.end(); ++it) + default_order.push_back(*it); + for (auto it = tree.begin(); it != tree.end(); ++it) + tag_order.push_back(*it); + + EXPECT_EQ(default_order, tag_order); +} + +TEST_F(TreeSetTest, PreorderTagTraversal) { + for (int v : {5, 3, 7, 1, 4, 6, 8}) + ASSERT_TRUE(tree.insert(v)); + + auto pre = preorder_vec(); + auto in = inorder_vec(); + + EXPECT_EQ(pre.size(), 7u); + EXPECT_THAT(in, ElementsAre(1, 3, 4, 5, 6, 7, 8)); + EXPECT_TRUE(contains_all(pre, {1, 3, 4, 5, 6, 7, 8})); + EXPECT_EQ(pre.front(), tree.root()->value); +} + +TEST_F(TreeSetTest, PostorderTagTraversal) { + for (int v : {5, 3, 7, 1, 4, 6, 8}) + ASSERT_TRUE(tree.insert(v)); + + auto post = postorder_vec(); + auto in = inorder_vec(); + + EXPECT_EQ(post.size(), 7u); + EXPECT_THAT(in, ElementsAre(1, 3, 4, 5, 6, 7, 8)); + EXPECT_TRUE(contains_all(post, {1, 3, 4, 5, 6, 7, 8})); + EXPECT_EQ(post.back(), tree.root()->value); +} + +TEST_F(TreeSetTest, TagRbeginRend) { + for (int v : {5, 3, 7, 1, 4, 6, 8}) + ASSERT_TRUE(tree.insert(v)); + + std::vector rev_pre, rev_post; + for (auto it = tree.rbegin(); it != tree.rend(); ++it) + rev_pre.push_back(*it); + for (auto it = tree.rbegin(); it != tree.rend(); + ++it) + rev_post.push_back(*it); + + auto pre = preorder_vec(); + auto post = postorder_vec(); + + EXPECT_EQ(rev_pre.size(), 7u); + EXPECT_EQ(rev_post.size(), 7u); + EXPECT_TRUE(contains_all(rev_pre, {1, 3, 4, 5, 6, 7, 8})); + EXPECT_TRUE(contains_all(rev_post, {1, 3, 4, 5, 6, 7, 8})); + + std::vector pre_reversed(pre.rbegin(), pre.rend()); + std::vector post_reversed(post.rbegin(), post.rend()); + EXPECT_EQ(rev_pre, pre_reversed); + EXPECT_EQ(rev_post, post_reversed); +} + +// ============================================================================ +// Group 10: Edge Cases +// ============================================================================ + +TEST_F(TreeSetTest, EmptyTreeBeginEqualsEnd) { + EXPECT_TRUE(tree.begin() == tree.end()); + EXPECT_TRUE(tree.rbegin() == tree.rend()); + EXPECT_TRUE(tree.cbegin() == tree.cend()); + EXPECT_TRUE(tree.crbegin() == tree.crend()); + EXPECT_TRUE(tree.begin() == tree.end()); + EXPECT_TRUE(tree.begin() == tree.end()); + EXPECT_TRUE(tree.begin() == tree.end()); +} + +TEST_F(TreeSetTest, SingleElementBeginRbeginSame) { + ASSERT_TRUE(tree.insert(5)); + EXPECT_EQ(*tree.begin(), 5); + EXPECT_EQ(*tree.rbegin(), 5); +} + +TEST_F(TreeSetTest, LargeTreeTraversal) { + for (int i = 1; i <= 100; ++i) + ASSERT_TRUE(tree.insert(i)); + EXPECT_EQ(tree.size(), 100u); + + auto in = inorder_vec(); + EXPECT_EQ(in.size(), 100u); + EXPECT_TRUE(is_sorted_ascending(in)); + EXPECT_EQ(in.front(), 1); + EXPECT_EQ(in.back(), 100); +} + +TEST_F(TreeSetTest, HeightAfterInsertions) { + ASSERT_TRUE(tree.insert(1)); + EXPECT_EQ(tree.height(), 1u); + + ASSERT_TRUE(tree.insert(2)); + ASSERT_TRUE(tree.insert(3)); + EXPECT_EQ(tree.height(), 2u); +} + +TEST_F(TreeSetTest, RemoveAllElements) { + ASSERT_TRUE(tree.insert(5)); + ASSERT_TRUE(tree.insert(3)); + ASSERT_TRUE(tree.insert(7)); + + ASSERT_TRUE(tree.remove(5)); + ASSERT_TRUE(tree.remove(3)); + ASSERT_TRUE(tree.remove(7)); + + EXPECT_TRUE(tree.is_empty()); + EXPECT_EQ(tree.size(), 0u); + EXPECT_TRUE(tree.begin() == tree.end()); +} From 92b0f5592bcc21ad4a3b6c34ca91d2ff1f01eb4d Mon Sep 17 00:00:00 2001 From: Dave Amiana Date: Mon, 13 Jul 2026 00:55:18 +0800 Subject: [PATCH 05/13] using the tpp hybrid pattern to maintain interface exposure and implementation to a different module --- include/TreeSet.hpp | 679 +----------------------------------- include/detail/Iterator.tpp | 155 ++++++++ include/detail/TreeSet.tpp | 533 ++++++++++++++++++++++++++++ 3 files changed, 691 insertions(+), 676 deletions(-) create mode 100644 include/detail/Iterator.tpp create mode 100644 include/detail/TreeSet.tpp diff --git a/include/TreeSet.hpp b/include/TreeSet.hpp index 06817c7..80080c7 100644 --- a/include/TreeSet.hpp +++ b/include/TreeSet.hpp @@ -2,9 +2,8 @@ #define TREESET_HPP #include -#include +#include #include -#include enum Color : bool { Red = true, @@ -101,155 +100,6 @@ class TreeSetIterator { auto advance_backward() -> void; }; -template -auto TreeSetIterator::advance_forward() -> void { - if constexpr (std::is_same_v) { - if (m_current->right != m_tree->m_nil) { - m_current = m_current->right; - while (m_current->left != m_tree->m_nil) { - m_current = m_current->left; - } - } else { - auto parent = m_current->parent; - while (parent != m_tree->m_nil && m_current == parent->right) { - m_current = parent; - parent = parent->parent; - } - m_current = parent; - } - } else if constexpr (std::is_same_v) { - if (m_current->left != m_tree->m_nil) { - m_current = m_current->left; - } else if (m_current->right != m_tree->m_nil) { - m_current = m_current->right; - } else { - auto parent = m_current->parent; - while (parent != m_tree->m_nil) { - if (m_current == parent->left && parent->right != m_tree->m_nil) { - m_current = parent->right; - return; - } - m_current = parent; - parent = parent->parent; - } - m_current = m_tree->m_nil; - } - } else if constexpr (std::is_same_v) { - auto parent = m_current->parent; - if (parent == m_tree->m_nil) { - m_current = m_tree->m_nil; - return; - } - if (m_current == parent->left && parent->right != m_tree->m_nil) { - m_current = parent->right; - while (true) { - if (m_current->left != m_tree->m_nil) { - m_current = m_current->left; - } else if (m_current->right != m_tree->m_nil) { - m_current = m_current->right; - } else { - break; - } - } - } else if (m_current == parent->left) { - m_current = parent; - } else { - m_current = parent; - } - } -} - -template -auto TreeSetIterator::advance_backward() -> void { - if constexpr (std::is_same_v) { - if (m_current == m_tree->m_nil) { - m_current = m_tree->m_root; - if (m_current != m_tree->m_nil) { - while (m_current->right != m_tree->m_nil) { - m_current = m_current->right; - } - } - } else if (m_current->left != m_tree->m_nil) { - m_current = m_current->left; - while (m_current->right != m_tree->m_nil) { - m_current = m_current->right; - } - } else { - auto parent = m_current->parent; - while (parent != m_tree->m_nil && m_current == parent->left) { - m_current = parent; - parent = parent->parent; - } - m_current = parent; - } - } else if constexpr (std::is_same_v) { - if (m_current == m_tree->m_nil) { - m_current = m_tree->m_root; - if (m_current == m_tree->m_nil) { - return; - } - while (true) { - if (m_current->right != m_tree->m_nil) { - m_current = m_current->right; - } else if (m_current->left != m_tree->m_nil) { - m_current = m_current->left; - } else { - break; - } - } - } else { - auto parent = m_current->parent; - if (parent == m_tree->m_nil) { - m_current = m_tree->m_nil; - return; - } - if (m_current == parent->left) { - m_current = parent; - } else { - if (parent->left != m_tree->m_nil) { - m_current = parent->left; - while (true) { - if (m_current->right != m_tree->m_nil) { - m_current = m_current->right; - } else if (m_current->left != m_tree->m_nil) { - m_current = m_current->left; - } else { - break; - } - } - } else { - m_current = parent; - } - } - } - } else if constexpr (std::is_same_v) { - if (m_current == m_tree->m_nil) { - m_current = m_tree->m_root; - return; - } - if (m_current->right != m_tree->m_nil) { - m_current = m_current->right; - return; - } - if (m_current->left != m_tree->m_nil) { - m_current = m_current->left; - return; - } - while (true) { - auto parent = m_current->parent; - if (parent == m_tree->m_nil) { - m_current = m_tree->m_nil; - return; - } - if (m_current == parent->right && parent->left != m_tree->m_nil) { - m_current = parent->left; - return; - } - m_current = parent; - } - } -} - template class TreeSet { public: TreeSet(); @@ -328,530 +178,7 @@ template class TreeSet { friend class TreeSetIterator; }; -template -TreeSet::TreeSet() - : m_nil(new Node(T{}, Color::Black, nullptr, nullptr, nullptr)), - m_root(m_nil) { - m_nil->left = m_nil->right = m_nil->parent = m_nil; -} - -template TreeSet::~TreeSet() { - destroy(m_root); - delete m_nil; -} - -template auto TreeSet::destroy(Node *t_node) -> void { - if (t_node != m_nil) { - destroy(t_node->left); - destroy(t_node->right); - delete t_node; - } -} - -template auto TreeSet::insert(const T &t_value) -> bool { - auto *z = new Node(t_value, Color::Red, m_nil, m_nil, m_nil); - auto *y = m_nil; - auto *x = m_root; - - while (x != m_nil) { - y = x; - if (t_value < x->value) { - x = x->left; - } else if (x->value < t_value) { - x = x->right; - } else { - delete z; - return false; - } - } - - z->parent = y; - if (y == m_nil) { - m_root = z; - } else if (t_value < y->value) { - y->left = z; - } else { - y->right = z; - } - - fix_insertion_at(z); - ++node_count; - return true; -} - -template auto TreeSet::remove(const T &t_value) -> bool { - auto *z = search(t_value, m_root); - if (z == m_nil) { - return false; - } - - auto *y = z; - auto y_original_color = y->color; - Node *x = nullptr; - - if (z->left == m_nil) { - x = z->right; - transplant(z, z->right); - } else if (z->right == m_nil) { - x = z->left; - transplant(z, z->left); - } else { - y = z->right; - while (y->left != m_nil) { - y = y->left; - } - y_original_color = y->color; - x = y->right; - if (y->parent == z) { - x->parent = y; - } else { - transplant(y, y->right); - y->right = z->right; - y->right->parent = y; - } - transplant(z, y); - y->left = z->left; - y->left->parent = y; - y->color = z->color; - } - - delete z; - --node_count; - - if (y_original_color == Color::Black) { - fix_delete(x); - } - return true; -} - -template auto TreeSet::successor(Node *t_node) -> Node * { - if (t_node->right != m_nil) { - auto *node = t_node->right; - while (node->left != m_nil) { - node = node->left; - } - return node; - } - auto *parent = t_node->parent; - while (parent != m_nil && t_node == parent->right) { - t_node = parent; - parent = parent->parent; - } - return parent; -} - -template -auto TreeSet::predecessor(Node *t_node) -> Node * { - if (t_node->left != m_nil) { - auto *node = t_node->left; - while (node->right != m_nil) { - node = node->right; - } - return node; - } - auto *parent = t_node->parent; - while (parent != m_nil && t_node == parent->left) { - t_node = parent; - parent = parent->parent; - } - return parent; -} - -template -auto TreeSet::search(const T &t_value, Node *t_node) const -> Node * { - if (t_node == m_nil) { - return m_nil; - } - if (t_value == t_node->value) { - return t_node; - } - if (t_value < t_node->value) { - return search(t_value, t_node->left); - } - return search(t_value, t_node->right); -} - -template -auto TreeSet::height(Node *t_node) const -> std::size_t { - if (t_node == m_nil) { - return 0; - } - if (t_node->left == m_nil && t_node->right == m_nil) { - return 1; - } - auto left_h = height(t_node->left); - auto right_h = height(t_node->right); - return (left_h > right_h ? left_h : right_h) + 1; -} - -template -auto TreeSet::fix_insertion_at(Node *t_node) -> void { - while (t_node->parent->color == Color::Red) { - if (t_node->parent == t_node->parent->parent->left) { - auto *uncle = t_node->parent->parent->right; - if (uncle->color == Color::Red) { - t_node->parent->color = Color::Black; - uncle->color = Color::Black; - t_node->parent->parent->color = Color::Red; - t_node = t_node->parent->parent; - } else { - if (t_node == t_node->parent->right) { - t_node = t_node->parent; - left_rotate(t_node); - } - t_node->parent->color = Color::Black; - t_node->parent->parent->color = Color::Red; - right_rotate(t_node->parent->parent); - } - } else { - auto *uncle = t_node->parent->parent->left; - if (uncle->color == Color::Red) { - t_node->parent->color = Color::Black; - uncle->color = Color::Black; - t_node->parent->parent->color = Color::Red; - t_node = t_node->parent->parent; - } else { - if (t_node == t_node->parent->left) { - t_node = t_node->parent; - right_rotate(t_node); - } - t_node->parent->color = Color::Black; - t_node->parent->parent->color = Color::Red; - left_rotate(t_node->parent->parent); - } - } - } - m_root->color = Color::Black; -} - -template auto TreeSet::left_rotate(Node *t_node) -> void { - auto *y = t_node->right; - t_node->right = y->left; - if (y->left != m_nil) { - y->left->parent = t_node; - } - y->parent = t_node->parent; - if (t_node->parent == m_nil) { - m_root = y; - } else if (t_node == t_node->parent->left) { - t_node->parent->left = y; - } else { - t_node->parent->right = y; - } - y->left = t_node; - t_node->parent = y; -} - -template auto TreeSet::right_rotate(Node *t_node) -> void { - auto *y = t_node->left; - t_node->left = y->right; - if (y->right != m_nil) { - y->right->parent = t_node; - } - y->parent = t_node->parent; - if (t_node->parent == m_nil) { - m_root = y; - } else if (t_node == t_node->parent->right) { - t_node->parent->right = y; - } else { - t_node->parent->left = y; - } - y->right = t_node; - t_node->parent = y; -} - -template -auto TreeSet::transplant(Node *t_u, Node *t_v) -> void { - if (t_u->parent == m_nil) { - m_root = t_v; - } else if (t_u == t_u->parent->left) { - t_u->parent->left = t_v; - } else { - t_u->parent->right = t_v; - } - t_v->parent = t_u->parent; -} - -template -auto TreeSet::swap_color(Node *t_a, Node *t_b) -> void { - std::swap(t_a->color, t_b->color); -} - -template -auto TreeSet::update_parent_child_link(Node *t_parent, - Node *t_old_child, - Node *t_new_child) -> void { - if (t_parent != m_nil) { - if (t_parent->left == t_old_child) { - t_parent->left = t_new_child; - } else { - t_parent->right = t_new_child; - } - } -} - -template auto TreeSet::fix_delete(Node *t_node) -> void { - while (t_node != m_root && t_node->color == Color::Black) { - if (t_node == t_node->parent->left) { - auto *w = t_node->parent->right; - if (w->color == Color::Red) { - w->color = Color::Black; - t_node->parent->color = Color::Red; - left_rotate(t_node->parent); - w = t_node->parent->right; - } - if (w->left->color == Color::Black && w->right->color == Color::Black) { - w->color = Color::Red; - t_node = t_node->parent; - } else { - if (w->right->color == Color::Black) { - w->left->color = Color::Black; - w->color = Color::Red; - right_rotate(w); - w = t_node->parent->right; - } - w->color = t_node->parent->color; - t_node->parent->color = Color::Black; - w->right->color = Color::Black; - left_rotate(t_node->parent); - t_node = m_root; - } - } else { - auto *w = t_node->parent->left; - if (w->color == Color::Red) { - w->color = Color::Black; - t_node->parent->color = Color::Red; - right_rotate(t_node->parent); - w = t_node->parent->left; - } - if (w->right->color == Color::Black && w->left->color == Color::Black) { - w->color = Color::Red; - t_node = t_node->parent; - } else { - if (w->left->color == Color::Black) { - w->right->color = Color::Black; - w->color = Color::Red; - left_rotate(w); - w = t_node->parent->left; - } - w->color = t_node->parent->color; - t_node->parent->color = Color::Black; - w->left->color = Color::Black; - right_rotate(t_node->parent); - t_node = m_root; - } - } - } - t_node->color = Color::Black; -} - -template auto TreeSet::leftmost() const -> Node * { - if (m_root == m_nil) { - return m_nil; - } - auto *node = m_root; - while (node->left != m_nil) { - node = node->left; - } - return node; -} - -template auto TreeSet::rightmost() const -> Node * { - if (m_root == m_nil) { - return m_nil; - } - auto *node = m_root; - while (node->right != m_nil) { - node = node->right; - } - return node; -} - -template auto TreeSet::first_postorder() const -> Node * { - if (m_root == m_nil) { - return m_nil; - } - auto *node = m_root; - while (true) { - if (node->left != m_nil) { - node = node->left; - } else if (node->right != m_nil) { - node = node->right; - } else { - break; - } - } - return node; -} - -template auto TreeSet::last_preorder() const -> Node * { - if (m_root == m_nil) { - return m_nil; - } - auto *node = m_root; - while (true) { - if (node->right != m_nil) { - node = node->right; - } else if (node->left != m_nil) { - node = node->left; - } else { - break; - } - } - return node; -} - -template auto TreeSet::size() const noexcept -> std::size_t { - return node_count; -} - -template auto TreeSet::height() const noexcept -> std::size_t { - return height(m_root); -} - -template auto TreeSet::is_empty() const noexcept -> bool { - return node_count == 0; -} - -template -auto TreeSet::is_element(const T &t_value) const noexcept -> bool { - return search(t_value, m_root) != m_nil; -} - -template auto TreeSet::root() noexcept -> Node * { - return m_root; -} - -template -auto TreeSet::min(Node *t_node) noexcept -> Node * { - while (t_node->left != m_nil) { - t_node = t_node->left; - } - return t_node; -} - -template -auto TreeSet::max(Node *t_node) noexcept -> Node * { - while (t_node->right != m_nil) { - t_node = t_node->right; - } - return t_node; -} - -template -auto TreeSet::begin() noexcept -> TreeSetIterator { - return TreeSetIterator(leftmost(), this, false); -} - -template -auto TreeSet::end() noexcept -> TreeSetIterator { - return TreeSetIterator(m_nil, this, false); -} - -template -auto TreeSet::rbegin() noexcept -> TreeSetIterator { - return TreeSetIterator(rightmost(), this, true); -} - -template -auto TreeSet::rend() noexcept -> TreeSetIterator { - return TreeSetIterator(m_nil, this, true); -} - -template -auto TreeSet::cbegin() const noexcept - -> TreeSetIterator { - return TreeSetIterator(leftmost(), this, false); -} - -template -auto TreeSet::cend() const noexcept -> TreeSetIterator { - return TreeSetIterator(m_nil, this, false); -} - -template -auto TreeSet::crbegin() const noexcept - -> TreeSetIterator { - return TreeSetIterator(rightmost(), this, true); -} - -template -auto TreeSet::crend() const noexcept -> TreeSetIterator { - return TreeSetIterator(m_nil, this, true); -} - -template -template -auto TreeSet::begin() noexcept -> TreeSetIterator { - if constexpr (std::is_same_v) { - return TreeSetIterator(m_root, this, false); - } else if constexpr (std::is_same_v) { - return TreeSetIterator(first_postorder(), this, false); - } else { - return TreeSetIterator(leftmost(), this, false); - } -} - -template -template -auto TreeSet::end() noexcept -> TreeSetIterator { - return TreeSetIterator(m_nil, this, false); -} - -template -template -auto TreeSet::rbegin() noexcept -> TreeSetIterator { - if constexpr (std::is_same_v) { - return TreeSetIterator(last_preorder(), this, true); - } else if constexpr (std::is_same_v) { - return TreeSetIterator(m_root, this, true); - } else { - return TreeSetIterator(rightmost(), this, true); - } -} - -template -template -auto TreeSet::rend() noexcept -> TreeSetIterator { - return TreeSetIterator(m_nil, this, true); -} - -template -template -auto TreeSet::cbegin() const noexcept - -> TreeSetIterator { - if constexpr (std::is_same_v) { - return TreeSetIterator(m_root, this, false); - } else if constexpr (std::is_same_v) { - return TreeSetIterator(first_postorder(), this, false); - } else { - return TreeSetIterator(leftmost(), this, false); - } -} - -template -template -auto TreeSet::cend() const noexcept -> TreeSetIterator { - return TreeSetIterator(m_nil, this, false); -} - -template -template -auto TreeSet::crbegin() const noexcept - -> TreeSetIterator { - if constexpr (std::is_same_v) { - return TreeSetIterator(last_preorder(), this, true); - } else if constexpr (std::is_same_v) { - return TreeSetIterator(m_root, this, true); - } else { - return TreeSetIterator(rightmost(), this, true); - } -} - -template -template -auto TreeSet::crend() const noexcept -> TreeSetIterator { - return TreeSetIterator(m_nil, this, true); -} +#include "detail/Iterator.tpp" +#include "detail/TreeSet.tpp" #endif // TREESET_HPP diff --git a/include/detail/Iterator.tpp b/include/detail/Iterator.tpp new file mode 100644 index 0000000..3eccdff --- /dev/null +++ b/include/detail/Iterator.tpp @@ -0,0 +1,155 @@ +#ifndef TREESET_ITERATOR_TPP +#define TREESET_ITERATOR_TPP + +#include + +template +auto TreeSetIterator::advance_forward() -> void { + if constexpr (std::is_same_v) { + if (m_current->right != m_tree->m_nil) { + m_current = m_current->right; + while (m_current->left != m_tree->m_nil) { + m_current = m_current->left; + } + } else { + auto parent = m_current->parent; + while (parent != m_tree->m_nil && m_current == parent->right) { + m_current = parent; + parent = parent->parent; + } + m_current = parent; + } + } else if constexpr (std::is_same_v) { + if (m_current->left != m_tree->m_nil) { + m_current = m_current->left; + } else if (m_current->right != m_tree->m_nil) { + m_current = m_current->right; + } else { + auto parent = m_current->parent; + while (parent != m_tree->m_nil) { + if (m_current == parent->left && parent->right != m_tree->m_nil) { + m_current = parent->right; + return; + } + m_current = parent; + parent = parent->parent; + } + m_current = m_tree->m_nil; + } + } else if constexpr (std::is_same_v) { + auto parent = m_current->parent; + if (parent == m_tree->m_nil) { + m_current = m_tree->m_nil; + return; + } + if (m_current == parent->left && parent->right != m_tree->m_nil) { + m_current = parent->right; + while (true) { + if (m_current->left != m_tree->m_nil) { + m_current = m_current->left; + } else if (m_current->right != m_tree->m_nil) { + m_current = m_current->right; + } else { + break; + } + } + } else if (m_current == parent->left) { + m_current = parent; + } else { + m_current = parent; + } + } +} + +template +auto TreeSetIterator::advance_backward() -> void { + if constexpr (std::is_same_v) { + if (m_current == m_tree->m_nil) { + m_current = m_tree->m_root; + if (m_current != m_tree->m_nil) { + while (m_current->right != m_tree->m_nil) { + m_current = m_current->right; + } + } + } else if (m_current->left != m_tree->m_nil) { + m_current = m_current->left; + while (m_current->right != m_tree->m_nil) { + m_current = m_current->right; + } + } else { + auto parent = m_current->parent; + while (parent != m_tree->m_nil && m_current == parent->left) { + m_current = parent; + parent = parent->parent; + } + m_current = parent; + } + } else if constexpr (std::is_same_v) { + if (m_current == m_tree->m_nil) { + m_current = m_tree->m_root; + if (m_current == m_tree->m_nil) { + return; + } + while (true) { + if (m_current->right != m_tree->m_nil) { + m_current = m_current->right; + } else if (m_current->left != m_tree->m_nil) { + m_current = m_current->left; + } else { + break; + } + } + } else { + auto parent = m_current->parent; + if (parent == m_tree->m_nil) { + m_current = m_tree->m_nil; + return; + } + if (m_current == parent->left) { + m_current = parent; + } else { + if (parent->left != m_tree->m_nil) { + m_current = parent->left; + while (true) { + if (m_current->right != m_tree->m_nil) { + m_current = m_current->right; + } else if (m_current->left != m_tree->m_nil) { + m_current = m_current->left; + } else { + break; + } + } + } else { + m_current = parent; + } + } + } + } else if constexpr (std::is_same_v) { + if (m_current == m_tree->m_nil) { + m_current = m_tree->m_root; + return; + } + if (m_current->right != m_tree->m_nil) { + m_current = m_current->right; + return; + } + if (m_current->left != m_tree->m_nil) { + m_current = m_current->left; + return; + } + while (true) { + auto parent = m_current->parent; + if (parent == m_tree->m_nil) { + m_current = m_tree->m_nil; + return; + } + if (m_current == parent->right && parent->left != m_tree->m_nil) { + m_current = parent->left; + return; + } + m_current = parent; + } + } +} + +#endif // TREESET_ITERATOR_TPP diff --git a/include/detail/TreeSet.tpp b/include/detail/TreeSet.tpp new file mode 100644 index 0000000..23d1101 --- /dev/null +++ b/include/detail/TreeSet.tpp @@ -0,0 +1,533 @@ +#ifndef TREESET_TPP +#define TREESET_TPP + +#include + +template +TreeSet::TreeSet() + : m_nil(new Node(T{}, Color::Black, nullptr, nullptr, nullptr)), + m_root(m_nil) { + m_nil->left = m_nil->right = m_nil->parent = m_nil; +} + +template TreeSet::~TreeSet() { + destroy(m_root); + delete m_nil; +} + +template auto TreeSet::destroy(Node *t_node) -> void { + if (t_node != m_nil) { + destroy(t_node->left); + destroy(t_node->right); + delete t_node; + } +} + +template auto TreeSet::insert(const T &t_value) -> bool { + auto *z = new Node(t_value, Color::Red, m_nil, m_nil, m_nil); + auto *y = m_nil; + auto *x = m_root; + + while (x != m_nil) { + y = x; + if (t_value < x->value) { + x = x->left; + } else if (x->value < t_value) { + x = x->right; + } else { + delete z; + return false; + } + } + + z->parent = y; + if (y == m_nil) { + m_root = z; + } else if (t_value < y->value) { + y->left = z; + } else { + y->right = z; + } + + fix_insertion_at(z); + ++node_count; + return true; +} + +template auto TreeSet::remove(const T &t_value) -> bool { + auto *z = search(t_value, m_root); + if (z == m_nil) { + return false; + } + + auto *y = z; + auto y_original_color = y->color; + Node *x = nullptr; + + if (z->left == m_nil) { + x = z->right; + transplant(z, z->right); + } else if (z->right == m_nil) { + x = z->left; + transplant(z, z->left); + } else { + y = z->right; + while (y->left != m_nil) { + y = y->left; + } + y_original_color = y->color; + x = y->right; + if (y->parent == z) { + x->parent = y; + } else { + transplant(y, y->right); + y->right = z->right; + y->right->parent = y; + } + transplant(z, y); + y->left = z->left; + y->left->parent = y; + y->color = z->color; + } + + delete z; + --node_count; + + if (y_original_color == Color::Black) { + fix_delete(x); + } + return true; +} + +template auto TreeSet::successor(Node *t_node) -> Node * { + if (t_node->right != m_nil) { + auto *node = t_node->right; + while (node->left != m_nil) { + node = node->left; + } + return node; + } + auto *parent = t_node->parent; + while (parent != m_nil && t_node == parent->right) { + t_node = parent; + parent = parent->parent; + } + return parent; +} + +template +auto TreeSet::predecessor(Node *t_node) -> Node * { + if (t_node->left != m_nil) { + auto *node = t_node->left; + while (node->right != m_nil) { + node = node->right; + } + return node; + } + auto *parent = t_node->parent; + while (parent != m_nil && t_node == parent->left) { + t_node = parent; + parent = parent->parent; + } + return parent; +} + +template +auto TreeSet::search(const T &t_value, Node *t_node) const + -> Node * { + if (t_node == m_nil) { + return m_nil; + } + if (t_value == t_node->value) { + return t_node; + } + if (t_value < t_node->value) { + return search(t_value, t_node->left); + } + return search(t_value, t_node->right); +} + +template +auto TreeSet::height(Node *t_node) const -> std::size_t { + if (t_node == m_nil) { + return 0; + } + if (t_node->left == m_nil && t_node->right == m_nil) { + return 1; + } + auto left_h = height(t_node->left); + auto right_h = height(t_node->right); + return (left_h > right_h ? left_h : right_h) + 1; +} + +template +auto TreeSet::fix_insertion_at(Node *t_node) -> void { + while (t_node->parent->color == Color::Red) { + if (t_node->parent == t_node->parent->parent->left) { + auto *uncle = t_node->parent->parent->right; + if (uncle->color == Color::Red) { + t_node->parent->color = Color::Black; + uncle->color = Color::Black; + t_node->parent->parent->color = Color::Red; + t_node = t_node->parent->parent; + } else { + if (t_node == t_node->parent->right) { + t_node = t_node->parent; + left_rotate(t_node); + } + t_node->parent->color = Color::Black; + t_node->parent->parent->color = Color::Red; + right_rotate(t_node->parent->parent); + } + } else { + auto *uncle = t_node->parent->parent->left; + if (uncle->color == Color::Red) { + t_node->parent->color = Color::Black; + uncle->color = Color::Black; + t_node->parent->parent->color = Color::Red; + t_node = t_node->parent->parent; + } else { + if (t_node == t_node->parent->left) { + t_node = t_node->parent; + right_rotate(t_node); + } + t_node->parent->color = Color::Black; + t_node->parent->parent->color = Color::Red; + left_rotate(t_node->parent->parent); + } + } + } + m_root->color = Color::Black; +} + +template auto TreeSet::left_rotate(Node *t_node) -> void { + auto *y = t_node->right; + t_node->right = y->left; + if (y->left != m_nil) { + y->left->parent = t_node; + } + y->parent = t_node->parent; + if (t_node->parent == m_nil) { + m_root = y; + } else if (t_node == t_node->parent->left) { + t_node->parent->left = y; + } else { + t_node->parent->right = y; + } + y->left = t_node; + t_node->parent = y; +} + +template auto TreeSet::right_rotate(Node *t_node) -> void { + auto *y = t_node->left; + t_node->left = y->right; + if (y->right != m_nil) { + y->right->parent = t_node; + } + y->parent = t_node->parent; + if (t_node->parent == m_nil) { + m_root = y; + } else if (t_node == t_node->parent->right) { + t_node->parent->right = y; + } else { + t_node->parent->left = y; + } + y->right = t_node; + t_node->parent = y; +} + +template +auto TreeSet::transplant(Node *t_u, Node *t_v) -> void { + if (t_u->parent == m_nil) { + m_root = t_v; + } else if (t_u == t_u->parent->left) { + t_u->parent->left = t_v; + } else { + t_u->parent->right = t_v; + } + t_v->parent = t_u->parent; +} + +template +auto TreeSet::swap_color(Node *t_a, Node *t_b) -> void { + std::swap(t_a->color, t_b->color); +} + +template +auto TreeSet::update_parent_child_link(Node *t_parent, + Node *t_old_child, + Node *t_new_child) -> void { + if (t_parent != m_nil) { + if (t_parent->left == t_old_child) { + t_parent->left = t_new_child; + } else { + t_parent->right = t_new_child; + } + } +} + +template auto TreeSet::fix_delete(Node *t_node) -> void { + while (t_node != m_root && t_node->color == Color::Black) { + if (t_node == t_node->parent->left) { + auto *w = t_node->parent->right; + if (w->color == Color::Red) { + w->color = Color::Black; + t_node->parent->color = Color::Red; + left_rotate(t_node->parent); + w = t_node->parent->right; + } + if (w->left->color == Color::Black && w->right->color == Color::Black) { + w->color = Color::Red; + t_node = t_node->parent; + } else { + if (w->right->color == Color::Black) { + w->left->color = Color::Black; + w->color = Color::Red; + right_rotate(w); + w = t_node->parent->right; + } + w->color = t_node->parent->color; + t_node->parent->color = Color::Black; + w->right->color = Color::Black; + left_rotate(t_node->parent); + t_node = m_root; + } + } else { + auto *w = t_node->parent->left; + if (w->color == Color::Red) { + w->color = Color::Black; + t_node->parent->color = Color::Red; + right_rotate(t_node->parent); + w = t_node->parent->left; + } + if (w->right->color == Color::Black && w->left->color == Color::Black) { + w->color = Color::Red; + t_node = t_node->parent; + } else { + if (w->left->color == Color::Black) { + w->right->color = Color::Black; + w->color = Color::Red; + left_rotate(w); + w = t_node->parent->left; + } + w->color = t_node->parent->color; + t_node->parent->color = Color::Black; + w->left->color = Color::Black; + right_rotate(t_node->parent); + t_node = m_root; + } + } + } + t_node->color = Color::Black; +} + +template auto TreeSet::leftmost() const -> Node * { + if (m_root == m_nil) { + return m_nil; + } + auto *node = m_root; + while (node->left != m_nil) { + node = node->left; + } + return node; +} + +template auto TreeSet::rightmost() const -> Node * { + if (m_root == m_nil) { + return m_nil; + } + auto *node = m_root; + while (node->right != m_nil) { + node = node->right; + } + return node; +} + +template auto TreeSet::first_postorder() const -> Node * { + if (m_root == m_nil) { + return m_nil; + } + auto *node = m_root; + while (true) { + if (node->left != m_nil) { + node = node->left; + } else if (node->right != m_nil) { + node = node->right; + } else { + break; + } + } + return node; +} + +template auto TreeSet::last_preorder() const -> Node * { + if (m_root == m_nil) { + return m_nil; + } + auto *node = m_root; + while (true) { + if (node->right != m_nil) { + node = node->right; + } else if (node->left != m_nil) { + node = node->left; + } else { + break; + } + } + return node; +} + +template auto TreeSet::size() const noexcept -> std::size_t { + return node_count; +} + +template auto TreeSet::height() const noexcept -> std::size_t { + return height(m_root); +} + +template auto TreeSet::is_empty() const noexcept -> bool { + return node_count == 0; +} + +template +auto TreeSet::is_element(const T &t_value) const noexcept -> bool { + return search(t_value, m_root) != m_nil; +} + +template auto TreeSet::root() noexcept -> Node * { + return m_root; +} + +template +auto TreeSet::min(Node *t_node) noexcept -> Node * { + while (t_node->left != m_nil) { + t_node = t_node->left; + } + return t_node; +} + +template +auto TreeSet::max(Node *t_node) noexcept -> Node * { + while (t_node->right != m_nil) { + t_node = t_node->right; + } + return t_node; +} + +template +auto TreeSet::begin() noexcept -> TreeSetIterator { + return TreeSetIterator(leftmost(), this, false); +} + +template +auto TreeSet::end() noexcept -> TreeSetIterator { + return TreeSetIterator(m_nil, this, false); +} + +template +auto TreeSet::rbegin() noexcept -> TreeSetIterator { + return TreeSetIterator(rightmost(), this, true); +} + +template +auto TreeSet::rend() noexcept -> TreeSetIterator { + return TreeSetIterator(m_nil, this, true); +} + +template +auto TreeSet::cbegin() const noexcept + -> TreeSetIterator { + return TreeSetIterator(leftmost(), this, false); +} + +template +auto TreeSet::cend() const noexcept -> TreeSetIterator { + return TreeSetIterator(m_nil, this, false); +} + +template +auto TreeSet::crbegin() const noexcept + -> TreeSetIterator { + return TreeSetIterator(rightmost(), this, true); +} + +template +auto TreeSet::crend() const noexcept -> TreeSetIterator { + return TreeSetIterator(m_nil, this, true); +} + +template +template +auto TreeSet::begin() noexcept -> TreeSetIterator { + if constexpr (std::is_same_v) { + return TreeSetIterator(m_root, this, false); + } else if constexpr (std::is_same_v) { + return TreeSetIterator(first_postorder(), this, false); + } else { + return TreeSetIterator(leftmost(), this, false); + } +} + +template +template +auto TreeSet::end() noexcept -> TreeSetIterator { + return TreeSetIterator(m_nil, this, false); +} + +template +template +auto TreeSet::rbegin() noexcept -> TreeSetIterator { + if constexpr (std::is_same_v) { + return TreeSetIterator(last_preorder(), this, true); + } else if constexpr (std::is_same_v) { + return TreeSetIterator(m_root, this, true); + } else { + return TreeSetIterator(rightmost(), this, true); + } +} + +template +template +auto TreeSet::rend() noexcept -> TreeSetIterator { + return TreeSetIterator(m_nil, this, true); +} + +template +template +auto TreeSet::cbegin() const noexcept + -> TreeSetIterator { + if constexpr (std::is_same_v) { + return TreeSetIterator(m_root, this, false); + } else if constexpr (std::is_same_v) { + return TreeSetIterator(first_postorder(), this, false); + } else { + return TreeSetIterator(leftmost(), this, false); + } +} + +template +template +auto TreeSet::cend() const noexcept -> TreeSetIterator { + return TreeSetIterator(m_nil, this, false); +} + +template +template +auto TreeSet::crbegin() const noexcept + -> TreeSetIterator { + if constexpr (std::is_same_v) { + return TreeSetIterator(last_preorder(), this, true); + } else if constexpr (std::is_same_v) { + return TreeSetIterator(m_root, this, true); + } else { + return TreeSetIterator(rightmost(), this, true); + } +} + +template +template +auto TreeSet::crend() const noexcept -> TreeSetIterator { + return TreeSetIterator(m_nil, this, true); +} + +#endif // TREESET_TPP From ad6e84ccb5b1f8e51c92c35ec185bc08f3710383 Mon Sep 17 00:00:00 2001 From: Dave Amiana Date: Mon, 13 Jul 2026 07:56:20 +0800 Subject: [PATCH 06/13] updated type trait constraint for ordered rbt --- include/TreeSet.hpp | 73 +++++++++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/include/TreeSet.hpp b/include/TreeSet.hpp index 80080c7..172d574 100644 --- a/include/TreeSet.hpp +++ b/include/TreeSet.hpp @@ -2,6 +2,8 @@ #define TREESET_HPP #include +#include +#include #include #include @@ -33,9 +35,27 @@ inline constexpr inorder_t inorder{}; inline constexpr preorder_t preorder{}; inline constexpr postorder_t postorder{}; -template class TreeSet; +template > class TreeSet; -template +namespace detail { +template +struct is_comparator_valid : std::false_type {}; + +template +struct is_comparator_valid< + T, Compare, + std::enable_if_t()(std::declval(), + std::declval())), + bool>>> : std::true_type {}; + +template +inline constexpr bool is_comparator_valid_v = + is_comparator_valid::value; +} // namespace detail + +template > class TreeSetIterator { public: using value_type = T; @@ -45,7 +65,8 @@ class TreeSetIterator { using iterator_category = std::bidirectional_iterator_tag; TreeSetIterator() = default; - TreeSetIterator(Node *t_current, const TreeSet *t_tree, bool t_reverse) + TreeSetIterator(Node *t_current, const TreeSet *t_tree, + bool t_reverse) : m_current(t_current), m_tree(t_tree), m_reverse(t_reverse) {} auto operator*() const -> reference { return m_current->value; } @@ -91,18 +112,23 @@ class TreeSetIterator { private: Node *m_current{nullptr}; - const TreeSet *m_tree{nullptr}; + const TreeSet *m_tree{nullptr}; bool m_reverse{false}; - template friend class TreeSet; + template friend class TreeSet; auto advance_forward() -> void; auto advance_backward() -> void; }; -template class TreeSet { +template class TreeSet { + static_assert(detail::is_comparator_valid_v, + "Compare must be callable with (const T&, const T&) and return " + "a type convertible to bool"); + public: TreeSet(); + TreeSet(std::initializer_list t_init); ~TreeSet(); TreeSet(const TreeSet &) = delete; @@ -120,33 +146,33 @@ template class TreeSet { auto min(Node *t_node) noexcept -> Node *; auto max(Node *t_node) noexcept -> Node *; - auto begin() noexcept -> TreeSetIterator; - auto end() noexcept -> TreeSetIterator; - auto rbegin() noexcept -> TreeSetIterator; - auto rend() noexcept -> TreeSetIterator; + auto begin() noexcept -> TreeSetIterator; + auto end() noexcept -> TreeSetIterator; + auto rbegin() noexcept -> TreeSetIterator; + auto rend() noexcept -> TreeSetIterator; - auto cbegin() const noexcept -> TreeSetIterator; - auto cend() const noexcept -> TreeSetIterator; - auto crbegin() const noexcept -> TreeSetIterator; - auto crend() const noexcept -> TreeSetIterator; + auto cbegin() const noexcept -> TreeSetIterator; + auto cend() const noexcept -> TreeSetIterator; + auto crbegin() const noexcept -> TreeSetIterator; + auto crend() const noexcept -> TreeSetIterator; template - auto begin() noexcept -> TreeSetIterator; + auto begin() noexcept -> TreeSetIterator; template - auto end() noexcept -> TreeSetIterator; + auto end() noexcept -> TreeSetIterator; template - auto rbegin() noexcept -> TreeSetIterator; + auto rbegin() noexcept -> TreeSetIterator; template - auto rend() noexcept -> TreeSetIterator; + auto rend() noexcept -> TreeSetIterator; template - auto cbegin() const noexcept -> TreeSetIterator; + auto cbegin() const noexcept -> TreeSetIterator; template - auto cend() const noexcept -> TreeSetIterator; + auto cend() const noexcept -> TreeSetIterator; template - auto crbegin() const noexcept -> TreeSetIterator; + auto crbegin() const noexcept -> TreeSetIterator; template - auto crend() const noexcept -> TreeSetIterator; + auto crend() const noexcept -> TreeSetIterator; private: auto fix_insertion_at(Node *t_node) -> void; @@ -173,8 +199,9 @@ template class TreeSet { Node *m_nil{nullptr}; Node *m_root{nullptr}; std::size_t node_count{0}; + Compare m_comp{}; - template + template friend class TreeSetIterator; }; From d5cce5603fbe6a5a68d625db45754f7f01f88721 Mon Sep 17 00:00:00 2001 From: Dave Amiana Date: Mon, 13 Jul 2026 07:56:43 +0800 Subject: [PATCH 07/13] udpated interface for rbt impl --- include/detail/Iterator.tpp | 10 +- include/detail/TreeSet.tpp | 252 +++++++++++++++++++++--------------- 2 files changed, 152 insertions(+), 110 deletions(-) diff --git a/include/detail/Iterator.tpp b/include/detail/Iterator.tpp index 3eccdff..cc7f795 100644 --- a/include/detail/Iterator.tpp +++ b/include/detail/Iterator.tpp @@ -3,8 +3,9 @@ #include -template -auto TreeSetIterator::advance_forward() -> void { +template +auto TreeSetIterator::advance_forward() + -> void { if constexpr (std::is_same_v) { if (m_current->right != m_tree->m_nil) { m_current = m_current->right; @@ -61,8 +62,9 @@ auto TreeSetIterator::advance_forward() -> void { } } -template -auto TreeSetIterator::advance_backward() -> void { +template +auto TreeSetIterator::advance_backward() + -> void { if constexpr (std::is_same_v) { if (m_current == m_tree->m_nil) { m_current = m_tree->m_root; diff --git a/include/detail/TreeSet.tpp b/include/detail/TreeSet.tpp index 23d1101..25e1ece 100644 --- a/include/detail/TreeSet.tpp +++ b/include/detail/TreeSet.tpp @@ -3,19 +3,26 @@ #include -template -TreeSet::TreeSet() +template +TreeSet::TreeSet() : m_nil(new Node(T{}, Color::Black, nullptr, nullptr, nullptr)), m_root(m_nil) { m_nil->left = m_nil->right = m_nil->parent = m_nil; } -template TreeSet::~TreeSet() { +template +TreeSet::TreeSet(std::initializer_list t_init) : TreeSet() { + for (const auto &val : t_init) + insert(val); +} + +template TreeSet::~TreeSet() { destroy(m_root); delete m_nil; } -template auto TreeSet::destroy(Node *t_node) -> void { +template +auto TreeSet::destroy(Node *t_node) -> void { if (t_node != m_nil) { destroy(t_node->left); destroy(t_node->right); @@ -23,16 +30,17 @@ template auto TreeSet::destroy(Node *t_node) -> void { } } -template auto TreeSet::insert(const T &t_value) -> bool { +template +auto TreeSet::insert(const T &t_value) -> bool { auto *z = new Node(t_value, Color::Red, m_nil, m_nil, m_nil); auto *y = m_nil; auto *x = m_root; while (x != m_nil) { y = x; - if (t_value < x->value) { + if (m_comp(t_value, x->value)) { x = x->left; - } else if (x->value < t_value) { + } else if (m_comp(x->value, t_value)) { x = x->right; } else { delete z; @@ -43,7 +51,7 @@ template auto TreeSet::insert(const T &t_value) -> bool { z->parent = y; if (y == m_nil) { m_root = z; - } else if (t_value < y->value) { + } else if (m_comp(t_value, y->value)) { y->left = z; } else { y->right = z; @@ -54,7 +62,8 @@ template auto TreeSet::insert(const T &t_value) -> bool { return true; } -template auto TreeSet::remove(const T &t_value) -> bool { +template +auto TreeSet::remove(const T &t_value) -> bool { auto *z = search(t_value, m_root); if (z == m_nil) { return false; @@ -99,7 +108,8 @@ template auto TreeSet::remove(const T &t_value) -> bool { return true; } -template auto TreeSet::successor(Node *t_node) -> Node * { +template +auto TreeSet::successor(Node *t_node) -> Node * { if (t_node->right != m_nil) { auto *node = t_node->right; while (node->left != m_nil) { @@ -115,8 +125,8 @@ template auto TreeSet::successor(Node *t_node) -> Node * { return parent; } -template -auto TreeSet::predecessor(Node *t_node) -> Node * { +template +auto TreeSet::predecessor(Node *t_node) -> Node * { if (t_node->left != m_nil) { auto *node = t_node->left; while (node->right != m_nil) { @@ -132,23 +142,23 @@ auto TreeSet::predecessor(Node *t_node) -> Node * { return parent; } -template -auto TreeSet::search(const T &t_value, Node *t_node) const - -> Node * { +template +auto TreeSet::search(const T &t_value, + Node *t_node) const -> Node * { if (t_node == m_nil) { return m_nil; } - if (t_value == t_node->value) { + if (!m_comp(t_value, t_node->value) && !m_comp(t_node->value, t_value)) { return t_node; } - if (t_value < t_node->value) { + if (m_comp(t_value, t_node->value)) { return search(t_value, t_node->left); } return search(t_value, t_node->right); } -template -auto TreeSet::height(Node *t_node) const -> std::size_t { +template +auto TreeSet::height(Node *t_node) const -> std::size_t { if (t_node == m_nil) { return 0; } @@ -160,8 +170,8 @@ auto TreeSet::height(Node *t_node) const -> std::size_t { return (left_h > right_h ? left_h : right_h) + 1; } -template -auto TreeSet::fix_insertion_at(Node *t_node) -> void { +template +auto TreeSet::fix_insertion_at(Node *t_node) -> void { while (t_node->parent->color == Color::Red) { if (t_node->parent == t_node->parent->parent->left) { auto *uncle = t_node->parent->parent->right; @@ -200,7 +210,8 @@ auto TreeSet::fix_insertion_at(Node *t_node) -> void { m_root->color = Color::Black; } -template auto TreeSet::left_rotate(Node *t_node) -> void { +template +auto TreeSet::left_rotate(Node *t_node) -> void { auto *y = t_node->right; t_node->right = y->left; if (y->left != m_nil) { @@ -218,7 +229,8 @@ template auto TreeSet::left_rotate(Node *t_node) -> void { t_node->parent = y; } -template auto TreeSet::right_rotate(Node *t_node) -> void { +template +auto TreeSet::right_rotate(Node *t_node) -> void { auto *y = t_node->left; t_node->left = y->right; if (y->right != m_nil) { @@ -236,8 +248,8 @@ template auto TreeSet::right_rotate(Node *t_node) -> void { t_node->parent = y; } -template -auto TreeSet::transplant(Node *t_u, Node *t_v) -> void { +template +auto TreeSet::transplant(Node *t_u, Node *t_v) -> void { if (t_u->parent == m_nil) { m_root = t_v; } else if (t_u == t_u->parent->left) { @@ -248,15 +260,14 @@ auto TreeSet::transplant(Node *t_u, Node *t_v) -> void { t_v->parent = t_u->parent; } -template -auto TreeSet::swap_color(Node *t_a, Node *t_b) -> void { +template +auto TreeSet::swap_color(Node *t_a, Node *t_b) -> void { std::swap(t_a->color, t_b->color); } -template -auto TreeSet::update_parent_child_link(Node *t_parent, - Node *t_old_child, - Node *t_new_child) -> void { +template +auto TreeSet::update_parent_child_link( + Node *t_parent, Node *t_old_child, Node *t_new_child) -> void { if (t_parent != m_nil) { if (t_parent->left == t_old_child) { t_parent->left = t_new_child; @@ -266,7 +277,8 @@ auto TreeSet::update_parent_child_link(Node *t_parent, } } -template auto TreeSet::fix_delete(Node *t_node) -> void { +template +auto TreeSet::fix_delete(Node *t_node) -> void { while (t_node != m_root && t_node->color == Color::Black) { if (t_node == t_node->parent->left) { auto *w = t_node->parent->right; @@ -321,7 +333,8 @@ template auto TreeSet::fix_delete(Node *t_node) -> void { t_node->color = Color::Black; } -template auto TreeSet::leftmost() const -> Node * { +template +auto TreeSet::leftmost() const -> Node * { if (m_root == m_nil) { return m_nil; } @@ -332,7 +345,8 @@ template auto TreeSet::leftmost() const -> Node * { return node; } -template auto TreeSet::rightmost() const -> Node * { +template +auto TreeSet::rightmost() const -> Node * { if (m_root == m_nil) { return m_nil; } @@ -343,7 +357,8 @@ template auto TreeSet::rightmost() const -> Node * { return node; } -template auto TreeSet::first_postorder() const -> Node * { +template +auto TreeSet::first_postorder() const -> Node * { if (m_root == m_nil) { return m_nil; } @@ -360,7 +375,8 @@ template auto TreeSet::first_postorder() const -> Node * { return node; } -template auto TreeSet::last_preorder() const -> Node * { +template +auto TreeSet::last_preorder() const -> Node * { if (m_root == m_nil) { return m_nil; } @@ -377,157 +393,181 @@ template auto TreeSet::last_preorder() const -> Node * { return node; } -template auto TreeSet::size() const noexcept -> std::size_t { +template +auto TreeSet::size() const noexcept -> std::size_t { return node_count; } -template auto TreeSet::height() const noexcept -> std::size_t { +template +auto TreeSet::height() const noexcept -> std::size_t { return height(m_root); } -template auto TreeSet::is_empty() const noexcept -> bool { +template +auto TreeSet::is_empty() const noexcept -> bool { return node_count == 0; } -template -auto TreeSet::is_element(const T &t_value) const noexcept -> bool { +template +auto TreeSet::is_element(const T &t_value) const noexcept -> bool { return search(t_value, m_root) != m_nil; } -template auto TreeSet::root() noexcept -> Node * { +template +auto TreeSet::root() noexcept -> Node * { return m_root; } -template -auto TreeSet::min(Node *t_node) noexcept -> Node * { +template +auto TreeSet::min(Node *t_node) noexcept -> Node * { while (t_node->left != m_nil) { t_node = t_node->left; } return t_node; } -template -auto TreeSet::max(Node *t_node) noexcept -> Node * { +template +auto TreeSet::max(Node *t_node) noexcept -> Node * { while (t_node->right != m_nil) { t_node = t_node->right; } return t_node; } -template -auto TreeSet::begin() noexcept -> TreeSetIterator { - return TreeSetIterator(leftmost(), this, false); +template +auto TreeSet::begin() noexcept + -> TreeSetIterator { + return TreeSetIterator(leftmost(), this, false); } -template -auto TreeSet::end() noexcept -> TreeSetIterator { - return TreeSetIterator(m_nil, this, false); +template +auto TreeSet::end() noexcept + -> TreeSetIterator { + return TreeSetIterator(m_nil, this, false); } -template -auto TreeSet::rbegin() noexcept -> TreeSetIterator { - return TreeSetIterator(rightmost(), this, true); +template +auto TreeSet::rbegin() noexcept + -> TreeSetIterator { + return TreeSetIterator(rightmost(), this, true); } -template -auto TreeSet::rend() noexcept -> TreeSetIterator { - return TreeSetIterator(m_nil, this, true); +template +auto TreeSet::rend() noexcept + -> TreeSetIterator { + return TreeSetIterator(m_nil, this, true); } -template -auto TreeSet::cbegin() const noexcept - -> TreeSetIterator { - return TreeSetIterator(leftmost(), this, false); +template +auto TreeSet::cbegin() const noexcept + -> TreeSetIterator { + return TreeSetIterator(leftmost(), this, false); } -template -auto TreeSet::cend() const noexcept -> TreeSetIterator { - return TreeSetIterator(m_nil, this, false); +template +auto TreeSet::cend() const noexcept + -> TreeSetIterator { + return TreeSetIterator(m_nil, this, false); } -template -auto TreeSet::crbegin() const noexcept - -> TreeSetIterator { - return TreeSetIterator(rightmost(), this, true); +template +auto TreeSet::crbegin() const noexcept + -> TreeSetIterator { + return TreeSetIterator(rightmost(), this, true); } -template -auto TreeSet::crend() const noexcept -> TreeSetIterator { - return TreeSetIterator(m_nil, this, true); +template +auto TreeSet::crend() const noexcept + -> TreeSetIterator { + return TreeSetIterator(m_nil, this, true); } -template +template template -auto TreeSet::begin() noexcept -> TreeSetIterator { +auto TreeSet::begin() noexcept + -> TreeSetIterator { if constexpr (std::is_same_v) { - return TreeSetIterator(m_root, this, false); + return TreeSetIterator(m_root, this, false); } else if constexpr (std::is_same_v) { - return TreeSetIterator(first_postorder(), this, false); + return TreeSetIterator(first_postorder(), + this, false); } else { - return TreeSetIterator(leftmost(), this, false); + return TreeSetIterator(leftmost(), this, + false); } } -template +template template -auto TreeSet::end() noexcept -> TreeSetIterator { - return TreeSetIterator(m_nil, this, false); +auto TreeSet::end() noexcept + -> TreeSetIterator { + return TreeSetIterator(m_nil, this, false); } -template +template template -auto TreeSet::rbegin() noexcept -> TreeSetIterator { +auto TreeSet::rbegin() noexcept + -> TreeSetIterator { if constexpr (std::is_same_v) { - return TreeSetIterator(last_preorder(), this, true); + return TreeSetIterator(last_preorder(), this, + true); } else if constexpr (std::is_same_v) { - return TreeSetIterator(m_root, this, true); + return TreeSetIterator(m_root, this, true); } else { - return TreeSetIterator(rightmost(), this, true); + return TreeSetIterator(rightmost(), this, + true); } } -template +template template -auto TreeSet::rend() noexcept -> TreeSetIterator { - return TreeSetIterator(m_nil, this, true); +auto TreeSet::rend() noexcept + -> TreeSetIterator { + return TreeSetIterator(m_nil, this, true); } -template +template template -auto TreeSet::cbegin() const noexcept - -> TreeSetIterator { +auto TreeSet::cbegin() const noexcept + -> TreeSetIterator { if constexpr (std::is_same_v) { - return TreeSetIterator(m_root, this, false); + return TreeSetIterator(m_root, this, false); } else if constexpr (std::is_same_v) { - return TreeSetIterator(first_postorder(), this, false); + return TreeSetIterator(first_postorder(), this, + false); } else { - return TreeSetIterator(leftmost(), this, false); + return TreeSetIterator(leftmost(), this, + false); } } -template +template template -auto TreeSet::cend() const noexcept -> TreeSetIterator { - return TreeSetIterator(m_nil, this, false); +auto TreeSet::cend() const noexcept + -> TreeSetIterator { + return TreeSetIterator(m_nil, this, false); } -template +template template -auto TreeSet::crbegin() const noexcept - -> TreeSetIterator { +auto TreeSet::crbegin() const noexcept + -> TreeSetIterator { if constexpr (std::is_same_v) { - return TreeSetIterator(last_preorder(), this, true); + return TreeSetIterator(last_preorder(), this, + true); } else if constexpr (std::is_same_v) { - return TreeSetIterator(m_root, this, true); + return TreeSetIterator(m_root, this, true); } else { - return TreeSetIterator(rightmost(), this, true); + return TreeSetIterator(rightmost(), this, + true); } } -template +template template -auto TreeSet::crend() const noexcept -> TreeSetIterator { - return TreeSetIterator(m_nil, this, true); +auto TreeSet::crend() const noexcept + -> TreeSetIterator { + return TreeSetIterator(m_nil, this, true); } #endif // TREESET_TPP From c1ba2b16ee98cd61cac9b6ea798fa6bc51c298f8 Mon Sep 17 00:00:00 2001 From: Dave Amiana Date: Mon, 13 Jul 2026 07:58:04 +0800 Subject: [PATCH 08/13] updated test group to include removal rotations, sfinae validation, initializer_list conf and custom comparator --- test/unittest.cpp | 390 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) diff --git a/test/unittest.cpp b/test/unittest.cpp index d0fc69d..af80f63 100644 --- a/test/unittest.cpp +++ b/test/unittest.cpp @@ -2,7 +2,9 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include +#include #include +#include #include using ::testing::ElementsAre; @@ -58,6 +60,44 @@ class TreeSetTest : public ::testing::Test { } }; +class TreeSetGreaterCompTest : public ::testing::Test { +protected: + TreeSet> tree; + + auto inorder_vec() -> std::vector { + std::vector result; + for (auto it = tree.begin(); it != tree.end(); ++it) + result.push_back(*it); + return result; + } + + auto reverse_inorder_vec() -> std::vector { + std::vector result; + for (auto it = tree.rbegin(); it != tree.rend(); ++it) + result.push_back(*it); + return result; + } + + auto contains_all(const std::vector &v, + std::initializer_list expected) -> bool { + for (int val : expected) { + if (std::find(v.begin(), v.end(), val) == v.end()) + return false; + } + return true; + } +}; + +class TreeSetInitListTest : public ::testing::Test { +protected: + auto inorder_vec(const TreeSet &t) -> std::vector { + std::vector result; + for (auto it = t.cbegin(); it != t.cend(); ++it) + result.push_back(*it); + return result; + } +}; + // ============================================================================ // Group 1: Insert and Remove // ============================================================================ @@ -579,3 +619,353 @@ TEST_F(TreeSetTest, RemoveAllElements) { EXPECT_EQ(tree.size(), 0u); EXPECT_TRUE(tree.begin() == tree.end()); } + +// ============================================================================ +// Group 11: Default Comparator (std::less) +// ============================================================================ + +TEST_F(TreeSetTest, DefaultComparatorSortsAscending) { + for (int v : {5, 3, 7, 1, 4, 6, 8}) + ASSERT_TRUE(tree.insert(v)); + EXPECT_THAT(inorder_vec(), ElementsAre(1, 3, 4, 5, 6, 7, 8)); +} + +TEST_F(TreeSetTest, DefaultComparatorDuplicateRejection) { + ASSERT_TRUE(tree.insert(5)); + EXPECT_FALSE(tree.insert(5)); + EXPECT_EQ(tree.size(), 1u); +} + +TEST_F(TreeSetTest, DefaultComparatorLookup) { + for (int v : {5, 3, 7, 1, 4}) + ASSERT_TRUE(tree.insert(v)); + EXPECT_TRUE(tree.is_element(1)); + EXPECT_TRUE(tree.is_element(5)); + EXPECT_FALSE(tree.is_element(99)); +} + +// ============================================================================ +// Group 12: Custom Comparator (std::greater) +// ============================================================================ + +TEST_F(TreeSetGreaterCompTest, InsertAndSize) { + ASSERT_TRUE(tree.insert(5)); + ASSERT_TRUE(tree.insert(3)); + ASSERT_TRUE(tree.insert(7)); + EXPECT_EQ(tree.size(), 3u); +} + +TEST_F(TreeSetGreaterCompTest, InorderSortsDescending) { + for (int v : {5, 3, 7, 1, 4, 6, 8}) + ASSERT_TRUE(tree.insert(v)); + EXPECT_THAT(inorder_vec(), ElementsAre(8, 7, 6, 5, 4, 3, 1)); +} + +TEST_F(TreeSetGreaterCompTest, DuplicateRejection) { + ASSERT_TRUE(tree.insert(5)); + EXPECT_FALSE(tree.insert(5)); + EXPECT_EQ(tree.size(), 1u); +} + +TEST_F(TreeSetGreaterCompTest, Lookup) { + for (int v : {5, 3, 7, 1, 4}) + ASSERT_TRUE(tree.insert(v)); + EXPECT_TRUE(tree.is_element(1)); + EXPECT_TRUE(tree.is_element(5)); + EXPECT_FALSE(tree.is_element(99)); +} + +TEST_F(TreeSetGreaterCompTest, Remove) { + for (int v : {5, 3, 7, 1, 4}) + ASSERT_TRUE(tree.insert(v)); + ASSERT_TRUE(tree.remove(4)); + EXPECT_FALSE(tree.is_element(4)); + EXPECT_EQ(tree.size(), 4u); + auto v = inorder_vec(); + EXPECT_TRUE(std::is_sorted(v.begin(), v.end(), std::greater{})); +} + +TEST_F(TreeSetGreaterCompTest, ReverseTraversalAscending) { + for (int v : {5, 3, 7, 1, 4}) + ASSERT_TRUE(tree.insert(v)); + EXPECT_THAT(reverse_inorder_vec(), ElementsAre(1, 3, 4, 5, 7)); +} + +TEST_F(TreeSetGreaterCompTest, RemoveRedLeaf) { + for (int v : {5, 3, 7}) + ASSERT_TRUE(tree.insert(v)); + ASSERT_TRUE(tree.remove(3)); + EXPECT_FALSE(tree.is_element(3)); + EXPECT_EQ(tree.size(), 2u); + auto in = inorder_vec(); + EXPECT_TRUE(std::is_sorted(in.begin(), in.end(), std::greater{})); +} + +TEST_F(TreeSetGreaterCompTest, RemoveAllElements) { + for (int v : {5, 3, 7}) + ASSERT_TRUE(tree.insert(v)); + ASSERT_TRUE(tree.remove(3)); + ASSERT_TRUE(tree.remove(5)); + ASSERT_TRUE(tree.remove(7)); + EXPECT_TRUE(tree.is_empty()); +} + +// ============================================================================ +// Group 13: Initializer List +// ============================================================================ + +TEST_F(TreeSetInitListTest, EmptyInitializerList) { + TreeSet t{}; + EXPECT_TRUE(t.is_empty()); + EXPECT_EQ(t.size(), 0u); +} + +TEST_F(TreeSetInitListTest, SingleElement) { + TreeSet t{5}; + EXPECT_EQ(t.size(), 1u); + EXPECT_TRUE(t.is_element(5)); +} + +TEST_F(TreeSetInitListTest, MultipleElements) { + TreeSet t{5, 3, 7, 1, 4}; + EXPECT_EQ(t.size(), 5u); + EXPECT_THAT(inorder_vec(t), ElementsAre(1, 3, 4, 5, 7)); +} + +TEST_F(TreeSetInitListTest, DuplicatesIgnored) { + TreeSet t{5, 3, 5, 7, 3}; + EXPECT_EQ(t.size(), 3u); + EXPECT_THAT(inorder_vec(t), ElementsAre(3, 5, 7)); +} + +TEST_F(TreeSetInitListTest, AlreadySorted) { + TreeSet t{1, 2, 3, 4, 5}; + EXPECT_EQ(t.size(), 5u); + EXPECT_THAT(inorder_vec(t), ElementsAre(1, 2, 3, 4, 5)); +} + +TEST_F(TreeSetInitListTest, ReverseSorted) { + TreeSet t{5, 4, 3, 2, 1}; + EXPECT_EQ(t.size(), 5u); + EXPECT_THAT(inorder_vec(t), ElementsAre(1, 2, 3, 4, 5)); +} + +TEST_F(TreeSetInitListTest, WithCustomComparator) { + TreeSet> t{5, 3, 7, 1, 4}; + EXPECT_EQ(t.size(), 5u); + std::vector result; + for (auto it = t.begin(); it != t.end(); ++it) + result.push_back(*it); + EXPECT_THAT(result, ElementsAre(7, 5, 4, 3, 1)); +} + +TEST_F(TreeSetInitListTest, InsertAfterConstruction) { + TreeSet t{3, 1, 5}; + ASSERT_TRUE(t.insert(4)); + ASSERT_TRUE(t.insert(2)); + EXPECT_EQ(t.size(), 5u); + EXPECT_THAT(inorder_vec(t), ElementsAre(1, 2, 3, 4, 5)); +} + +TEST_F(TreeSetInitListTest, RemoveAfterConstruction) { + TreeSet t{5, 3, 7, 1, 4}; + ASSERT_TRUE(t.remove(3)); + ASSERT_TRUE(t.remove(7)); + EXPECT_EQ(t.size(), 3u); + EXPECT_THAT(inorder_vec(t), ElementsAre(1, 4, 5)); +} + +// ============================================================================ +// Group 14: SFINAE Validation +// ============================================================================ + +TEST(SfinaeValidationTest, ValidComparatorStdLess) { + EXPECT_TRUE((detail::is_comparator_valid_v>)); +} + +TEST(SfinaeValidationTest, ValidComparatorStdGreater) { + EXPECT_TRUE((detail::is_comparator_valid_v>)); +} + +TEST(SfinaeValidationTest, ValidComparatorLambda) { + auto comp = [](int a, int b) { return a < b; }; + EXPECT_TRUE((detail::is_comparator_valid_v)); +} + +TEST(SfinaeValidationTest, ValidComparatorFunctor) { + struct IntComp { + auto operator()(int a, int b) const -> bool { return a < b; } + }; + EXPECT_TRUE((detail::is_comparator_valid_v)); +} + +TEST(SfinaeValidationTest, InvalidComparatorNotCallable) { + struct NotCallable {}; + EXPECT_FALSE((detail::is_comparator_valid_v)); +} + +TEST(SfinaeValidationTest, InvalidComparatorWrongArgCount) { + struct OneArg { + auto operator()(int) const -> bool { return true; } + }; + EXPECT_FALSE((detail::is_comparator_valid_v)); +} + +TEST(SfinaeValidationTest, InvalidComparatorWrongArgType) { + struct WrongType { + auto operator()(std::string, std::string) const -> bool { return true; } + }; + EXPECT_FALSE((detail::is_comparator_valid_v)); +} + +TEST(SfinaeValidationTest, InvalidComparatorNonBoolReturn) { + struct ReturnsString { + auto operator()(int, int) const -> std::string { return ""; } + }; + EXPECT_FALSE((detail::is_comparator_valid_v)); +} + +TEST(SfinaeValidationTest, StringWithLess) { + EXPECT_TRUE( + (detail::is_comparator_valid_v>)); +} + +TEST(SfinaeValidationTest, StringWithGreater) { + EXPECT_TRUE( + (detail::is_comparator_valid_v>)); +} + +// ============================================================================ +// Group 15: Removal Rotation Cases +// ============================================================================ + +TEST_F(TreeSetTest, RemoveTriggersCase2_SiblingBlackBothChildrenBlack) { + // 10(B) + // / \ + // 5(B) 15(B) + // / \ + // 1(B) 7(B) + // + // Remove 7 (black leaf) -> sibling 1 is black, both children black + // -> recolor sibling red, move up to parent 5 + for (int v : {10, 5, 15, 1, 7}) + ASSERT_TRUE(tree.insert(v)); + + ASSERT_TRUE(tree.remove(7)); + EXPECT_FALSE(tree.is_element(7)); + EXPECT_EQ(tree.size(), 4u); + EXPECT_THAT(inorder_vec(), ElementsAre(1, 5, 10, 15)); +} + +TEST_F(TreeSetTest, RemoveTriggersCase3_SiblingBlackLeftChildRed) { + // 20(B) + // / \ + // 10(B) 25(B) + // / \ + // 5(B) 15(R) + // / + // 1(R) + // + // Remove 1 (red) -> no fixup, tree unchanged + // Remove 5 (black, now leaf) -> sibling 15 is red, triggers rotation + // After fixup: 15 becomes root, 10 left, 20 right + for (int v : {20, 10, 25, 5, 15, 1}) + ASSERT_TRUE(tree.insert(v)); + + ASSERT_TRUE(tree.remove(1)); + EXPECT_EQ(tree.size(), 5u); + EXPECT_THAT(inorder_vec(), ElementsAre(5, 10, 15, 20, 25)); + + ASSERT_TRUE(tree.remove(5)); + EXPECT_EQ(tree.size(), 4u); + auto in = inorder_vec(); + EXPECT_THAT(in, ElementsAre(10, 15, 20, 25)); + EXPECT_TRUE(is_sorted_ascending(in)); +} + +TEST_F(TreeSetTest, RemoveTriggersCase4_SiblingBlackRightChildRed) { + // 20(B) + // / \ + // 10(B) 25(B) + // / \ + // 5(B) 15(B) + // \ + // 18(R) + // + // Remove 5 (black leaf) -> sibling 15 has red right child 18 + // -> left rotate on 10, recolor + for (int v : {20, 10, 25, 5, 15, 18}) + ASSERT_TRUE(tree.insert(v)); + + ASSERT_TRUE(tree.remove(5)); + EXPECT_EQ(tree.size(), 5u); + auto in = inorder_vec(); + EXPECT_THAT(in, ElementsAre(10, 15, 18, 20, 25)); + EXPECT_TRUE(is_sorted_ascending(in)); +} + +TEST_F(TreeSetTest, RemoveMaintainsRootBlack) { + for (int v : {10, 5, 15, 1, 7, 12, 20}) + ASSERT_TRUE(tree.insert(v)); + + ASSERT_TRUE(tree.remove(1)); + EXPECT_EQ(tree.root()->color, Color::Black); + + ASSERT_TRUE(tree.remove(7)); + EXPECT_EQ(tree.root()->color, Color::Black); +} + +TEST_F(TreeSetTest, RemoveRedLeafNoFixup) { + for (int v : {10, 5, 15}) + ASSERT_TRUE(tree.insert(v)); + + ASSERT_TRUE(tree.remove(5)); + EXPECT_EQ(tree.size(), 2u); + EXPECT_FALSE(tree.is_element(5)); + EXPECT_THAT(inorder_vec(), ElementsAre(10, 15)); +} + +TEST_F(TreeSetTest, RemoveSequentialMaintainsInvariant) { + for (int i = 1; i <= 15; ++i) + ASSERT_TRUE(tree.insert(i)); + + for (int i = 1; i <= 10; ++i) { + ASSERT_TRUE(tree.remove(i)); + auto in = inorder_vec(); + EXPECT_TRUE(is_sorted_ascending(in)); + EXPECT_EQ(in.size(), static_cast(15 - i)); + } + + EXPECT_THAT(inorder_vec(), ElementsAre(11, 12, 13, 14, 15)); +} + +TEST_F(TreeSetTest, RemoveNonexistentInPopulatedTree) { + for (int v : {10, 5, 15, 1, 7, 12, 20}) + ASSERT_TRUE(tree.insert(v)); + + EXPECT_FALSE(tree.remove(99)); + EXPECT_EQ(tree.size(), 7u); + EXPECT_THAT(inorder_vec(), ElementsAre(1, 5, 7, 10, 12, 15, 20)); +} + +TEST_F(TreeSetTest, RemoveRootWithTwoChildren) { + for (int v : {10, 5, 15, 3, 7, 12, 20}) + ASSERT_TRUE(tree.insert(v)); + + ASSERT_TRUE(tree.remove(10)); + EXPECT_FALSE(tree.is_element(10)); + EXPECT_EQ(tree.size(), 6u); + auto in = inorder_vec(); + EXPECT_TRUE(is_sorted_ascending(in)); + EXPECT_EQ(tree.root()->color, Color::Black); +} + +TEST_F(TreeSetGreaterCompTest, RemoveTriggersRotationWithGreaterComp) { + for (int v : {10, 15, 5, 20, 12, 3, 7}) + ASSERT_TRUE(tree.insert(v)); + + ASSERT_TRUE(tree.remove(3)); + EXPECT_EQ(tree.size(), 6u); + auto in = inorder_vec(); + EXPECT_TRUE(std::is_sorted(in.begin(), in.end(), std::greater{})); +} From 0ab931fffe072d3803f7a44d208fa29600fdb08d Mon Sep 17 00:00:00 2001 From: Dave Amiana Date: Mon, 13 Jul 2026 08:03:27 +0800 Subject: [PATCH 09/13] setting freed pointer to nullptr state to guarantee correctness and prevent invalid memor handling --- include/detail/TreeSet.tpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/detail/TreeSet.tpp b/include/detail/TreeSet.tpp index 25e1ece..7199976 100644 --- a/include/detail/TreeSet.tpp +++ b/include/detail/TreeSet.tpp @@ -19,6 +19,7 @@ TreeSet::TreeSet(std::initializer_list t_init) : TreeSet() { template TreeSet::~TreeSet() { destroy(m_root); delete m_nil; + m_nil = nullptr; } template @@ -27,6 +28,7 @@ auto TreeSet::destroy(Node *t_node) -> void { destroy(t_node->left); destroy(t_node->right); delete t_node; + t_node = nullptr; } } @@ -44,6 +46,7 @@ auto TreeSet::insert(const T &t_value) -> bool { x = x->right; } else { delete z; + z = nullptr; return false; } } @@ -100,6 +103,7 @@ auto TreeSet::remove(const T &t_value) -> bool { } delete z; + z = nullptr; --node_count; if (y_original_color == Color::Black) { From a86bb70fbce462cd74ccb756680788367d3e95bf Mon Sep 17 00:00:00 2001 From: Dave Amiana Date: Mon, 13 Jul 2026 08:26:35 +0800 Subject: [PATCH 10/13] udpated coupled interface for treeset operators and added copy and move semantics to TreeSet --- include/TreeSet.hpp | 39 ++++++++++++++++++++++++++++++++++++++ include/detail/TreeSet.tpp | 29 ++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/include/TreeSet.hpp b/include/TreeSet.hpp index 172d574..16234aa 100644 --- a/include/TreeSet.hpp +++ b/include/TreeSet.hpp @@ -6,6 +6,7 @@ #include #include #include +#include enum Color : bool { Red = true, @@ -134,6 +135,9 @@ template class TreeSet { TreeSet(const TreeSet &) = delete; auto operator=(const TreeSet &) -> TreeSet & = delete; + TreeSet(TreeSet &&t_other); + auto operator=(TreeSet &&t_other) -> TreeSet &; + auto insert(const T &t_value) -> bool; auto remove(const T &t_value) -> bool; @@ -203,6 +207,41 @@ template class TreeSet { template friend class TreeSetIterator; + + template + friend auto symmetric_diff(const TreeSet &, const TreeSet &) + -> TreeSet; + template + friend auto set_union(const TreeSet &, const TreeSet &) + -> TreeSet; + template + friend auto intersection(const TreeSet &, const TreeSet &) + -> TreeSet; + template + friend auto asymmetric_diff(const TreeSet &, const TreeSet &) + -> TreeSet; + template + friend auto set_or(const TreeSet &, const TreeSet &) + -> TreeSet; + template + friend auto set_and(const TreeSet &, const TreeSet &) + -> TreeSet; + template + friend auto product(const TreeSet &, const TreeSet &) + -> TreeSet, std::less>>; + + template + friend auto operator-(const TreeSet &, const TreeSet &) + -> TreeSet; + template + friend auto operator||(const TreeSet &, const TreeSet &) + -> TreeSet; + template + friend auto operator&&(const TreeSet &, const TreeSet &) + -> TreeSet; + template + friend auto operator*(const TreeSet &, const TreeSet &) + -> TreeSet, std::less>>; }; #include "detail/Iterator.tpp" diff --git a/include/detail/TreeSet.tpp b/include/detail/TreeSet.tpp index 7199976..3832fdf 100644 --- a/include/detail/TreeSet.tpp +++ b/include/detail/TreeSet.tpp @@ -22,6 +22,35 @@ template TreeSet::~TreeSet() { m_nil = nullptr; } +template +TreeSet::TreeSet(TreeSet &&t_other) + : m_nil(t_other.m_nil), m_root(t_other.m_root), + node_count(t_other.node_count), m_comp(std::move(t_other.m_comp)) { + t_other.m_nil = new Node(T{}, Color::Black, nullptr, nullptr, nullptr); + t_other.m_nil->left = t_other.m_nil->right = t_other.m_nil->parent = + t_other.m_nil; + t_other.m_root = t_other.m_nil; + t_other.node_count = 0; +} + +template +auto TreeSet::operator=(TreeSet &&t_other) -> TreeSet & { + if (this != &t_other) { + destroy(m_root); + delete m_nil; + m_nil = t_other.m_nil; + m_root = t_other.m_root; + node_count = t_other.node_count; + m_comp = std::move(t_other.m_comp); + t_other.m_nil = new Node(T{}, Color::Black, nullptr, nullptr, nullptr); + t_other.m_nil->left = t_other.m_nil->right = t_other.m_nil->parent = + t_other.m_nil; + t_other.m_root = t_other.m_nil; + t_other.node_count = 0; + } + return *this; +} + template auto TreeSet::destroy(Node *t_node) -> void { if (t_node != m_nil) { From a7ac4e986e9ea7660339f8653f6c08547e9ac65c Mon Sep 17 00:00:00 2001 From: Dave Amiana Date: Mon, 13 Jul 2026 08:27:05 +0800 Subject: [PATCH 11/13] implemented set operations for treeset with o(m+n) merging --- include/TreeSetOps.hpp | 55 +++++++++++ include/detail/TreeSetOps.tpp | 169 ++++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 include/TreeSetOps.hpp create mode 100644 include/detail/TreeSetOps.tpp diff --git a/include/TreeSetOps.hpp b/include/TreeSetOps.hpp new file mode 100644 index 0000000..d3b3bc9 --- /dev/null +++ b/include/TreeSetOps.hpp @@ -0,0 +1,55 @@ +#ifndef TREESET_OPS_HPP +#define TREESET_OPS_HPP + +#include "TreeSet.hpp" +#include + +template +auto symmetric_diff(const TreeSet &t_lhs, + const TreeSet &t_rhs) -> TreeSet; + +template +auto set_union(const TreeSet &t_lhs, + const TreeSet &t_rhs) -> TreeSet; + +template +auto intersection(const TreeSet &t_lhs, + const TreeSet &t_rhs) -> TreeSet; + +template +auto asymmetric_diff(const TreeSet &t_lhs, + const TreeSet &t_rhs) -> TreeSet; + +template +auto set_or(const TreeSet &t_lhs, + const TreeSet &t_rhs) -> TreeSet; + +template +auto set_and(const TreeSet &t_lhs, + const TreeSet &t_rhs) -> TreeSet; + +template +auto product(const TreeSet &t_lhs, + const TreeSet &t_rhs) + -> TreeSet, std::less>>; + +template +auto operator-(const TreeSet &t_lhs, + const TreeSet &t_rhs) -> TreeSet; + +template +auto operator||(const TreeSet &t_lhs, + const TreeSet &t_rhs) -> TreeSet; + +template +auto operator&&(const TreeSet &t_lhs, + const TreeSet &t_rhs) -> TreeSet; + +template +auto operator*(const TreeSet &t_lhs, + const TreeSet &t_rhs) + -> TreeSet, std::less>>; + +#include "detail/TreeSetOps.tpp" + +#endif // TREESET_OPS_HPP diff --git a/include/detail/TreeSetOps.tpp b/include/detail/TreeSetOps.tpp new file mode 100644 index 0000000..b65bc44 --- /dev/null +++ b/include/detail/TreeSetOps.tpp @@ -0,0 +1,169 @@ +#ifndef TREESET_OPS_TPP +#define TREESET_OPS_TPP + +template +auto symmetric_diff(const TreeSet &t_lhs, + const TreeSet &t_rhs) -> TreeSet { + TreeSet result; + auto it_a = t_lhs.cbegin(); + auto it_b = t_rhs.cbegin(); + auto end_a = t_lhs.cend(); + auto end_b = t_rhs.cend(); + + while (it_a != end_a && it_b != end_b) { + if (t_lhs.m_comp(*it_a, *it_b)) { + result.insert(*it_a); + ++it_a; + } else if (t_lhs.m_comp(*it_b, *it_a)) { + result.insert(*it_b); + ++it_b; + } else { + ++it_a; + ++it_b; + } + } + while (it_a != end_a) { + result.insert(*it_a); + ++it_a; + } + while (it_b != end_b) { + result.insert(*it_b); + ++it_b; + } + return result; +} + +template +auto set_union(const TreeSet &t_lhs, + const TreeSet &t_rhs) -> TreeSet { + TreeSet result; + auto it_a = t_lhs.cbegin(); + auto it_b = t_rhs.cbegin(); + auto end_a = t_lhs.cend(); + auto end_b = t_rhs.cend(); + + while (it_a != end_a && it_b != end_b) { + if (t_lhs.m_comp(*it_a, *it_b)) { + result.insert(*it_a); + ++it_a; + } else if (t_lhs.m_comp(*it_b, *it_a)) { + result.insert(*it_b); + ++it_b; + } else { + result.insert(*it_a); + ++it_a; + ++it_b; + } + } + while (it_a != end_a) { + result.insert(*it_a); + ++it_a; + } + while (it_b != end_b) { + result.insert(*it_b); + ++it_b; + } + return result; +} + +template +auto intersection(const TreeSet &t_lhs, + const TreeSet &t_rhs) -> TreeSet { + TreeSet result; + auto it_a = t_lhs.cbegin(); + auto it_b = t_rhs.cbegin(); + auto end_a = t_lhs.cend(); + auto end_b = t_rhs.cend(); + + while (it_a != end_a && it_b != end_b) { + if (t_lhs.m_comp(*it_a, *it_b)) { + ++it_a; + } else if (t_lhs.m_comp(*it_b, *it_a)) { + ++it_b; + } else { + result.insert(*it_a); + ++it_a; + ++it_b; + } + } + return result; +} + +template +auto asymmetric_diff(const TreeSet &t_lhs, + const TreeSet &t_rhs) -> TreeSet { + TreeSet result; + auto it_a = t_lhs.cbegin(); + auto it_b = t_rhs.cbegin(); + auto end_a = t_lhs.cend(); + auto end_b = t_rhs.cend(); + + while (it_a != end_a && it_b != end_b) { + if (t_lhs.m_comp(*it_a, *it_b)) { + result.insert(*it_a); + ++it_a; + } else if (t_lhs.m_comp(*it_b, *it_a)) { + ++it_b; + } else { + ++it_a; + ++it_b; + } + } + while (it_a != end_a) { + result.insert(*it_a); + ++it_a; + } + return result; +} + +template +auto set_or(const TreeSet &t_lhs, + const TreeSet &t_rhs) -> TreeSet { + return set_union(t_lhs, t_rhs); +} + +template +auto set_and(const TreeSet &t_lhs, + const TreeSet &t_rhs) -> TreeSet { + return intersection(t_lhs, t_rhs); +} + +template +auto product(const TreeSet &t_lhs, + const TreeSet &t_rhs) + -> TreeSet, std::less>> { + TreeSet, std::less>> result; + for (auto it_a = t_lhs.cbegin(); it_a != t_lhs.cend(); ++it_a) { + for (auto it_b = t_rhs.cbegin(); it_b != t_rhs.cend(); ++it_b) { + result.insert(std::make_pair(*it_a, *it_b)); + } + } + return result; +} + +template +auto operator-(const TreeSet &t_lhs, + const TreeSet &t_rhs) -> TreeSet { + return asymmetric_diff(t_lhs, t_rhs); +} + +template +auto operator||(const TreeSet &t_lhs, + const TreeSet &t_rhs) -> TreeSet { + return set_or(t_lhs, t_rhs); +} + +template +auto operator&&(const TreeSet &t_lhs, + const TreeSet &t_rhs) -> TreeSet { + return set_and(t_lhs, t_rhs); +} + +template +auto operator*(const TreeSet &t_lhs, + const TreeSet &t_rhs) + -> TreeSet, std::less>> { + return product(t_lhs, t_rhs); +} + +#endif // TREESET_OPS_TPP From 148e140666f88a0a746842bcedae1d888105a1e2 Mon Sep 17 00:00:00 2001 From: Dave Amiana Date: Mon, 13 Jul 2026 08:27:20 +0800 Subject: [PATCH 12/13] implemented test suites for set operations --- test/unittest.cpp | 448 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 448 insertions(+) diff --git a/test/unittest.cpp b/test/unittest.cpp index af80f63..cee04ba 100644 --- a/test/unittest.cpp +++ b/test/unittest.cpp @@ -1,10 +1,12 @@ #include "../include/TreeSet.hpp" +#include "../include/TreeSetOps.hpp" #include "gmock/gmock.h" #include "gtest/gtest.h" #include #include #include #include +#include #include using ::testing::ElementsAre; @@ -969,3 +971,449 @@ TEST_F(TreeSetGreaterCompTest, RemoveTriggersRotationWithGreaterComp) { auto in = inorder_vec(); EXPECT_TRUE(std::is_sorted(in.begin(), in.end(), std::greater{})); } + +// ============================================================================ +// Group 16: Move Semantics +// ============================================================================ + +class TreeSetMoveTest : public ::testing::Test { +protected: + auto inorder_vec(const TreeSet &t) -> std::vector { + std::vector result; + for (auto it = t.cbegin(); it != t.cend(); ++it) + result.push_back(*it); + return result; + } +}; + +TEST_F(TreeSetMoveTest, MoveConstructorTransfersOwnership) { + TreeSet a; + a.insert(1); + a.insert(2); + a.insert(3); + + TreeSet b(std::move(a)); + EXPECT_EQ(b.size(), 3u); + EXPECT_THAT(inorder_vec(b), ElementsAre(1, 2, 3)); + EXPECT_TRUE(a.is_empty()); +} + +TEST_F(TreeSetMoveTest, MoveAssignmentTransfersOwnership) { + TreeSet a; + a.insert(10); + a.insert(20); + + TreeSet b; + b.insert(99); + + b = std::move(a); + EXPECT_EQ(b.size(), 2u); + EXPECT_THAT(inorder_vec(b), ElementsAre(10, 20)); + EXPECT_TRUE(a.is_empty()); +} + +TEST_F(TreeSetMoveTest, MoveFromEmptyTree) { + TreeSet a; + TreeSet b(std::move(a)); + EXPECT_TRUE(b.is_empty()); + EXPECT_EQ(b.size(), 0u); +} + +TEST_F(TreeSetMoveTest, MoveAssignmentSelfIsNoop) { + TreeSet a; + a.insert(5); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wself-move" + a = std::move(a); +#pragma clang diagnostic pop + EXPECT_EQ(a.size(), 1u); + EXPECT_TRUE(a.is_element(5)); +} + +TEST_F(TreeSetMoveTest, MovedFromTreeUsableAfterMove) { + TreeSet a; + a.insert(1); + a.insert(2); + TreeSet b(std::move(a)); + ASSERT_TRUE(a.insert(10)); + EXPECT_EQ(a.size(), 1u); + EXPECT_TRUE(a.is_element(10)); +} + +// ============================================================================ +// Group 17: Set Operations +// ============================================================================ + +class TreeSetOpsTest : public ::testing::Test { +protected: + auto inorder_vec(const TreeSet &t) -> std::vector { + std::vector result; + for (auto it = t.cbegin(); it != t.cend(); ++it) + result.push_back(*it); + return result; + } +}; + +TEST_F(TreeSetOpsTest, SymmetricDiffDisjoint) { + TreeSet a{1, 2, 3}; + TreeSet b{4, 5, 6}; + auto result = symmetric_diff(a, b); + EXPECT_THAT(inorder_vec(result), ElementsAre(1, 2, 3, 4, 5, 6)); +} + +TEST_F(TreeSetOpsTest, SymmetricDiffOverlapping) { + TreeSet a{1, 2, 3, 4}; + TreeSet b{3, 4, 5, 6}; + auto result = symmetric_diff(a, b); + EXPECT_THAT(inorder_vec(result), ElementsAre(1, 2, 5, 6)); +} + +TEST_F(TreeSetOpsTest, SymmetricDiffIdentical) { + TreeSet a{1, 2, 3}; + TreeSet b{1, 2, 3}; + auto result = symmetric_diff(a, b); + EXPECT_TRUE(result.is_empty()); +} + +TEST_F(TreeSetOpsTest, SymmetricDiffEmptyLeft) { + TreeSet a; + TreeSet b{1, 2, 3}; + auto result = symmetric_diff(a, b); + EXPECT_THAT(inorder_vec(result), ElementsAre(1, 2, 3)); +} + +TEST_F(TreeSetOpsTest, SymmetricDiffEmptyRight) { + TreeSet a{1, 2, 3}; + TreeSet b; + auto result = symmetric_diff(a, b); + EXPECT_THAT(inorder_vec(result), ElementsAre(1, 2, 3)); +} + +TEST_F(TreeSetOpsTest, SymmetricDiffBothEmpty) { + TreeSet a; + TreeSet b; + auto result = symmetric_diff(a, b); + EXPECT_TRUE(result.is_empty()); +} + +TEST_F(TreeSetOpsTest, SetUnionDisjoint) { + TreeSet a{1, 2, 3}; + TreeSet b{4, 5, 6}; + auto result = set_union(a, b); + EXPECT_THAT(inorder_vec(result), ElementsAre(1, 2, 3, 4, 5, 6)); +} + +TEST_F(TreeSetOpsTest, SetUnionOverlapping) { + TreeSet a{1, 2, 3, 4}; + TreeSet b{3, 4, 5, 6}; + auto result = set_union(a, b); + EXPECT_THAT(inorder_vec(result), ElementsAre(1, 2, 3, 4, 5, 6)); +} + +TEST_F(TreeSetOpsTest, SetUnionIdentical) { + TreeSet a{1, 2, 3}; + TreeSet b{1, 2, 3}; + auto result = set_union(a, b); + EXPECT_THAT(inorder_vec(result), ElementsAre(1, 2, 3)); +} + +TEST_F(TreeSetOpsTest, SetUnionEmptyLeft) { + TreeSet a; + TreeSet b{1, 2, 3}; + auto result = set_union(a, b); + EXPECT_THAT(inorder_vec(result), ElementsAre(1, 2, 3)); +} + +TEST_F(TreeSetOpsTest, SetUnionEmptyRight) { + TreeSet a{1, 2, 3}; + TreeSet b; + auto result = set_union(a, b); + EXPECT_THAT(inorder_vec(result), ElementsAre(1, 2, 3)); +} + +TEST_F(TreeSetOpsTest, SetUnionBothEmpty) { + TreeSet a; + TreeSet b; + auto result = set_union(a, b); + EXPECT_TRUE(result.is_empty()); +} + +TEST_F(TreeSetOpsTest, IntersectionDisjoint) { + TreeSet a{1, 2, 3}; + TreeSet b{4, 5, 6}; + auto result = intersection(a, b); + EXPECT_TRUE(result.is_empty()); +} + +TEST_F(TreeSetOpsTest, IntersectionOverlapping) { + TreeSet a{1, 2, 3, 4}; + TreeSet b{3, 4, 5, 6}; + auto result = intersection(a, b); + EXPECT_THAT(inorder_vec(result), ElementsAre(3, 4)); +} + +TEST_F(TreeSetOpsTest, IntersectionIdentical) { + TreeSet a{1, 2, 3}; + TreeSet b{1, 2, 3}; + auto result = intersection(a, b); + EXPECT_THAT(inorder_vec(result), ElementsAre(1, 2, 3)); +} + +TEST_F(TreeSetOpsTest, IntersectionEmptyLeft) { + TreeSet a; + TreeSet b{1, 2, 3}; + auto result = intersection(a, b); + EXPECT_TRUE(result.is_empty()); +} + +TEST_F(TreeSetOpsTest, IntersectionEmptyRight) { + TreeSet a{1, 2, 3}; + TreeSet b; + auto result = intersection(a, b); + EXPECT_TRUE(result.is_empty()); +} + +TEST_F(TreeSetOpsTest, IntersectionBothEmpty) { + TreeSet a; + TreeSet b; + auto result = intersection(a, b); + EXPECT_TRUE(result.is_empty()); +} + +TEST_F(TreeSetOpsTest, AsymmetricDiffDisjoint) { + TreeSet a{1, 2, 3}; + TreeSet b{4, 5, 6}; + auto result = asymmetric_diff(a, b); + EXPECT_THAT(inorder_vec(result), ElementsAre(1, 2, 3)); +} + +TEST_F(TreeSetOpsTest, AsymmetricDiffOverlapping) { + TreeSet a{1, 2, 3, 4}; + TreeSet b{3, 4, 5, 6}; + auto result = asymmetric_diff(a, b); + EXPECT_THAT(inorder_vec(result), ElementsAre(1, 2)); +} + +TEST_F(TreeSetOpsTest, AsymmetricDiffIdentical) { + TreeSet a{1, 2, 3}; + TreeSet b{1, 2, 3}; + auto result = asymmetric_diff(a, b); + EXPECT_TRUE(result.is_empty()); +} + +TEST_F(TreeSetOpsTest, AsymmetricDiffEmptyLeft) { + TreeSet a; + TreeSet b{1, 2, 3}; + auto result = asymmetric_diff(a, b); + EXPECT_TRUE(result.is_empty()); +} + +TEST_F(TreeSetOpsTest, AsymmetricDiffEmptyRight) { + TreeSet a{1, 2, 3}; + TreeSet b; + auto result = asymmetric_diff(a, b); + EXPECT_THAT(inorder_vec(result), ElementsAre(1, 2, 3)); +} + +TEST_F(TreeSetOpsTest, AsymmetricDiffBothEmpty) { + TreeSet a; + TreeSet b; + auto result = asymmetric_diff(a, b); + EXPECT_TRUE(result.is_empty()); +} + +TEST_F(TreeSetOpsTest, SetOrDelegaesToUnion) { + TreeSet a{1, 2, 3}; + TreeSet b{3, 4, 5}; + auto result = set_or(a, b); + EXPECT_THAT(inorder_vec(result), ElementsAre(1, 2, 3, 4, 5)); +} + +TEST_F(TreeSetOpsTest, SetAndDelegaesToIntersection) { + TreeSet a{1, 2, 3}; + TreeSet b{3, 4, 5}; + auto result = set_and(a, b); + EXPECT_THAT(inorder_vec(result), ElementsAre(3)); +} + +// ============================================================================ +// Group 18: Operator Overloads +// ============================================================================ + +TEST_F(TreeSetOpsTest, OperatorMinusIsAsymmetricDiff) { + TreeSet a{1, 2, 3, 4}; + TreeSet b{3, 4, 5, 6}; + auto result = a - b; + EXPECT_THAT(inorder_vec(result), ElementsAre(1, 2)); +} + +TEST_F(TreeSetOpsTest, OperatorOrIsSetOr) { + TreeSet a{1, 2, 3}; + TreeSet b{3, 4, 5}; + auto result = a || b; + EXPECT_THAT(inorder_vec(result), ElementsAre(1, 2, 3, 4, 5)); +} + +TEST_F(TreeSetOpsTest, OperatorAndIsSetAnd) { + TreeSet a{1, 2, 3}; + TreeSet b{3, 4, 5}; + auto result = a && b; + EXPECT_THAT(inorder_vec(result), ElementsAre(3)); +} + +TEST_F(TreeSetOpsTest, OperatorMinusEmptySets) { + TreeSet a; + TreeSet b{1, 2, 3}; + auto result = a - b; + EXPECT_TRUE(result.is_empty()); +} + +TEST_F(TreeSetOpsTest, OperatorOrBothEmpty) { + TreeSet a; + TreeSet b; + auto result = a || b; + EXPECT_TRUE(result.is_empty()); +} + +TEST_F(TreeSetOpsTest, OperatorAndDisjoint) { + TreeSet a{1, 2}; + TreeSet b{3, 4}; + auto result = a && b; + EXPECT_TRUE(result.is_empty()); +} + +// ============================================================================ +// Group 19: Cartesian Product +// ============================================================================ + +TEST_F(TreeSetOpsTest, ProductBasic) { + TreeSet a{1, 2}; + TreeSet b{3, 4}; + auto result = product(a, b); + EXPECT_EQ(result.size(), 4u); + EXPECT_TRUE(result.is_element({1, 3})); + EXPECT_TRUE(result.is_element({1, 4})); + EXPECT_TRUE(result.is_element({2, 3})); + EXPECT_TRUE(result.is_element({2, 4})); +} + +TEST_F(TreeSetOpsTest, ProductEmptyLeft) { + TreeSet a; + TreeSet b{1, 2, 3}; + auto result = product(a, b); + EXPECT_TRUE(result.is_empty()); +} + +TEST_F(TreeSetOpsTest, ProductEmptyRight) { + TreeSet a{1, 2, 3}; + TreeSet b; + auto result = product(a, b); + EXPECT_TRUE(result.is_empty()); +} + +TEST_F(TreeSetOpsTest, ProductBothEmpty) { + TreeSet a; + TreeSet b; + auto result = product(a, b); + EXPECT_TRUE(result.is_empty()); +} + +TEST_F(TreeSetOpsTest, ProductSingleElements) { + TreeSet a{5}; + TreeSet b{7}; + auto result = product(a, b); + EXPECT_EQ(result.size(), 1u); + EXPECT_TRUE(result.is_element({5, 7})); +} + +TEST_F(TreeSetOpsTest, ProductOperator) { + TreeSet a{1, 2}; + TreeSet b{3, 4}; + auto result = a * b; + EXPECT_EQ(result.size(), 4u); + EXPECT_TRUE(result.is_element({1, 3})); + EXPECT_TRUE(result.is_element({2, 4})); +} + +TEST_F(TreeSetOpsTest, ProductSortedLexicographically) { + TreeSet a{3, 1}; + TreeSet b{4, 2}; + auto result = product(a, b); + std::vector> pairs; + for (auto it = result.cbegin(); it != result.cend(); ++it) + pairs.push_back(*it); + EXPECT_THAT(pairs, ElementsAre(std::make_pair(1, 2), std::make_pair(1, 4), + std::make_pair(3, 2), std::make_pair(3, 4))); +} + +// ============================================================================ +// Group 20: Set Operations with Larger Sets +// ============================================================================ + +TEST_F(TreeSetOpsTest, LargeSetUnion) { + TreeSet a; + TreeSet b; + for (int i = 1; i <= 50; ++i) + a.insert(i); + for (int i = 26; i <= 75; ++i) + b.insert(i); + auto result = set_union(a, b); + EXPECT_EQ(result.size(), 75u); + auto in = inorder_vec(result); + EXPECT_EQ(in.front(), 1); + EXPECT_EQ(in.back(), 75); +} + +TEST_F(TreeSetOpsTest, LargeSetIntersection) { + TreeSet a; + TreeSet b; + for (int i = 1; i <= 50; ++i) + a.insert(i); + for (int i = 26; i <= 75; ++i) + b.insert(i); + auto result = intersection(a, b); + EXPECT_EQ(result.size(), 25u); + auto in = inorder_vec(result); + EXPECT_EQ(in.front(), 26); + EXPECT_EQ(in.back(), 50); +} + +TEST_F(TreeSetOpsTest, LargeSymmetricDiff) { + TreeSet a; + TreeSet b; + for (int i = 1; i <= 50; ++i) + a.insert(i); + for (int i = 26; i <= 75; ++i) + b.insert(i); + auto result = symmetric_diff(a, b); + EXPECT_EQ(result.size(), 50u); + auto in = inorder_vec(result); + EXPECT_EQ(in.front(), 1); + EXPECT_EQ(in.back(), 75); + for (int i = 26; i <= 50; ++i) + EXPECT_FALSE(result.is_element(i)); +} + +TEST_F(TreeSetOpsTest, LargeAsymmetricDiff) { + TreeSet a; + TreeSet b; + for (int i = 1; i <= 50; ++i) + a.insert(i); + for (int i = 26; i <= 75; ++i) + b.insert(i); + auto result = asymmetric_diff(a, b); + EXPECT_EQ(result.size(), 25u); + auto in = inorder_vec(result); + EXPECT_EQ(in.front(), 1); + EXPECT_EQ(in.back(), 25); +} + +TEST_F(TreeSetOpsTest, LargeProduct) { + TreeSet a{1, 2, 3}; + TreeSet b{10, 20}; + auto result = product(a, b); + EXPECT_EQ(result.size(), 6u); + for (int x : {1, 2, 3}) + for (int y : {10, 20}) + EXPECT_TRUE(result.is_element({x, y})); +} From c1bc7981c85778c2fc5240cad63d1715bbd45839 Mon Sep 17 00:00:00 2001 From: Dave Amiana Date: Mon, 13 Jul 2026 08:29:51 +0800 Subject: [PATCH 13/13] moved out friend functions to independent free functions by dispatch while maintaining o(m+n) merge --- include/TreeSet.hpp | 37 ++--------------------------------- include/detail/TreeSet.tpp | 5 +++++ include/detail/TreeSetOps.tpp | 16 +++++++-------- 3 files changed, 15 insertions(+), 43 deletions(-) diff --git a/include/TreeSet.hpp b/include/TreeSet.hpp index 16234aa..e363ee9 100644 --- a/include/TreeSet.hpp +++ b/include/TreeSet.hpp @@ -146,6 +146,8 @@ template class TreeSet { auto is_empty() const noexcept -> bool; auto is_element(const T &t_value) const noexcept -> bool; + auto comp() const noexcept -> const Compare &; + auto root() noexcept -> Node *; auto min(Node *t_node) noexcept -> Node *; auto max(Node *t_node) noexcept -> Node *; @@ -207,41 +209,6 @@ template class TreeSet { template friend class TreeSetIterator; - - template - friend auto symmetric_diff(const TreeSet &, const TreeSet &) - -> TreeSet; - template - friend auto set_union(const TreeSet &, const TreeSet &) - -> TreeSet; - template - friend auto intersection(const TreeSet &, const TreeSet &) - -> TreeSet; - template - friend auto asymmetric_diff(const TreeSet &, const TreeSet &) - -> TreeSet; - template - friend auto set_or(const TreeSet &, const TreeSet &) - -> TreeSet; - template - friend auto set_and(const TreeSet &, const TreeSet &) - -> TreeSet; - template - friend auto product(const TreeSet &, const TreeSet &) - -> TreeSet, std::less>>; - - template - friend auto operator-(const TreeSet &, const TreeSet &) - -> TreeSet; - template - friend auto operator||(const TreeSet &, const TreeSet &) - -> TreeSet; - template - friend auto operator&&(const TreeSet &, const TreeSet &) - -> TreeSet; - template - friend auto operator*(const TreeSet &, const TreeSet &) - -> TreeSet, std::less>>; }; #include "detail/Iterator.tpp" diff --git a/include/detail/TreeSet.tpp b/include/detail/TreeSet.tpp index 3832fdf..3cb6252 100644 --- a/include/detail/TreeSet.tpp +++ b/include/detail/TreeSet.tpp @@ -446,6 +446,11 @@ auto TreeSet::is_element(const T &t_value) const noexcept -> bool { return search(t_value, m_root) != m_nil; } +template +auto TreeSet::comp() const noexcept -> const Compare & { + return m_comp; +} + template auto TreeSet::root() noexcept -> Node * { return m_root; diff --git a/include/detail/TreeSetOps.tpp b/include/detail/TreeSetOps.tpp index b65bc44..d22de54 100644 --- a/include/detail/TreeSetOps.tpp +++ b/include/detail/TreeSetOps.tpp @@ -11,10 +11,10 @@ auto symmetric_diff(const TreeSet &t_lhs, auto end_b = t_rhs.cend(); while (it_a != end_a && it_b != end_b) { - if (t_lhs.m_comp(*it_a, *it_b)) { + if (t_lhs.comp()(*it_a, *it_b)) { result.insert(*it_a); ++it_a; - } else if (t_lhs.m_comp(*it_b, *it_a)) { + } else if (t_lhs.comp()(*it_b, *it_a)) { result.insert(*it_b); ++it_b; } else { @@ -43,10 +43,10 @@ auto set_union(const TreeSet &t_lhs, auto end_b = t_rhs.cend(); while (it_a != end_a && it_b != end_b) { - if (t_lhs.m_comp(*it_a, *it_b)) { + if (t_lhs.comp()(*it_a, *it_b)) { result.insert(*it_a); ++it_a; - } else if (t_lhs.m_comp(*it_b, *it_a)) { + } else if (t_lhs.comp()(*it_b, *it_a)) { result.insert(*it_b); ++it_b; } else { @@ -76,9 +76,9 @@ auto intersection(const TreeSet &t_lhs, auto end_b = t_rhs.cend(); while (it_a != end_a && it_b != end_b) { - if (t_lhs.m_comp(*it_a, *it_b)) { + if (t_lhs.comp()(*it_a, *it_b)) { ++it_a; - } else if (t_lhs.m_comp(*it_b, *it_a)) { + } else if (t_lhs.comp()(*it_b, *it_a)) { ++it_b; } else { result.insert(*it_a); @@ -99,10 +99,10 @@ auto asymmetric_diff(const TreeSet &t_lhs, auto end_b = t_rhs.cend(); while (it_a != end_a && it_b != end_b) { - if (t_lhs.m_comp(*it_a, *it_b)) { + if (t_lhs.comp()(*it_a, *it_b)) { result.insert(*it_a); ++it_a; - } else if (t_lhs.m_comp(*it_b, *it_a)) { + } else if (t_lhs.comp()(*it_b, *it_a)) { ++it_b; } else { ++it_a;