From 1ea8473fe149c946a9bb4de8d668ce1b03c81177 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Fri, 6 Feb 2026 18:08:11 +0200 Subject: [PATCH] Added etl::intrusive_avl_tree class. --- include/etl/file_error_numbers.h | 1 + include/etl/intrusive_avl_tree.h | 2008 ++++++++++++++++++ test/CMakeLists.txt | 1 + test/data.h | 9 +- test/meson.build | 1 + test/syntax_check/CMakeLists.txt | 1 + test/syntax_check/intrusive_avl_tree.h.t.cpp | 29 + test/test_intrusive_avl_tree.cpp | 1606 ++++++++++++++ test/vs2022/etl.vcxproj | 115 + test/vs2022/etl.vcxproj.filters | 9 + 10 files changed, 3777 insertions(+), 3 deletions(-) create mode 100644 include/etl/intrusive_avl_tree.h create mode 100644 test/syntax_check/intrusive_avl_tree.h.t.cpp create mode 100644 test/test_intrusive_avl_tree.cpp diff --git a/include/etl/file_error_numbers.h b/include/etl/file_error_numbers.h index 0953b26f..945f1d87 100644 --- a/include/etl/file_error_numbers.h +++ b/include/etl/file_error_numbers.h @@ -111,4 +111,5 @@ SOFTWARE. #define ETL_SIGNAL_FILE_ID "78" #define ETL_FORMAT_FILE_ID "79" #define ETL_INPLACE_FUNCTION_FILE_ID "80" +#define ETL_INTRUSIVE_AVL_TREE_FILE_ID "81" #endif diff --git a/include/etl/intrusive_avl_tree.h b/include/etl/intrusive_avl_tree.h new file mode 100644 index 00000000..fb09dee7 --- /dev/null +++ b/include/etl/intrusive_avl_tree.h @@ -0,0 +1,2008 @@ +///\file + +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Sergei Shirokov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_INTRUSIVE_AVL_TREE_INCLUDED +#define ETL_INTRUSIVE_AVL_TREE_INCLUDED + +#include "platform.h" +#include "error_handler.h" +#include "intrusive_links.h" +#include "iterator.h" +#include "memory.h" +#include "type_traits.h" +#include "utility.h" + +#include + +namespace etl +{ + //*************************************************************************** + /// Exception for the intrusive_avl_tree. + /// \ingroup intrusive_avl_tree + //*************************************************************************** + class intrusive_avl_tree_exception : public exception + { + public: + + intrusive_avl_tree_exception(const string_type reason_, const string_type file_name_, const numeric_type line_number_) + : exception(reason_, file_name_, line_number_) + { + } + }; + + //*************************************************************************** + /// Iterator exception for the intrusive_avl_tree. + /// \ingroup intrusive_avl_tree + //*************************************************************************** + class intrusive_avl_tree_iterator_exception : public intrusive_avl_tree_exception + { + public: + + intrusive_avl_tree_iterator_exception(const string_type file_name_, const numeric_type line_number_) + : intrusive_avl_tree_exception(ETL_ERROR_TEXT("intrusive_avl_tree:iterator", ETL_INTRUSIVE_AVL_TREE_FILE_ID "A"), file_name_, line_number_) + { + } + }; + + //*************************************************************************** + /// Already exception for the intrusive_avl_tree. + ///\ingroup intrusive_avl_tree + //*************************************************************************** + class intrusive_avl_tree_value_is_already_linked : public intrusive_avl_tree_exception + { + public: + + intrusive_avl_tree_value_is_already_linked(const string_type file_name_, const numeric_type line_number_) + : intrusive_avl_tree_exception(ETL_ERROR_TEXT("intrusive_avl_tree:value is already linked", ETL_INTRUSIVE_AVL_TREE_FILE_ID"B"), file_name_, + line_number_) + { + } + }; + + //*************************************************************************** + /// \ingroup intrusive_avl_tree + /// Base for intrusive AVL tree. Stores elements derived from 'intrusive_avl_tree_base::link_type'. + /// \tparam ID_ The link ID that the value is derived from. + //*************************************************************************** + template + class intrusive_avl_tree_base + { + public: + + enum + { + ID = ID_, + }; + +#if ETL_USING_CPP11 + intrusive_avl_tree_base(const intrusive_avl_tree_base&) = delete; + intrusive_avl_tree_base& operator=(const intrusive_avl_tree_base&) = delete; +#endif + + /// Base for elements of this AVL tree. + /// It's expected that a tree node type is inherited from this base. + /// + /// Almost nothing is exposed from this type as public (or even protected) API - + /// this is done deliberately and by design, so that: + /// - impose as minimal as possible of "intrusive"-ness when you inherit your nodes from this base. + /// - don't worry about possible name conflicts + /// - hide implementation details + struct link_type : private etl::tree_link + { + private: + + typedef etl::tree_link base; + + public: + + link_type() ETL_NOEXCEPT + : base() + , etl_size(0) + { + } + +#if ETL_USING_CPP11 + //*************************************************************************** + /// Constructs a new item by moving `other` item into `this` one. + /// Complexity: O(1) + /// After construction `this` item will replace `other` item + /// in the same tree position as the `other` was, so no tree balancing is needed. + /// The `other` item becomes unlinked. + /// + /// Note also that move construction/assignment of the `intrusive_avl_tree` class itself + /// is heavily based on this move constructor for its `origin` sentinel item. + //*************************************************************************** + link_type(link_type&& other) ETL_NOEXCEPT + : base() + , etl_size(0) + { + move_impl(other); + } + + //*************************************************************************** + /// Assigns `other` item by moving it into `this` one. + /// Does nothing in case of self-assignment (when `this == &other`). + /// Complexity: + /// - O(log(N)) if `this` item is already linked to a tree, and so + /// the item has to be erased from the tree (with rebalancing). + /// - O(1) if `this` item is not linked to any tree. + /// Might rotate original tree of `this` item as necessary + /// to keep the tree balanced after erasing the item. + /// After assignment `this` item will replace `other` item + /// in the same tree position as the `other` was, so no tree balancing is needed. + /// The `other` item becomes unlinked. + /// Note that after assignment `this` item might change its tree. + //*************************************************************************** + link_type& operator=(link_type&& other) ETL_NOEXCEPT + { + if (this != &other) + { + // Before move assigning `other` we have to + // make sure this item is not linked to any tree. + erase_impl(this); + move_impl(other); + } + return *this; + } +#endif + +#if ETL_USING_CPP11 + // Disable copy construction and assignment. + link_type(const link_type&) = delete; + link_type& operator=(const link_type&) = delete; +#endif + + //*************************************************************************** + /// Destructor of a tree link. + /// Complexity: O(log(N)). + /// Might rotate the tree as necessary to keep it balanced. + /// NB! The tree itself is not real owner (of memory) of its nodes (and their inherited links). + /// The real owner (aka the user) of a node could destruct it as he/she wishes, + /// with or without prior explicit erasing it from its tree. So, if the node link happens + /// to be still linked to a tree during its destruction, then we have to unlink it - + /// otherwise node's parent and its former children will keep dangling pointers to the node, + /// which will essentially break the tree consistency, and leading to UB. + //*************************************************************************** + ~link_type() + { + // We can't just remove item (by simple rewiring of links at parent and children) + // b/c its former "owner" tree has to be rebalanced after the removal - + // hence the full-blown erasing which might rotate the tree as necessary. + erase_impl(this); + } + + private: + + friend class intrusive_avl_tree_base; + + union + { + int_fast8_t etl_bf; ///< Stores -1, 0 or +1 balancing factor in the real nodes. + size_t etl_size; ///< Stores total number on items in the tree (origin node only). + }; + +#if ETL_USING_CPP11 +#else + // Disable copy construction and assignment. + link_type(const link_type&); + link_type& operator=(const link_type&); +#endif + + ETL_NODISCARD + bool is_linked() const + { + return base::is_linked(); + } + + ETL_NODISCARD + bool is_origin() const + { + return ETL_NULLPTR == base::etl_parent; + } + + ETL_NODISCARD + link_type* get_parent() + { + return static_cast(base::etl_parent); + } + + ETL_NODISCARD + const link_type* get_parent() const + { + return static_cast(base::etl_parent); + } + + ETL_NODISCARD + bool is_child(const bool is_right) const + { + const link_type* parent = get_parent(); + return (ETL_NULLPTR != parent) && (this == parent->get_child(is_right)); + } + + ETL_NODISCARD + bool is_left_child() const + { + return is_child(false); + } + + ETL_NODISCARD + bool is_right_child() const + { + return is_child(true); + } + + ETL_NODISCARD + link_type* get_child(const bool is_right) + { + return static_cast(is_right ? base::etl_right : base::etl_left); + } + + ETL_NODISCARD + const link_type* get_child(const bool is_right) const + { + return static_cast(is_right ? base::etl_right : base::etl_left); + } + + void set_child(link_type* const child, const bool is_right) + { + base*& child_ref = is_right ? base::etl_right : base::etl_left; + child_ref = child; + } + + ETL_NODISCARD + link_type* get_left() + { + return static_cast(base::etl_left); + } + + ETL_NODISCARD + const link_type* get_left() const + { + return static_cast(base::etl_left); + } + + ETL_NODISCARD + link_type* get_right() + { + return static_cast(base::etl_right); + } + + ETL_NODISCARD + const link_type* get_right() const + { + return static_cast(base::etl_right); + } + + void move_impl(link_type& other) + { + const bool is_right = other.is_right_child(); + base::etl_parent = other.etl_parent; + base::etl_left = other.etl_left; + base::etl_right = other.etl_right; + etl_size = other.etl_size; + + other.clear(); + other.etl_size = 0; + + if (ETL_NULLPTR != base::etl_parent) + { + get_parent()->link_child(this, is_right); + } + if (ETL_NULLPTR != base::etl_left) + { + get_left()->set_parent(this); + } + if (ETL_NULLPTR != base::etl_right) + { + get_right()->set_parent(this); + } + } + + void rotate(const bool is_right) + { + const bool was_right = is_right_child(); + link_type* const child = get_child(!is_right); + etl::link_rotate(this, child); + if (link_type* const parent = child->get_parent()) + { + parent->set_child(child, was_right); + } + } + + ETL_NODISCARD + link_type* adjust_balance(const bool increase) + { + const int_fast8_t new_bf = etl_bf + (increase ? +1 : -1); + if ((-1 <= new_bf) && (new_bf <= +1)) + { + etl_bf = new_bf; + return this; + } + + const bool is_right_rotation = new_bf < 0; + const int_fast8_t sign = is_right_rotation ? +1 : -1; + link_type* const z_leaf = get_child(!is_right_rotation); + if (z_leaf->etl_bf * sign <= 0) + { + rotate(is_right_rotation); + if (z_leaf->etl_bf == 0) + { + etl_bf = -sign; + z_leaf->etl_bf = +sign; + } + else + { + etl_bf = 0; + z_leaf->etl_bf = 0; + } + return z_leaf; + } + + link_type* const y_leaf = z_leaf->get_child(is_right_rotation); + z_leaf->rotate(!is_right_rotation); + rotate(is_right_rotation); + if (y_leaf->etl_bf == 0) + { + etl_bf = 0; + z_leaf->etl_bf = 0; + } + else if (y_leaf->etl_bf == sign) + { + etl_bf = 0; + y_leaf->etl_bf = 0; + z_leaf->etl_bf = -sign; + } + else + { + etl_bf = +sign; + y_leaf->etl_bf = 0; + z_leaf->etl_bf = 0; + } + return y_leaf; + } + + void link_child(link_type* child, const bool is_right) + { + if (is_right) + { + etl::link_right(this, child); + } + else + { + etl::link_left(this, child); + } + } + + }; // link_type + + //************************************************************************* + /// Checks if the tree is in the empty state. + /// Complexity: O(1). + //************************************************************************* + ETL_NODISCARD + bool empty() const ETL_NOEXCEPT + { + return ETL_NULLPTR == get_root(); + } + + //************************************************************************* + /// Returns the number of elements. + /// Complexity: O(1). + //************************************************************************* + ETL_NODISCARD + size_t size() const ETL_NOEXCEPT + { + return get_origin().etl_size; + } + + //************************************************************************* + /// Unlinks all current items, leaving this tree in the empty state. + /// Complexity: O(N). + /// Operation invalidates all existing iterators. + /// + /// Note that the same "clear all" effect could be achieved by using `erase` + /// method for each item, but b/c of intermediate tree rebalancing + /// complexity will be higher - O(N*log(N)). + //************************************************************************* + void clear() ETL_NOEXCEPT + { +#if ETL_USING_CPP11 + auto unlinker = [](link_type& link) + { + link.clear(); + link.etl_size = 0; + }; +#else + struct + { + void operator()(link_type& link) const + { + link.clear(); + link.etl_size = 0; + } + } unlinker; +#endif + + // No need to balance b/c everything will be unlinked. + // Note that "post order" visitation is important - + // it ensures that once a link is passed to the "visitor" functor, + // traversal won't use pointer to this link anymore, + // so we could efficiently clear the link. + visit_post_order_impl(&origin, false, unlinker); + origin.set_left(ETL_NULLPTR); + origin.etl_size = 0; + } + + //******************************************* + /// Swap with another tree. + /// Complexity: O(1). + /// Does nothing in case of self-swap (when `this == &other`). + //******************************************* + void swap(intrusive_avl_tree_base& other) ETL_NOEXCEPT + { + if (this != &other) + { + ETL_OR_STD::swap(origin, other.origin); + } + } + + //************************************************************************* + /// Swaps the trees. + /// Complexity: O(1). + /// Does nothing in case of self-swap (when `&lhs == &rhs`). + //************************************************************************* + friend void swap(intrusive_avl_tree_base& lhs, intrusive_avl_tree_base& rhs) ETL_NOEXCEPT + { + if (&lhs != &rhs) + { + lhs.swap(rhs); + } + } + + protected: + + //************************************************************************* + /// Default constructor. + //************************************************************************* + intrusive_avl_tree_base() ETL_NOEXCEPT {} + +#if ETL_USING_CPP11 + //************************************************************************* + /// Move constructor. + /// Complexity: O(1). + /// NB! Proper `= default` move behavior is actually based on the move + /// construction of the `origin` link (see `link_type(link_type&& other)`). + //************************************************************************* + intrusive_avl_tree_base(intrusive_avl_tree_base&&) ETL_NOEXCEPT = default; + + //************************************************************************* + /// Move assignment. + /// Does nothing in case of self-assignment (when `this == &other`). + /// Complexity: O(N), where N is the size of `this` tree before assignment - + /// all former items have to be unlinked. + //************************************************************************* + intrusive_avl_tree_base& operator=(intrusive_avl_tree_base&& other) ETL_NOEXCEPT + { + if (this != &other) + { + intrusive_avl_tree_base tmp(std::move(other)); + swap(tmp); + } + return *this; + } +#endif + + //************************************************************************* + /// Destructor of a tree. + /// Complexity: O(N). + /// All existing nodes will stay alive, + /// but will be completely unlinked from the tree (and from each other). + /// If you want more control on what happens with still linked tree nodes then + /// enumerate and unlink (aka "erase") them first (potentially moving to somewhere else). + /// If needed whole content of the tree could be O(1) effectively moved + /// to another tree, leaving this tree as empty - use C++11 move constructor of the tree. + //*************************************************************************** + ~intrusive_avl_tree_base() + { + // It's important to clear, so that none of the former (but still alive) items + // stay linked to this tree (neither directly at the root item, + // nor transitively via "parent" links from leaf items up to the origin). + clear(); + } + + ETL_NODISCARD + link_type* get_root() ETL_NOEXCEPT + { + return static_cast(origin.etl_left); + } + + ETL_NODISCARD + const link_type* get_root() const ETL_NOEXCEPT + { + return static_cast(origin.etl_left); + } + + ETL_NODISCARD + link_type& get_origin() ETL_NOEXCEPT + { + return origin; + } + + const link_type& get_origin() const ETL_NOEXCEPT + { + return origin; + } + + template + ETL_NODISCARD + static TLink* get_origin(TLink* link) ETL_NOEXCEPT + { + while ((ETL_NULLPTR != link) && !link->is_origin()) + { + link = link->get_parent(); + } + return link; + } + + ETL_NODISCARD + static bool is_real_link(const link_type* link) ETL_NOEXCEPT + { + return (ETL_NULLPTR != link) && !link->is_origin(); + } + + template + ETL_NODISCARD + static TLink* begin_impl(TLink& origin) ETL_NOEXCEPT + { + TLink* curr = &origin; + TLink* next = curr->get_child(false); + while (ETL_NULLPTR != next) + { + curr = next; + next = next->get_child(false); + } + return curr; + } + + template + ETL_NODISCARD + static TLink* end_impl(TLink& origin) ETL_NOEXCEPT + { + return &origin; + } + + template + ETL_NODISCARD + static TLink* find_extremum_impl(TLink* curr, const bool is_max) ETL_NOEXCEPT + { + if (ETL_NULLPTR != curr) + { + TLink* next = curr->get_child(is_max); + while (ETL_NULLPTR != next) + { + curr = next; + next = curr->get_child(is_max); + } + } + return curr; + } + + template + ETL_NODISCARD + static TLink* next_in_order_impl(TLink* curr) ETL_NOEXCEPT + { + if ((ETL_NULLPTR == curr) || curr->is_origin()) + { + return curr; + } + + if (TLink* const next = curr->get_child(true)) + { + return find_extremum_impl(next, false); + } + + while (curr->is_right_child()) + { + curr = curr->get_parent(); + } + return curr->get_parent(); + } + + template + ETL_NODISCARD + static TLink* prev_in_order_impl(TLink* curr) ETL_NOEXCEPT + { + if (ETL_NULLPTR == curr) + { + return ETL_NULLPTR; + } + + if (TLink* const next = curr->get_child(false)) + { + return find_extremum_impl(next, true); + } + + while (curr->is_left_child()) + { + curr = curr->get_parent(); + } + return curr->is_origin() ? curr : curr->get_parent(); + } + + template + static void visit_in_order_impl(TLink* curr, const bool is_reverse, Visitor visitor) + { + TLink* prev = ETL_NULLPTR; + while (ETL_NULLPTR != curr) + { + TLink* next = curr->get_parent(); + if (prev == next) + { + if (TLink* const child1 = curr->get_child(is_reverse)) + { + next = child1; + } + else + { + if (!curr->is_origin()) + { + visitor(*curr); + } + + if (TLink* const child2 = curr->get_child(!is_reverse)) + { + next = child2; + } + } + } + else if (prev == curr->get_child(is_reverse)) + { + if (!curr->is_origin()) + { + visitor(*curr); + } + + if (TLink* const child2 = curr->get_child(!is_reverse)) + { + next = child2; + } + } + + prev = etl::exchange(curr, next); + } + } + + template + static void visit_post_order_impl(TLink* curr, const bool is_reverse, Visitor visitor) + { + TLink* prev = ETL_NULLPTR; + while (ETL_NULLPTR != curr) + { + TLink* next = curr->get_parent(); + if (prev == next) + { + if (TLink* const child1 = curr->get_child(is_reverse)) + { + next = child1; + } + else if (TLink* const child2 = curr->get_child(!is_reverse)) + { + next = child2; + } + else if (!curr->is_origin()) + { + visitor(*curr); + } + } + else if (prev == curr->get_child(is_reverse)) + { + if (TLink* const child2 = curr->get_child(!is_reverse)) + { + next = child2; + } + else if (!curr->is_origin()) + { + visitor(*curr); + } + } + else if (!curr->is_origin()) + { + visitor(*curr); + } + + prev = etl::exchange(curr, next); + } + } + + template + static void visit_pre_order_impl(TLink* curr, const bool is_reverse, Visitor visitor) + { + TLink* prev = ETL_NULLPTR; + while (ETL_NULLPTR != curr) + { + TLink* next = curr->get_parent(); + if (prev == next) + { + if (!curr->is_origin()) + { + visitor(*curr); + } + + if (TLink* const child1 = curr->get_child(is_reverse)) + { + next = child1; + } + else if (TLink* const child2 = curr->get_child(!is_reverse)) + { + next = child2; + } + } + else if (prev == curr->get_child(is_reverse)) + { + if (TLink* const child2 = curr->get_child(!is_reverse)) + { + next = child2; + } + } + + prev = etl::exchange(curr, next); + } + } + + ETL_NODISCARD + static int_fast8_t get_balance_factor_impl(const link_type* const curr) ETL_NOEXCEPT + { + return (ETL_NULLPTR != curr) ? curr->etl_bf : 0; + } + + template + ETL_NODISCARD + static TLink* get_parent_impl(TLink* const curr) ETL_NOEXCEPT + { + if (ETL_NULLPTR == curr) + { + return ETL_NULLPTR; + } + TLink* const parent = curr->get_parent(); + if ((ETL_NULLPTR == parent) || parent->is_origin()) + { + return ETL_NULLPTR; + } + return parent; + } + + template + ETL_NODISCARD + static TLink* get_child_impl(TLink* const curr, const bool is_right) ETL_NOEXCEPT + { + if (ETL_NULLPTR == curr) + { + return ETL_NULLPTR; + } + return curr->get_child(is_right); + } + + template + void assign_impl(TIterator first, TIterator last, TBinaryCompare binary_comp) + { +#if ETL_IS_DEBUG_BUILD + const intmax_t diff = etl::distance(first, last); + ETL_ASSERT(diff >= 0, ETL_ERROR(intrusive_avl_tree_iterator_exception)); +#endif + + // Add all the elements. + while (first != last) + { + link_type& link = *first++; + auto& value = static_cast(link); +#if ETL_USING_CPP11 + find_or_insert_impl( // + [&value, &binary_comp](const TValue& other) { return binary_comp(value, other); }, // + [&value] { return &value; }); +#else + const CompareFactory compareFactory(value, binary_comp); + find_or_insert_impl(compareFactory, compareFactory); +#endif + } + } + + template + static TValue* find_impl(TLink* const root, TCompare comp) + { + // Try to find existing node. + TLink* curr = root; + while (ETL_NULLPTR != curr) + { + auto* const result = static_cast(curr); + const int cmp = comp(*result); + if (0 == cmp) + { + // Found! + return result; + } + + curr = curr->get_child(cmp > 0); + } + + // Not found. + return ETL_NULLPTR; + } + + template + etl::pair find_or_insert_impl(TCompare comp, TFactory factory) + { + // Try to find existing node. + bool is_right = false; + link_type* curr = get_root(); + link_type* parent = &origin; + while (ETL_NULLPTR != curr) + { + auto* const result = static_cast(curr); + const int cmp = comp(*result); + if (0 == cmp) + { + // Found! Tree was not modified. + return etl::make_pair(result, false); + } + + parent = curr; + is_right = cmp > 0; + curr = curr->get_child(is_right); + } + + // Try to instantiate new node. + TValue* const result = factory(); + if (ETL_NULLPTR == result) + { + // Failed (or rejected)! The tree was not modified. + return etl::make_pair(ETL_NULLPTR, false); + } + + link_type& result_link = static_cast(*result); + ETL_ASSERT(!(result_link.is_linked()), ETL_ERROR(intrusive_avl_tree_value_is_already_linked)); + + // Link the new node. + parent->link_child(&result_link, is_right); + get_origin(parent)->etl_size += 1; + + retrace_on_insert(&result_link); + + // Successfully linked, so the tree was modified. + return etl::make_pair(result, true); + } + + template + static TValue* find_bound_impl(TLink* const root, TCompare comp) + { + TValue* result = ETL_NULLPTR; + TLink* next = root; + while (ETL_NULLPTR != next) + { + auto* const next_value = static_cast(next); + const int cmp = comp(*next_value); + if ((cmp > 0) || (IsUpper && (cmp == 0))) + { + next = next->get_right(); + } + else + { + result = next_value; + next = next->get_left(); + } + } + return result; + } + + static void erase_impl(link_type* const z_link) ETL_NOEXCEPT + { + // Remove only real and still linked items. + // Tree itself uses this `link_type` for its `origin` sentinel item, + // but you can't (and there is no need to) erase it. + if ((ETL_NULLPTR == z_link) || z_link->is_origin() || !z_link->is_linked()) + { + return; + } + + link_type* parent = z_link->get_parent(); + bool is_right = z_link->is_right_child(); + + if (!z_link->has_left()) + { + parent->link_child(z_link->get_right(), is_right); + } + else if (!z_link->has_right()) + { + parent->link_child(z_link->get_left(), is_right); + } + else + { + link_type* const y_link = next_in_order_impl(z_link); + link_type* const y_link_parent = y_link->get_parent(); + y_link->etl_bf = z_link->etl_bf; + if (z_link != y_link_parent) + { + y_link_parent->link_child(y_link->get_right(), y_link->is_right_child()); + link_type* const z_right = z_link->get_right(); + y_link->set_right(z_right); + z_right->set_parent(y_link); + parent->link_child(y_link, is_right); + + is_right = false; + parent = y_link_parent; + } + else + { + parent->link_child(y_link, is_right); + + is_right = true; + parent = y_link; + } + link_type* const z_left = z_link->get_left(); + y_link->set_left(z_left); + z_left->set_parent(y_link); + } + + z_link->clear(); + z_link->etl_size = 0; + get_origin(parent)->etl_size -= 1; + + retrace_on_erase(parent, is_right); + } + + private: + + /// This is the origin link which left child points to the root link (if any) of the tree. + /// This link instance is internal one, and it's almost never exposed to the public + /// API of the tree - the only exception is its natural usage for the "end" terminal iterator. + /// So, the end iterator contains address of this sentinel node (rather than not null pointer). + link_type origin; + +#if ETL_USING_CPP11 +#else + // Disable copy construction and assignment. + intrusive_avl_tree_base(const intrusive_avl_tree_base&); + intrusive_avl_tree_base& operator=(const intrusive_avl_tree_base&); + + template + struct CompareFactory + { + TValue& value_ref; + TBinaryCompare binary_comp; + + CompareFactory(TValue& value, TBinaryCompare comp) + : value_ref(value) + , binary_comp(comp) + { + } + + /// Adopts binary comparator to "unary" one. + ETL_NODISCARD + int operator()(const TValue& other) const + { + return binary_comp(value_ref, other); + } + + /// Fake "factory" that returns address of existing value. + ETL_NODISCARD + TValue* operator()() const + { + return &value_ref; + } + + }; // CompareFactory +#endif + + static void retrace_on_insert(link_type* curr) + { + link_type* parent = curr->get_parent(); + while (!parent->is_origin()) + { + const bool is_right = curr->is_right_child(); + curr = parent->adjust_balance(is_right); + parent = curr->get_parent(); + if (curr->etl_bf == 0) + { + break; + } + } + + if (parent->is_origin()) + { + parent->set_left(curr); + } + } + + static void retrace_on_erase(link_type* parent, bool is_right) + { + while (!parent->is_origin()) + { + link_type* const curr = parent->adjust_balance(!is_right); + parent = curr->get_parent(); + if ((curr->etl_bf != 0) || parent->is_origin()) + { + if (parent->is_origin()) + { + parent->set_left(curr); + } + break; + } + is_right = curr->is_right_child(); + } + } + + }; // intrusive_avl_tree_base + + //*************************************************************************** + /// \ingroup intrusive_avl_tree + /// An intrusive AVL tree. Stores elements derived from 'intrusive_avl_tree::link_type'. + /// + /// NB! The tree itself is not real owner (of memory) of its nodes. + /// The node `TValue` type contains all the necessary means (via required inheritance from `link_type`) + /// to be able to participate in the tree as a "linked" node - hence the "intrusive" term. + /// + /// \warning This tree cannot be used for concurrent access from multiple threads. + /// \tparam TValue The type of value that the tree holds. + /// \tparam ID_ The link ID that the value is derived from. + //*************************************************************************** + template + class intrusive_avl_tree : public etl::intrusive_avl_tree_base + { + typedef etl::intrusive_avl_tree_base base; + + public: + + // Node typedef. + typedef typename base::link_type link_type; + + // STL style typedefs. + typedef TValue value_type; + typedef value_type* pointer; + typedef const value_type* const_pointer; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef size_t size_type; +#if ETL_USING_CPP11 + typedef value_type&& rvalue_reference; +#endif + + //************************************************************************* + /// Defines iterator of mutable items of the tree. + /// + /// Normally, an iterator might... + /// - either reference an existing item; + /// - or be equal to the `end`. + /// Iterator could also be in "valueless" state: + /// - see default constructor; + /// - special cases of `find_or_insert`; + /// - advanced traversal methods, like `get_parent` and `get_child`. + /// Both "end" and "valueless" conditions could be easy isolated using `has_value` or `bool operator`: + /// - `has_value() == true` - iterator references a real node + /// - `it == end()` - terminal sentinel + /// - `!has_value() && it != end()` - valueless iterator + /// The real item (refenced by the iterator) could be modified by the user, + /// but so that it doesn't affect current ordering of the tree. + //************************************************************************* + class iterator : public etl::iterator + { + public: + + friend class intrusive_avl_tree; + friend class const_iterator; + + iterator() ETL_NOEXCEPT + : p_value(ETL_NULLPTR) + { + } + + //************************************************************************* + /// Increments iterator to point to the next ("greater") item. + /// Complexity: amortized O(1), worst-case O(log(N)). + //************************************************************************* + iterator& operator++() ETL_NOEXCEPT + { + p_value = base::next_in_order_impl(p_value); + return *this; + } + + //************************************************************************* + /// Increments iterator to point to the next ("greater") item. + /// Complexity: amortized O(1), worst-case O(log(N)). + /// Returns original iterator (before the increment). + //************************************************************************* + iterator operator++(int) ETL_NOEXCEPT + { + iterator temp(*this); + p_value = base::next_in_order_impl(p_value); + return temp; + } + + //************************************************************************* + /// Decrements iterator to point to the previous ("smaller") item. + /// Complexity: amortized O(1), worst-case O(log(N)). + //************************************************************************* + iterator& operator--() ETL_NOEXCEPT + { + p_value = base::prev_in_order_impl(p_value); + return *this; + } + + //************************************************************************* + /// Decrements iterator to point to the previous ("smaller") item. + /// Complexity: amortized O(1), worst-case O(log(N)). + /// Returns original iterator (before the decrement). + //************************************************************************* + iterator operator--(int) ETL_NOEXCEPT + { + iterator temp(*this); + p_value = base::prev_in_order_impl(p_value); + return temp; + } + + //************************************************************************* + /// Dereferences an existing mutable item. + /// Could be used only for iterator which has value (`has_value() == true`). + /// If asserts or exceptions are enabled, throws an `etl::intrusive_avl_tree_iterator_exception` + /// if iterator doesn't reference a real item. + //************************************************************************* + reference operator*() const + { + ETL_ASSERT(has_value(), ETL_ERROR(intrusive_avl_tree_iterator_exception)); + +#include "private/diagnostic_null_dereference_push.h" + + return *static_cast(p_value); +#include "private/diagnostic_pop.h" + } + + //************************************************************************* + /// Gets address of an existing mutable item (if any). + /// Returns `nullptr` if iterator doesn't reference a real item. + //************************************************************************* + pointer get() const ETL_NOEXCEPT + { + return static_cast(has_value() ? p_value : ETL_NULLPTR); + } + + //************************************************************************* + /// Dereferences an existing mutable item. + /// Could be used only for iterator which has value (`has_value() == true`). + /// If asserts or exceptions are enabled, throws an `etl::intrusive_avl_tree_iterator_exception` + /// if iterator doesn't reference a real item. + //************************************************************************* + pointer operator->() const + { + ETL_ASSERT_OR_RETURN_VALUE(has_value(), ETL_ERROR(intrusive_avl_tree_iterator_exception), ETL_NULLPTR); + + return static_cast(p_value); + } + + friend bool operator==(const iterator& lhs, const iterator& rhs) ETL_NOEXCEPT + { + return lhs.p_value == rhs.p_value; + } + + friend bool operator!=(const iterator& lhs, const iterator& rhs) ETL_NOEXCEPT + { + return !(lhs == rhs); + } + + explicit operator bool() const ETL_NOEXCEPT + { + return has_value(); + } + + ETL_NODISCARD + bool has_value() const ETL_NOEXCEPT + { + return base::is_real_link(p_value); + } + + //************************************************************************* + /// Gets balance factor of tree node. + /// Complexity: O(1). + /// Normally is not needed unless advanced traversal is required. + /// Result: -1, 0 or +1 depending on children tree height difference. + //************************************************************************* + ETL_NODISCARD + int_fast8_t get_balance_factor() const ETL_NOEXCEPT + { + return base::get_balance_factor_impl(p_value); + } + + //************************************************************************* + /// Gets parent node. + /// Complexity: O(1). + /// Normally is not needed unless advanced traversal is required. + /// Result iterator will be valueless (`has_value() == false`) if there is no parent. + //************************************************************************* + ETL_NODISCARD + iterator get_parent() const ETL_NOEXCEPT + { + return iterator(base::get_parent_impl(p_value)); + } + + //************************************************************************* + /// Gets a child node. + /// Complexity: O(1). + /// Normally is not needed unless advanced traversal is required. + /// Result iterator will be valueless (`has_value() == false`) if there is no such child. + //************************************************************************* + ETL_NODISCARD + iterator get_child(const bool is_right) const ETL_NOEXCEPT + { + return iterator(base::get_child_impl(p_value, is_right)); + } + + private: + + explicit iterator(link_type* value) + : p_value(value) + { + } + + link_type* p_value; + + }; // iterator + + //************************************************************************* + /// Defines constant iterator of immutable items of the tree. + /// + /// Normally, an iterator might... + /// - either reference an existing item; + /// - or be equal to the `end`. + /// Iterator could also be in "valueless" state: + /// - see default constructor; + /// - advanced traversal methods, like `get_parent` and `get_child`. + /// Both "end" and "valueless" conditions could be easy isolated using `has_value` or `bool operator`: + /// - `has_value() == true` - iterator references a real node + /// - `it == end()` - terminal sentinel + /// - `!has_value() && it != end()` - valueless iterator + //************************************************************************* + class const_iterator : public etl::iterator + { + public: + + friend class intrusive_avl_tree; + + const_iterator() ETL_NOEXCEPT + : p_value(ETL_NULLPTR) + { + } + + // Implicit by design - it's always safe to make const iterator from ordinary one. + const_iterator(const typename intrusive_avl_tree::iterator& other) ETL_NOEXCEPT // NOLINT + : p_value(other.p_value) + { + } + + //************************************************************************* + /// Increments const iterator to point to the next ("greater") item. + /// Complexity: amortized O(1), worst-case O(log(N)). + //************************************************************************* + const_iterator& operator++() ETL_NOEXCEPT + { + p_value = base::next_in_order_impl(p_value); + return *this; + } + + //************************************************************************* + /// Increments const iterator to point to the next ("greater") item. + /// Complexity: amortized O(1), worst-case O(log(N)). + /// Returns original iterator (before the increment). + const_iterator operator++(int) ETL_NOEXCEPT + { + const_iterator temp(*this); + p_value = base::next_in_order_impl(p_value); + return temp; + } + + //************************************************************************* + /// Decrements const iterator to point to the previous ("smaller") item. + /// Complexity: amortized O(1), worst-case O(log(N)). + //************************************************************************* + const_iterator& operator--() ETL_NOEXCEPT + { + p_value = base::prev_in_order_impl(p_value); + return *this; + } + + //************************************************************************* + /// Decrements const iterator to point to the previous ("smaller") item. + /// Complexity: amortized O(1), worst-case O(log(N)). + /// Returns original iterator (before the decrement). + //************************************************************************* + const_iterator operator--(int) ETL_NOEXCEPT + { + const_iterator temp(*this); + p_value = base::prev_in_order_impl(p_value); + return temp; + } + + //************************************************************************* + /// Dereferences an existing immutable item. + /// Could be used only for iterator which has value (`has_value() == true`). + /// If asserts or exceptions are enabled, throws an `etl::intrusive_avl_tree_iterator_exception` + /// if iterator doesn't reference a real item. + //************************************************************************* + const_reference operator*() const + { + ETL_ASSERT(has_value(), ETL_ERROR(intrusive_avl_tree_iterator_exception)); + + return *static_cast(p_value); + } + + //************************************************************************* + /// Gets address of an existing immutable item (if any). + /// Returns `nullptr` if iterator doesn't reference a real item. + //************************************************************************* + const_pointer get() const ETL_NOEXCEPT + { + return static_cast(has_value() ? p_value : ETL_NULLPTR); + } + + //************************************************************************* + /// Dereferences an existing immutable item. + /// Could be used only for iterator which has value (`has_value() == true`). + /// If asserts or exceptions are enabled, throws an `etl::intrusive_avl_tree_iterator_exception` + /// if iterator doesn't reference a real item. + //************************************************************************* + const_pointer operator->() const + { + ETL_ASSERT_OR_RETURN_VALUE(has_value(), ETL_ERROR(intrusive_avl_tree_iterator_exception), ETL_NULLPTR); + + return static_cast(p_value); + } + + friend bool operator==(const const_iterator& lhs, const const_iterator& rhs) ETL_NOEXCEPT + { + return lhs.p_value == rhs.p_value; + } + + friend bool operator!=(const const_iterator& lhs, const const_iterator& rhs) ETL_NOEXCEPT + { + return !(lhs == rhs); + } + + explicit operator bool() const ETL_NOEXCEPT + { + return has_value(); + } + + ETL_NODISCARD + bool has_value() const ETL_NOEXCEPT + { + return base::is_real_link(p_value); + } + + //************************************************************************* + /// Gets balance factor of tree node. + /// Complexity: O(1). + /// Normally is not needed unless advanced traversal is required. + /// Result: -1, 0 or +1 depending on children tree height difference. + //************************************************************************* + ETL_NODISCARD + int_fast8_t get_balance_factor() const ETL_NOEXCEPT + { + return base::get_balance_factor_impl(p_value); + } + + //************************************************************************* + /// Gets parent node. + /// Complexity: O(1). + /// Normally is not needed unless advanced traversal is required. + /// Result iterator will be valueless (`has_value() == false`) if there is no parent. + //************************************************************************* + ETL_NODISCARD + const_iterator get_parent() const ETL_NOEXCEPT + { + return const_iterator(base::get_parent_impl(p_value)); + } + + //************************************************************************* + /// Gets a child node. + /// Complexity: O(1). + /// Normally is not needed unless advanced traversal is required. + /// Result iterator will be valueless (`has_value() == false`) if there is no such child. + //************************************************************************* + ETL_NODISCARD + const_iterator get_child(const bool is_right) const ETL_NOEXCEPT + { + return const_iterator(base::get_child_impl(p_value, is_right)); + } + + private: + + explicit const_iterator(const link_type* value) + : p_value(value) + { + } + + const link_type* p_value; + + }; // const_iterator + + //************************************************************************* + /// Default constructor. + //************************************************************************* + intrusive_avl_tree() ETL_NOEXCEPT + : intrusive_avl_tree_base() + { + } + + //************************************************************************* + /// Constructor from range of items. + /// Complexity: O(N*log(N)). + /// NB! All items in the range must be in "unlinked" state initially, otherwise "already linked" exception. + /// On success, all inserted items (except possible duplicated, see below comparator description) will be linked. + /// + /// The binary comparator should accept two `const value_type&` arguments (aka `lhs` and `rhs`), + /// and return integer result (aka `lhs - rhs` "substraction" result): + /// - `>0` if the `lhs` is "greater" than the `rhs` + /// - `0` if the `lhs` is "equal" to the `rhs` + /// - `<0` if the `lhs` is "smaller" than the `rhs` + /// - if throws then exception is propagated + /// Hints: + /// - if you want duplicates then always return either `>0` or `<0`, even for equal arguments: + /// - `<0` (e.g. `-1`) will prepend a duplicate item to the tree + /// - `>0` (e.g. `+1`) will append a duplicate item to the tree + /// - if you want to avoid duplicates in the result tree then use `0` for "equal" arguments - + /// in such case only the first item of duplicates in the range + /// will be linked to the tree; the rest will be skipped (left unlinked) + /// - `etl::compare::cmp` could be directly used as comparator (if value's `less` is defined) - + /// its `Less`, `Equal` and `Greater` are convertible/compatible with expected result integers. + /// + /// If asserts or exceptions are enabled, throws: + /// - an etl::intrusive_avl_tree_iterator_exception if the `first` > `last` (DEBUG build only). + /// - an etl::intrusive_avl_tree_value_is_already_linked if any item (in `[first, last)` range) is already linked to some other tree. + /// - whatever the `binary_comp` might throw + /// Any exception during tree building will unlink already inserted nodes - so it's all or nothing. + //************************************************************************* + template + intrusive_avl_tree(TIterator first, TIterator last, TBinaryCompare binary_comp, + typename etl::enable_if::value, int>::type = 0) + { + base::template assign_impl(first, last, binary_comp); + } + + //************************************************************************* + /// Destructor. + /// Complexity: O(N). + //************************************************************************* + ~intrusive_avl_tree() = default; + +#if ETL_USING_CPP11 + //************************************************************************* + /// Move constructor. + /// Complexity: O(1). + //************************************************************************* + intrusive_avl_tree(intrusive_avl_tree&&) ETL_NOEXCEPT = default; + + //************************************************************************* + /// Move assignment. + /// Does nothing in case of self-assignment (when `this == &other`). + /// Complexity: O(N), where N is the size of `this` tree before assignment - + /// all former items have to be unlinked. + //************************************************************************* + intrusive_avl_tree& operator=(intrusive_avl_tree&&) ETL_NOEXCEPT = default; + + intrusive_avl_tree(const intrusive_avl_tree&) = delete; + intrusive_avl_tree& operator=(const intrusive_avl_tree&) = delete; +#endif + + //************************************************************************* + /// Gets the beginning of the intrusive_avl_tree. + /// Complexity: O(log(N)). + /// Returns `end` if tree is empty. + //************************************************************************* + ETL_NODISCARD + iterator begin() ETL_NOEXCEPT + { + return iterator(base::begin_impl(base::get_origin())); + } + + //************************************************************************* + /// Gets the beginning of the intrusive_avl_tree. + /// Complexity: O(log(N)). + /// Returns `end` if tree is empty. + //************************************************************************* + ETL_NODISCARD + const_iterator begin() const ETL_NOEXCEPT + { + return const_iterator(base::begin_impl(base::get_origin())); + } + + //************************************************************************* + /// Gets the beginning of the intrusive_avl_tree. + /// Complexity: O(log(N)). + /// Returns `end` if tree is empty. + //************************************************************************* + ETL_NODISCARD + const_iterator cbegin() const ETL_NOEXCEPT + { + return begin(); + } + + //************************************************************************* + /// Gets the end of the intrusive_avl_tree. + /// Complexity: O(1). + //************************************************************************* + ETL_NODISCARD + iterator end() ETL_NOEXCEPT + { + return iterator(base::end_impl(base::get_origin())); + } + + //************************************************************************* + /// Gets the end of the intrusive_avl_tree. + /// Complexity: O(1). + //************************************************************************* + ETL_NODISCARD + const_iterator end() const ETL_NOEXCEPT + { + return const_iterator(base::end_impl(base::get_origin())); + } + + //************************************************************************* + /// Gets the end of the intrusive_avl_tree. + /// Complexity: O(1). + //************************************************************************* + ETL_NODISCARD + const_iterator cend() const ETL_NOEXCEPT + { + return end(); + } + + //************************************************************************* + /// Gets the maximum of the intrusive_avl_tree. + /// Complexity: O(log(N)). + /// Returns `end` if tree is empty. + //************************************************************************* + ETL_NODISCARD + iterator max() ETL_NOEXCEPT + { + return --end(); + } + + //************************************************************************* + /// Gets the maximum of the intrusive_avl_tree. + /// Complexity: O(log(N)). + /// Returns `end` if tree is empty. + //************************************************************************* + ETL_NODISCARD + const_iterator max() const ETL_NOEXCEPT + { + return --end(); + } + + //************************************************************************* + /// Gets the minimum of the intrusive_avl_tree. + /// Complexity: O(log(N)). + /// Returns `end` if tree is empty. + //************************************************************************* + ETL_NODISCARD + iterator min() ETL_NOEXCEPT + { + return begin(); + } + + //************************************************************************* + /// Gets the minimum of the intrusive_avl_tree. + /// Complexity: O(log(N)). + /// Returns `end` if tree is empty. + //************************************************************************* + ETL_NODISCARD + const_iterator min() const ETL_NOEXCEPT + { + return begin(); + } + + //************************************************************************* + /// Returns an iterator pointing to the first item that compares as "not less". + /// Complexity: O(log(N)) assuming O(1) for the comparator. + /// The unary comparator should accept single `const value_type& value` argument, + /// and return integer result: + /// - `>0` (e.g. `+1`) if the find target is "greater" than the argument + /// - `0` if the target is found + /// - `<0` (e.g. `-1`) if the target is "smaller" than the argument + /// - if throws then exception is propagated + /// + /// Result iterator will be `end()` if no such item exists. + //************************************************************************* + template + iterator lower_bound(TCompare comp) + { + pointer ptr = base::template find_bound_impl(base::get_root(), comp); + return make_iterator(ptr, end()); + } + template + const_iterator lower_bound(TCompare comp) const + { + const_pointer ptr = base::template find_bound_impl(base::get_root(), comp); + return make_iterator(ptr, end()); + } + + //************************************************************************* + /// Returns an iterator pointing to the first item that compares as "greater". + /// Complexity: O(log(N)) assuming O(1) for the comparator. + /// The unary comparator should accept single `const value_type& value` argument, + /// and return integer result: + /// - `>0` (e.g. `+1`) if the find target is "greater" than the argument + /// - `0` if the target is found + /// - `<0` (e.g. `-1`) if the target is "smaller" than the argument + /// - if throws then exception is propagated + /// + /// Result iterator will be `end()` if no such item exists. + //************************************************************************* + template + iterator upper_bound(TCompare comp) + { + pointer ptr = base::template find_bound_impl(base::get_root(), comp); + return make_iterator(ptr, end()); + } + template + const_iterator upper_bound(TCompare comp) const + { + const_pointer ptr = base::template find_bound_impl(base::get_root(), comp); + return make_iterator(ptr, end()); + } + + //************************************************************************* + /// Gets root node (if any). + /// Complexity: O(1). + /// Normally is not needed unless advanced traversal is required. + /// Result iterator will be valueless (`has_value() == false`) if tree is empty. + //************************************************************************* + ETL_NODISCARD + iterator get_root() ETL_NOEXCEPT + { + return iterator(base::get_root()); + } + + //************************************************************************* + /// Gets root node (if any). + /// Complexity: O(1). + /// Normally is not needed unless advanced traversal is required. + /// Result iterator will be valueless (`has_value() == false`) if tree is empty. + //************************************************************************* + ETL_NODISCARD + const_iterator get_root() const ETL_NOEXCEPT + { + return const_iterator(base::get_root()); + } + + //************************************************************************* + /// Finds an item using given unary comparator. + /// Complexity: O(log(N)) assuming O(1) for the comparator. + /// + /// The unary comparator should accept single `const value_type& value` argument, + /// and return integer result: + /// - `>0` (e.g. `+1`) if the find target is "greater" than the argument + /// - `0` if the target is found + /// - `<0` (e.g. `-1`) if the target is "smaller" than the argument + /// - if throws then exception is propagated + /// + /// Result iterator will be `end()` if there is no matching item. + //************************************************************************* + template + iterator find(TCompare comp) + { + pointer ptr = base::template find_impl(base::get_root(), comp); + return make_iterator(ptr, end()); + } + + //************************************************************************* + /// Finds a constant item using given unary comparator. + /// Complexity: O(log(N)) assuming O(1) for the comparator. + /// + /// The unary comparator should accept single `const value_type& value` argument, + /// and return integer result: + /// - `>0` (e.g. `+1`) if the find target is "greater" than the argument + /// - `0` if the target is found + /// - `<0` (e.g. `-1`) if the target is "smaller" + /// - if throws then exception is propagated + /// + /// Result iterator will be `end()` if there is no matching item. + //************************************************************************* + template + const_iterator find(TCompare comp) const + { + const_pointer ptr = base::template find_impl(base::get_root(), comp); + return make_iterator(ptr, end()); + } + + //************************************************************************* + /// Finds an existing item using given unary comparator, and if not found + /// then invokes `factory` functor to insert a new item. The new item + /// is inserted at the tree position where the search has stopped. + /// Complexity: O(log(N)) assuming O(1) for the comparator and factory. + /// Operation does NOT invalidate any already existing iterators, + /// but depending on where the new item was linked to the tree + /// the existing iterators may skip the recently linked item. + /// + /// The unary comparator should accept single `const value_type& value` argument, + /// and return integer result: + /// - `>0` (e.g. `+1`) if the find target is "greater" than the argument + /// - `0` if the target is found + /// - `<0` (e.g. `-1`) if the target is "smaller" than the argument + /// - if throws then exception is propagated + /// Hint: If result tree should contain "duplicates" then return non-zero + /// result even for "equal" items, like e.g.: + /// - `+1` to append a new item after existing duplicates + /// - `-1` to prepend a new item before existing duplicates + /// + /// The factory functor should return address of a "new" value (castable to the `link_type*`). + /// The returned value should not be already linked to any tree, otherwise throws + /// `intrusive_avl_tree_value_is_already_linked` (if asserts or exceptions are enabled). + /// If return address is `nullptr` then tree won't be modified, + /// and final result iterator will be valueless. + /// If factory throws then exception is propagated (without modifying the tree). + /// + /// Result contains a pair of: + /// - iterator to found (or inserted) item (could be valueless, but never `end()`). + /// - boolean indicating whether tree was modified (due to successful insertion). + //************************************************************************* + template + etl::pair find_or_insert(TCompare comp, TFactory factory) + { + const etl::pair ptr_mod = base::template find_or_insert_impl(comp, factory); + return etl::make_pair(make_iterator(ptr_mod.first, iterator()), ptr_mod.second); + } + + //************************************************************************* + /// Erases the value at the specified position. + /// Complexity: O(log(N)) - includes tree rebalancing. + /// Operation invalidates any existing iterator to the same item, + /// but it does NOT affect any other iterators. + /// \param position iterator must originate from the same tree instance. + /// Returns iterator to the next tree node (after the erased one). + /// If asserts or exceptions are enabled, throws etl::intrusive_avl_tree_iterator_exception + /// if iterator doesn't reference a real item. + /// Hint: use `clear` method if you need erase all items - no tree rebalancing will be involved. + //************************************************************************* + iterator erase(iterator position) + { + ETL_ASSERT(position.has_value(), ETL_ERROR(intrusive_avl_tree_iterator_exception)); +#if ETL_IS_DEBUG_BUILD + // Iterator must originate from the same tree instance. + // The check is still of the same O(log(N)) complexity. + ETL_ASSERT(base::get_origin(position.p_value) == &base::get_origin(), ETL_ERROR(intrusive_avl_tree_iterator_exception)); +#endif + + iterator next(position); + ++next; + + base::erase_impl(position.p_value); + + return next; + } + + //************************************************************************* + /// Erases the value at the specified position. + /// Complexity: O(log(N)) - includes tree rebalancing. + /// Operation invalidates any existing iterator to the same item, + /// but it does NOT affect any other iterators. + /// \param position iterator must originate from the same tree instance. + /// Returns iterator to the next tree node (after the erased one). + /// If asserts or exceptions are enabled, throws etl::intrusive_avl_tree_iterator_exception + /// if iterator doesn't reference a real item. + /// Hint: use `clear` method if you need erase all items - no tree rebalancing will be involved. + //************************************************************************* + iterator erase(const_iterator position) + { + // It's safe to `const_cast` b/c we just need iterator to locate corresponding link. + // Tree itself is not `const` anyway - so it's a legitimate operation, and it also matches + // with similar `std::list::erase` or `etl::intrusive_list::erase` methods. + return erase(iterator(const_cast(position.p_value))); + } + + //************************************************************************* + /// Visits all items of the tree in (ascending or descending) order. + /// See https://en.wikipedia.org/wiki/Tree_traversal + /// Complexity: O(N); no recursion. + /// `is_reverse` determines in which order: `false` -> ascending, `true` -> descending. + /// The `visitor` functor will be called with reference to the next in-order item. + /// The item could be modified but so that it doesn't affect current ordering of the tree. + /// NB! The visitor must not modify the tree during visitation. + /// If visitor throws then exception is propagated. + //************************************************************************* + template + void visit_in_order(const bool is_reverse, Visitor visitor) + { +#if ETL_USING_CPP11 + base::visit_in_order_impl( // + &base::get_origin(), is_reverse, // + [&visitor](link_type& link) { visitor(static_cast(link)); }); +#else + const CastingVisitor casting_visitor(visitor); + base::visit_in_order_impl(&base::get_origin(), is_reverse, casting_visitor); +#endif + } + + //************************************************************************* + /// Visits all items of the tree in (ascending or descending) order. + /// See https://en.wikipedia.org/wiki/Tree_traversal + /// Complexity: O(N); no recursion. + /// `is_reverse` determines in which order: `false` -> ascending, `true` -> descending. + /// The `visitor` functor will be called with const reference to the next in-order item. + /// NB! The visitor must not modify the tree during visitation. + /// If visitor throws then exception is propagated. + //************************************************************************* + template + void visit_in_order(const bool is_reverse, Visitor visitor) const + { +#if ETL_USING_CPP11 + base::visit_in_order_impl( // + &base::get_origin(), is_reverse, // + [&visitor](const link_type& link) { visitor(static_cast(link)); }); +#else + const CastingVisitor casting_visitor(visitor); + base::visit_in_order_impl(&base::get_origin(), is_reverse, casting_visitor); +#endif + } + + //************************************************************************* + /// Visits all items of the tree using "post" ordering - + /// child items first, and then "current" item (finishing with the root one). + /// See https://en.wikipedia.org/wiki/Tree_traversal + /// Complexity: O(N); no recursion. + /// `is_reverse` determines in which order to visit children: + /// - `false` -> "smaller" child first (if any), and then "bigger" one + /// - `true` -> "bigger" child first (if any), and then "smaller" one + /// The `visitor` functor will be called with reference to the next post-order item. + /// The item could be modified but so that it doesn't affect current ordering of the tree. + /// NB! The visitor must not modify the tree during visitation. + /// If visitor throws then exception is propagated. + //************************************************************************* + template + void visit_post_order(const bool is_reverse, Visitor visitor) + { +#if ETL_USING_CPP11 + base::visit_post_order_impl( // + &base::get_origin(), is_reverse, // + [&visitor](link_type& link) { visitor(static_cast(link)); }); +#else + const CastingVisitor casting_visitor(visitor); + base::visit_post_order_impl(&base::get_origin(), is_reverse, casting_visitor); +#endif + } + + //************************************************************************* + /// Visits all items of the tree using "post" ordering - + /// child items first, and then "current" item (finishing with the root one). + /// See https://en.wikipedia.org/wiki/Tree_traversal + /// Complexity: O(N); no recursion. + /// `is_reverse` determines in which order to visit children: + /// - `false` -> "smaller" child first (if any), and then "bigger" one + /// - `true` -> "bigger" child first (if any), and then "smaller" one + /// The `visitor` functor will be called with const reference to the next post-order item. + /// NB! The visitor must not modify the tree during visitation. + /// If visitor throws then exception is propagated. + //************************************************************************* + template + void visit_post_order(const bool is_reverse, Visitor visitor) const + { +#if ETL_USING_CPP11 + base::visit_post_order_impl( // + &base::get_origin(), is_reverse, // + [&visitor](const link_type& link) { visitor(static_cast(link)); }); +#else + const CastingVisitor casting_visitor(visitor); + base::visit_post_order_impl(&base::get_origin(), is_reverse, casting_visitor); +#endif + } + + //************************************************************************* + /// Visits all items of the tree using "pre" ordering - + /// "current" item first (starting from the root), and then children. + /// See https://en.wikipedia.org/wiki/Tree_traversal + /// Complexity: O(N); no recursion. + /// `is_reverse` determines in which order to visit children: + /// - `false` -> "smaller" child first (if any), and then "bigger" one + /// - `true` -> "bigger" child first (if any), and then "smaller" one + /// The `visitor` functor will be called with reference to the next pre-order item. + /// The item could be modified but so that it doesn't affect current ordering of the tree. + /// NB! The visitor must not modify the tree during visitation. + /// If visitor throws then exception is propagated. + //************************************************************************* + template + void visit_pre_order(const bool is_reverse, Visitor visitor) + { +#if ETL_USING_CPP11 + base::visit_pre_order_impl( // + &base::get_origin(), is_reverse, // + [&visitor](link_type& link) { visitor(static_cast(link)); }); +#else + const CastingVisitor casting_visitor(visitor); + base::visit_pre_order_impl(&base::get_origin(), is_reverse, casting_visitor); +#endif + } + + //************************************************************************* + /// Visits all items of the tree using "pre" ordering - + /// "current" item first (starting from the root), and then children. + /// See https://en.wikipedia.org/wiki/Tree_traversal + /// Complexity: O(N); no recursion. + /// `is_reverse` determines in which order to visit children: + /// - `false` -> "smaller" child first (if any), and then "bigger" one + /// - `true` -> "bigger" child first (if any), and then "smaller" one + /// The `visitor` functor will be called with const reference to the next pre-order item. + /// NB! The visitor must not modify the tree during visitation. + /// If visitor throws then exception is propagated. + //************************************************************************* + template + void visit_pre_order(const bool is_reverse, Visitor visitor) const + { +#if ETL_USING_CPP11 + base::visit_pre_order_impl( // + &base::get_origin(), is_reverse, // + [&visitor](const link_type& link) { visitor(static_cast(link)); }); +#else + const CastingVisitor casting_visitor(visitor); + base::visit_pre_order_impl(&base::get_origin(), is_reverse, casting_visitor); +#endif + } + + private: + +#if ETL_USING_CPP11 +#else + // Disable copy construction and assignment. + intrusive_avl_tree(const intrusive_avl_tree&); + intrusive_avl_tree& operator=(const intrusive_avl_tree& rhs); + + template + struct CastingVisitor + { + Visitor& visitor; + explicit CastingVisitor(Visitor& visitor) + : visitor(visitor) + { + } + + template + void operator()(TLink& link) const + { + visitor(static_cast(link)); + } + }; +#endif + + template + ETL_NODISCARD + static TIterator make_iterator(Pointer const ptr, const TIterator end) + { + return (ETL_NULLPTR != ptr) ? TIterator(ptr) : end; + } + + }; // intrusive_avl_tree + +} // namespace etl + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a559add4..f6103799 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -205,6 +205,7 @@ add_executable(etl_tests test_inplace_function.cpp test_instance_count.cpp test_integral_limits.cpp + test_intrusive_avl_tree.cpp test_intrusive_forward_list.cpp test_intrusive_links.cpp test_intrusive_list.cpp diff --git a/test/data.h b/test/data.h index 782ef92a..75a75a6f 100644 --- a/test/data.h +++ b/test/data.h @@ -220,10 +220,13 @@ public: TestDataM& operator=(TestDataM&& other) noexcept { - value = std::move(other.value); - valid = true; + if (this != &other) + { + value = std::move(other.value); + valid = other.valid; - other.valid = false; + other.valid = false; + } return *this; } diff --git a/test/meson.build b/test/meson.build index b84a21be..9e4d2d79 100644 --- a/test/meson.build +++ b/test/meson.build @@ -134,6 +134,7 @@ etl_test_sources = files( 'test_indirect_vector_external_buffer.cpp', 'test_instance_count.cpp', 'test_integral_limits.cpp', + 'test_intrusive_avl_tree.cpp', 'test_intrusive_forward_list.cpp', 'test_intrusive_links.cpp', 'test_intrusive_list.cpp', diff --git a/test/syntax_check/CMakeLists.txt b/test/syntax_check/CMakeLists.txt index 0e2627df..20851e7b 100644 --- a/test/syntax_check/CMakeLists.txt +++ b/test/syntax_check/CMakeLists.txt @@ -220,6 +220,7 @@ target_sources(tests PRIVATE inplace_function.h.t.cpp instance_count.h.t.cpp integral_limits.h.t.cpp + intrusive_avl_tree.h.t.cpp intrusive_forward_list.h.t.cpp intrusive_links.h.t.cpp intrusive_list.h.t.cpp diff --git a/test/syntax_check/intrusive_avl_tree.h.t.cpp b/test/syntax_check/intrusive_avl_tree.h.t.cpp new file mode 100644 index 00000000..13985fd7 --- /dev/null +++ b/test/syntax_check/intrusive_avl_tree.h.t.cpp @@ -0,0 +1,29 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Sergei Shirokov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#include diff --git a/test/test_intrusive_avl_tree.cpp b/test/test_intrusive_avl_tree.cpp new file mode 100644 index 00000000..77754bfa --- /dev/null +++ b/test/test_intrusive_avl_tree.cpp @@ -0,0 +1,1606 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Sergei Shirokov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#include "unit_test_framework.h" + +#include "data.h" + +#include "etl/compare.h" +#include "etl/intrusive_avl_tree.h" +#include "etl/optional.h" + +#include +#include +#include +#include +#include +#include + +typedef TestDataM ItemM; +typedef TestDataNDC ItemNDC; + +namespace +{ + typedef etl::intrusive_avl_tree_base<0>::link_type ZeroLink; + typedef etl::intrusive_avl_tree_base<1>::link_type FirstLink; + typedef etl::intrusive_avl_tree_base<2>::link_type SecondLink; + + //*************************************************************************** + class ItemNDCNode + : public ZeroLink + , public FirstLink + { + public: + + using compare = etl::compare; + + ItemNDCNode(const int value, const int index) + : data(value, index) + { + } + + friend bool operator<(const ItemNDCNode& lhs, const ItemNDCNode& rhs) + { + return lhs.data < rhs.data; + } + + friend bool operator==(const ItemNDCNode& lhs, const ItemNDCNode& rhs) + { + return lhs.data == rhs.data; + } + + struct CompareByValue + { + int value; + etl::optional excptn; + + explicit CompareByValue(const int value_, const etl::optional& excptn_ = etl::nullopt) + : value(value_) + , excptn(excptn_) + { + } + + ETL_NODISCARD + int operator()(const ItemNDCNode& other) const + { + if (excptn.has_value()) + { + throw etl::exception(excptn.value()); + } + return value - other.data.value; + } + + }; // CompareByValue + + ETL_NODISCARD + static compare::cmp_result always_less(const ItemNDCNode& other) + { + (void)other; + return compare::Less; + } + + ETL_NODISCARD + static compare::cmp_result always_greater(const ItemNDCNode& other) + { + (void)other; + return compare::Greater; + } + + ETL_NODISCARD + static compare::cmp_result compare_prepend_dups(const ItemNDCNode& lhs, const ItemNDCNode& rhs) + { + const auto cmp = compare::cmp(lhs, rhs); + return (cmp != compare::Equal) ? cmp : compare::Less; + } + + ETL_NODISCARD + static compare::cmp_result compare_append_dups(const ItemNDCNode& lhs, const ItemNDCNode& rhs) + { + const auto cmp = compare::cmp(lhs, rhs); + return (cmp != compare::Equal) ? cmp : compare::Greater; + } + + ItemNDC data; + + }; // ItemNDCNode + + //*************************************************************************** + class ItemMNode : public SecondLink + { + public: + + explicit ItemMNode(const int value) + : data(value) + { + } + + ETL_NODISCARD + friend bool operator<(const ItemMNode& lhs, const ItemMNode& rhs) + { + return lhs.data < rhs.data; + } + + ETL_NODISCARD + friend bool operator==(const ItemMNode& lhs, const ItemMNode& rhs) + { + return lhs.data == rhs.data; + } + + struct CompareByValue + { + int value; + + ETL_NODISCARD + int operator()(const ItemMNode& other) const + { + return value - other.data.value; + } + }; + + ETL_NODISCARD + static int compare(const ItemMNode& lhs, const ItemMNode& rhs) + { + return lhs.data.value - rhs.data.value; + } + + ItemM data; + + }; // ItemMNode + + //*************************************************************************** + typedef etl::intrusive_avl_tree DataNDC0; + typedef etl::intrusive_avl_tree DataNDC1; + typedef etl::intrusive_avl_tree DataM; + + typedef std::vector InitialDataM; + typedef std::vector InitialDataNDC; + + SUITE(test_intrusive_avl_tree) + { + //************************************************************************* + struct SetupFixture + { + InitialDataNDC sorted_data; + InitialDataNDC unsorted_data; + InitialDataM sorted_data_moveable; + + SetupFixture() + { + sorted_data.clear(); + for (int i = 0; i < 31; ++i) + { + sorted_data.emplace_back(i, i); + sorted_data_moveable.emplace_back(i); + } + + constexpr std::array unsorted_order{ + 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 17, 16, 19, 18, 21, 20, 23, 22, 25, 24, 27, 26, 30, 29, 28, + }; + unsorted_data.clear(); + for (const auto idx : unsorted_order) + { + unsorted_data.emplace_back(static_cast(idx), static_cast(idx)); + } + } + + template + int verify_link(const Iterator it) const + { + if (!it.has_value()) + { + return 0; + } + + const auto parent = it.get_parent(); + const auto left_child = it.get_child(false); + const auto right_child = it.get_child(true); + if (parent.has_value()) + { + const auto parent_left_child = parent.get_child(false); + const auto parent_right_child = parent.get_child(true); + CHECK((it == parent_left_child) || (it == parent_right_child)); + } + + const int left_height = verify_link(left_child); + if (left_child.has_value()) + { + CHECK(it == left_child.get_parent()); + } + + const int right_height = verify_link(right_child); + if (right_child.has_value()) + { + CHECK(it == right_child.get_parent()); + } + + const int_least8_t balance_factor = it.get_balance_factor(); + CHECK((-1 <= balance_factor) && (balance_factor <= 1)); + CHECK_EQUAL(right_height - left_height, static_cast(balance_factor)); + return 1 + etl::max(right_height, left_height); + } + + template + void verify_tree(const Tree& tree) const + { + typedef typename Tree::value_type value_type; + typedef etl::reverse_iterator rev_it; + + if (tree.empty()) + { + CHECK(tree.begin() == tree.end()); + CHECK(!tree.get_root().has_value()); + } + else + { + CHECK(tree.get_root().has_value()); + CHECK(!tree.get_root().get_parent().has_value()); + } + + const auto root = tree.get_root(); + verify_link(root); + CHECK(etl::is_sorted(tree.begin(), tree.end())); + CHECK(etl::is_sorted(rev_it(tree.end()), rev_it(tree.begin()), etl::greater())); + CHECK(tree.min() == tree.begin()); + CHECK(tree.max() == --tree.end()); + } + + template + ETL_NODISCARD + std::string to_graphviz(const Tree& tree) const + { + std::ostringstream ss; + ss << "// https://magjac.com/graphviz-visual-editor/\n"; + ss << "// OR paste to `dot -Tsvg > output.svg`.\n//\n"; + ss << "digraph {\n"; + ss << "node[style=filled,fontcolor=white,shape=egg,fillcolor=black,fontsize=28,fontname=Menlo];\n"; + + const auto beg = tree.begin(); + const auto end = tree.end(); + ss << "origin[fillcolor=gray];"; + for (auto curr = beg; curr != end; ++curr) + { + ss << curr->data.index; + ss << "[label=" << curr->data.value; + const auto balance_factor = curr.get_balance_factor(); + if (balance_factor != 0) + { + const std::string bf_color = (balance_factor < 0) ? "blue" : "orangered"; + ss << ",fillcolor=" << bf_color; + } + ss << "];"; + } + ss << "\n"; + for (auto curr = beg; curr != end; ++curr) + { + if (auto child = curr.get_child(false)) + { + ss << curr->data.index; + ss << ":sw->" << child->data.index << ":n;"; + } + if (auto child = curr.get_child(true)) + { + ss << curr->data.index; + ss << ":se->" << child->data.index << ":n;"; + } + } + if (auto root = tree.get_root()) + { + ss << "origin:sw->" << root->data.index << ":n[label=root,fontsize=28];"; + } + ss << "\n}\n\n"; + return ss.str(); + } + }; + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_default_constructor) + { + DataNDC0 data0; + DataNDC1 data1; + + CHECK(data0.empty()); + CHECK_EQUAL(0, data0.size()); + CHECK(data1.empty()); + CHECK_EQUAL(0, data1.size()); + + CHECK(data0.max() == data0.end()); + CHECK(data0.min() == data0.end()); + CHECK(data0.begin() == data0.end()); + CHECK(data0.cbegin() == data0.cend()); + CHECK(data0.lower_bound(ItemNDCNode::always_less) == data0.end()); + CHECK(data0.lower_bound(ItemNDCNode::always_greater) == data0.end()); + CHECK(data0.upper_bound(ItemNDCNode::always_less) == data0.end()); + CHECK(data0.upper_bound(ItemNDCNode::always_greater) == data0.end()); + + CHECK(data1.max() == data1.end()); + CHECK(data1.min() == data1.end()); + CHECK(data1.begin() == data1.end()); + CHECK(data1.cbegin() == data1.cend()); + CHECK(data1.lower_bound(ItemNDCNode::always_less) == data1.end()); + CHECK(data1.lower_bound(ItemNDCNode::always_greater) == data1.end()); + CHECK(data1.upper_bound(ItemNDCNode::always_less) == data1.end()); + CHECK(data1.upper_bound(ItemNDCNode::always_greater) == data1.end()); + + verify_tree(data0); + verify_tree(data1); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_constructor_range_append_duplicates) + { + InitialDataNDC duplicate_data; + duplicate_data.emplace_back(10, 0); + duplicate_data.emplace_back(5, 1); + duplicate_data.emplace_back(10, 2); + duplicate_data.emplace_back(15, 3); + duplicate_data.emplace_back(10, 4); + duplicate_data.emplace_back(7, 5); + duplicate_data.emplace_back(10, 6); + duplicate_data.emplace_back(12, 7); + + DataNDC0 data0(duplicate_data.begin(), duplicate_data.end(), ItemNDCNode::compare_append_dups); + verify_tree(data0); + CHECK_EQUAL(duplicate_data.size(), data0.size()); + + std::vector> actual_values_idx; + for (auto it = data0.begin(); it != data0.end(); ++it) + { + actual_values_idx.emplace_back(it->data.value, it->data.index); + } + const std::vector> expected_values_idx{ + std::make_pair(5, 1), std::make_pair(7, 5), std::make_pair(10, 0), std::make_pair(10, 2), + std::make_pair(10, 4), std::make_pair(10, 6), std::make_pair(12, 7), std::make_pair(15, 3), + }; + CHECK(actual_values_idx == expected_values_idx); + CHECK_EQUAL(data0.min()->data.index, 1); + CHECK_EQUAL(data0.max()->data.index, 3); + + size_t duplicate_count = 0; + for (auto it = data0.begin(); it != data0.end(); ++it) + { + if (it->data.value == 10) + { + ++duplicate_count; + } + } + CHECK_EQUAL(4U, duplicate_count); + + while (true) + { + auto it = data0.find(ItemNDCNode::CompareByValue{10}); + if (it == data0.end()) + { + break; + } + data0.erase(it); + verify_tree(data0); + } + + std::vector values_after_erase; + for (auto it = data0.begin(); it != data0.end(); ++it) + { + values_after_erase.push_back(it->data.value); + } + const std::vector expected_after_erase{5, 7, 12, 15}; + CHECK(values_after_erase == expected_after_erase); + CHECK(data0.find(ItemNDCNode::CompareByValue{10}) == data0.end()); + CHECK_EQUAL(data0.min()->data.index, 1); + CHECK_EQUAL(data0.max()->data.index, 3); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_constructor_range_prepend_duplicates) + { + InitialDataNDC duplicate_data; + duplicate_data.emplace_back(10, 0); + duplicate_data.emplace_back(7, 1); + duplicate_data.emplace_back(10, 2); + duplicate_data.emplace_back(15, 3); + duplicate_data.emplace_back(10, 4); + duplicate_data.emplace_back(7, 5); + duplicate_data.emplace_back(10, 6); + duplicate_data.emplace_back(12, 7); + + DataNDC0 data0(duplicate_data.begin(), duplicate_data.end(), ItemNDCNode::compare_prepend_dups); + verify_tree(data0); + CHECK_EQUAL(duplicate_data.size(), data0.size()); + + std::vector> actual_values_idx; + for (auto it = data0.begin(); it != data0.end(); ++it) + { + actual_values_idx.emplace_back(it->data.value, it->data.index); + } + const std::vector> expected_values_idx{ + std::make_pair(7, 5), std::make_pair(7, 1), std::make_pair(10, 6), std::make_pair(10, 4), + std::make_pair(10, 2), std::make_pair(10, 0), std::make_pair(12, 7), std::make_pair(15, 3), + }; + CHECK(actual_values_idx == expected_values_idx); + CHECK_EQUAL(data0.min()->data.index, 5); + CHECK_EQUAL(data0.max()->data.index, 3); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_constructor_range_avoid_duplicates) + { + InitialDataNDC duplicate_data; + duplicate_data.emplace_back(10, 0); + duplicate_data.emplace_back(7, 1); + duplicate_data.emplace_back(10, 2); + duplicate_data.emplace_back(15, 3); + duplicate_data.emplace_back(10, 4); + duplicate_data.emplace_back(7, 5); + duplicate_data.emplace_back(10, 6); + duplicate_data.emplace_back(12, 7); + + DataNDC0 data0(duplicate_data.begin(), duplicate_data.end(), ItemNDCNode::compare::cmp); + verify_tree(data0); + + std::vector> actual_values_idx; + for (auto it = data0.begin(); it != data0.end(); ++it) + { + actual_values_idx.emplace_back(it->data.value, it->data.index); + } + const std::vector> expected_values_idx{ + std::make_pair(7, 1), + std::make_pair(10, 0), + std::make_pair(12, 7), + std::make_pair(15, 3), + }; + CHECK(actual_values_idx == expected_values_idx); + CHECK_EQUAL(data0.min()->data.index, 1); + CHECK_EQUAL(data0.max()->data.index, 3); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_constructor_with_bad_iterator) + { + auto action = [this]() + { + // Note end/begin order -> should throw! + DataNDC0 data0(sorted_data.end(), sorted_data.begin(), ItemNDCNode::compare::cmp); + }; + CHECK_THROW(action(), etl::intrusive_avl_tree_iterator_exception); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_constructor_partial_construction_unlinks_inserted_items) + { + InitialDataNDC nodes; + nodes.emplace_back(0, 0); + nodes.emplace_back(1, 1); + nodes.emplace_back(2, 2); + nodes.emplace_back(3, 3); + nodes.emplace_back(4, 4); + + DataNDC0 other_tree; + other_tree.find_or_insert(ItemNDCNode::CompareByValue{2}, [&nodes] { return &nodes[2]; }); + + auto construct_failing_tree = [&]() + { + DataNDC0 data0(nodes.begin(), nodes.end(), ItemNDCNode::compare::cmp); + }; + CHECK_THROW(construct_failing_tree(), etl::intrusive_avl_tree_value_is_already_linked); + + // The foreign tree still owns the conflicting node. + CHECK_EQUAL(1, other_tree.size()); + verify_tree(other_tree); + { + const auto it = other_tree.find(ItemNDCNode::CompareByValue{2}); + CHECK(it.has_value()); + CHECK_EQUAL(&nodes[2], it.get()); + } + + // Previously inserted nodes from the failed construction must have been unlinked. + other_tree.erase(other_tree.find(ItemNDCNode::CompareByValue{2})); + CHECK(other_tree.empty()); + + DataNDC0 recovered(nodes.begin(), nodes.end(), ItemNDCNode::compare::cmp); + CHECK_EQUAL(nodes.size(), recovered.size()); + verify_tree(recovered); + CHECK(std::equal(recovered.begin(), recovered.end(), nodes.begin())); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_empty_begin_end_root) + { + DataNDC0 data0; + + CHECK(data0.begin() == data0.end()); + CHECK(!data0.get_root().has_value()); + + const DataNDC0::const_iterator begin = data0.begin(); + const DataNDC0::const_iterator end = data0.end(); + CHECK(begin == end); + + CHECK(data0.cbegin() == data0.cend()); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_iterator) + { + typedef etl::reverse_iterator rev_it; + + DataNDC0 data0(sorted_data.begin(), sorted_data.end(), ItemNDCNode::compare::cmp); + verify_tree(data0); + + bool are_equal = std::equal(data0.begin(), data0.end(), sorted_data.begin()); + CHECK(are_equal); + + are_equal = std::equal(rev_it(data0.end()), rev_it(data0.begin()), sorted_data.rbegin()); + CHECK(are_equal); + + auto curr = data0.begin(); + CHECK(curr); + CHECK(curr.has_value()); + const auto& front = *curr; + CHECK_EQUAL(&front, curr.get()); + CHECK_EQUAL(front.data.value, sorted_data.front().data.value); + CHECK_EQUAL(curr->data.value, sorted_data.front().data.value); + auto prev = curr++; + CHECK(curr == prev.get_parent()); + CHECK(prev == curr.get_child(false)); + CHECK(curr != data0.begin()); + CHECK(prev == data0.begin()); + CHECK(prev-- == data0.begin()); + CHECK(prev == data0.end()); + + curr = data0.end(); + CHECK(!curr); + CHECK(!curr.has_value()); + CHECK(nullptr == curr.get()); + CHECK(curr-- == data0.end()); + CHECK(curr != data0.end()); + CHECK(curr); + CHECK(curr.has_value()); + CHECK_EQUAL(curr->data.value, sorted_data.back().data.value); + + prev = curr--; + CHECK(curr == prev.get_parent()); + CHECK(prev == curr.get_child(true)); + + curr = data0.get_root(); + CHECK(curr.has_value()); + CHECK_EQUAL(curr->data.value, sorted_data.at(sorted_data.size() / 2).data.value); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_iterator_default) + { + DataNDC0::iterator it; + + CHECK(nullptr == it.get()); + CHECK_EQUAL(false, it.has_value()); + CHECK_EQUAL(false, static_cast(it)); + + CHECK_EQUAL(0, it.get_balance_factor()); + CHECK_EQUAL(false, it.get_parent().has_value()); + CHECK_EQUAL(false, it.get_child(true).has_value()); + CHECK_EQUAL(false, it.get_child(false).has_value()); + + ++it; + CHECK(nullptr == it.get()); + CHECK_EQUAL(false, it.has_value()); + CHECK_EQUAL(false, static_cast(it)); + + --it; + CHECK(nullptr == it.get()); + CHECK_EQUAL(false, it.has_value()); + CHECK_EQUAL(false, static_cast(it)); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_const_iterator) + { + typedef etl::reverse_iterator rev_it; + + const DataNDC0 data0(unsorted_data.begin(), unsorted_data.end(), ItemNDCNode::compare::cmp); + verify_tree(data0); + +#ifdef ETL_AVL_DUMP_GRAPHVIZ + const auto tree_dump = to_graphviz(data0); + std::cout << tree_dump; +#endif + + bool are_equal = std::equal(data0.begin(), data0.end(), sorted_data.begin()); + CHECK(are_equal); + + are_equal = std::equal(rev_it(data0.cend()), rev_it(data0.cbegin()), sorted_data.rbegin()); + CHECK(are_equal); + + auto curr = data0.begin(); + CHECK(curr); + CHECK(curr.has_value()); + const auto& front = *curr; + CHECK_EQUAL(&front, curr.get()); + CHECK_EQUAL(front.data.value, sorted_data.front().data.value); + CHECK_EQUAL(curr->data.value, sorted_data.front().data.value); + auto prev = curr++; + CHECK(curr == prev.get_parent()); + CHECK(prev == curr.get_child(false)); + CHECK(curr != data0.begin()); + CHECK(prev == data0.begin()); + CHECK(prev-- == data0.begin()); + CHECK(prev == data0.end()); + + curr = data0.end(); + CHECK(!curr); + CHECK(!curr.has_value()); + CHECK(nullptr == curr.get()); + CHECK(curr-- == data0.end()); + CHECK(curr); + CHECK(curr.has_value()); + CHECK(curr != data0.end()); + CHECK_EQUAL(curr->data.value, sorted_data.back().data.value); + + prev = curr--; + CHECK(curr == prev.get_parent()); + CHECK(prev == curr.get_child(true)); + + curr = data0.get_root(); + CHECK(curr.has_value()); + CHECK_EQUAL(curr->data.value, sorted_data.at(sorted_data.size() / 2).data.value); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_const_iterator_default) + { + DataNDC0::const_iterator it; + + CHECK(nullptr == it.get()); + CHECK_EQUAL(false, it.has_value()); + CHECK_EQUAL(false, static_cast(it)); + + CHECK_EQUAL(0, it.get_balance_factor()); + CHECK_EQUAL(false, it.get_parent().has_value()); + CHECK_EQUAL(false, it.get_child(true).has_value()); + CHECK_EQUAL(false, it.get_child(false).has_value()); + + ++it; + CHECK(nullptr == it.get()); + CHECK_EQUAL(false, it.has_value()); + CHECK_EQUAL(false, static_cast(it)); + + --it; + CHECK(nullptr == it.get()); + CHECK_EQUAL(false, it.has_value()); + CHECK_EQUAL(false, static_cast(it)); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_iterator_misuse_assertions) + { + DataNDC0 data0(sorted_data.begin(), sorted_data.end(), ItemNDCNode::compare::cmp); + + auto dereference_default_iterator = []() + { + DataNDC0::iterator it; + return (*it).data.value; + }; + CHECK_THROW(dereference_default_iterator(), etl::intrusive_avl_tree_iterator_exception); + + auto arrow_default_iterator = []() + { + DataNDC0::iterator it; + return it->data.value; + }; + CHECK_THROW(arrow_default_iterator(), etl::intrusive_avl_tree_iterator_exception); + + auto dereference_end_iterator = [&]() + { + return (*data0.end()).data.value; + }; + CHECK_THROW(dereference_end_iterator(), etl::intrusive_avl_tree_iterator_exception); + + auto arrow_end_iterator = [&]() + { + return data0.end()->data.value; + }; + CHECK_THROW(arrow_end_iterator(), etl::intrusive_avl_tree_iterator_exception); + + auto erase_default_iterator = [&]() + { + DataNDC0::iterator it; + data0.erase(it); + }; + CHECK_THROW(erase_default_iterator(), etl::intrusive_avl_tree_iterator_exception); + + auto erase_end_iterator = [&]() + { + data0.erase(data0.end()); + }; + CHECK_THROW(erase_end_iterator(), etl::intrusive_avl_tree_iterator_exception); + + // Try foreign tree begin iterator. + { + DataNDC0 data0b; + auto erase_foreign_iterator = [&]() + { + data0b.erase(data0.begin()); + }; + CHECK_THROW(erase_foreign_iterator(), etl::intrusive_avl_tree_iterator_exception); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_find) + { + DataNDC0 data0(sorted_data.begin(), sorted_data.end(), ItemNDCNode::compare::cmp); + + auto iterator = data0.find(ItemNDCNode::always_less); + CHECK(!iterator.has_value()); + CHECK(iterator == data0.end()); + + iterator = data0.find(ItemNDCNode::always_greater); + CHECK(!iterator.has_value()); + CHECK(iterator == data0.end()); + + iterator = data0.find(ItemNDCNode::CompareByValue{5}); + CHECK(iterator.has_value()); + CHECK(iterator != data0.end()); + CHECK_EQUAL(iterator->data, sorted_data[5].data); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_find_const) + { + const DataNDC0 data0(sorted_data.begin(), sorted_data.end(), ItemNDCNode::compare::cmp); + + auto iterator = data0.find(ItemNDCNode::always_less); + CHECK(!iterator.has_value()); + CHECK(iterator == data0.end()); + + iterator = data0.find(ItemNDCNode::always_greater); + CHECK(!iterator.has_value()); + CHECK(iterator == data0.end()); + + iterator = data0.find(ItemNDCNode::CompareByValue{5}); + CHECK(iterator != data0.end()); + CHECK(iterator.has_value()); + CHECK_EQUAL(iterator->data, sorted_data[5].data); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_find_throwing) + { + const DataNDC0 data0(sorted_data.begin(), sorted_data.end(), ItemNDCNode::compare::cmp); + + const ItemNDCNode::CompareByValue throwing_compare{0, etl::exception("13", __FILE__, __LINE__)}; + CHECK_THROW(data0.find(throwing_compare), etl::exception); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_find_or_insert) + { + DataNDC0 data0; + + ItemNDCNode node0a(0, 0); + ItemNDCNode node0b(0, 1); + + // Insert new. + { + CHECK(data0.empty()); + const auto it_mod = data0.find_or_insert(ItemNDCNode::CompareByValue{0}, [&node0a] { return &node0a; }); + CHECK(!data0.empty()); + CHECK_EQUAL(1, data0.size()); + CHECK_EQUAL(data0.min().get(), &node0a); + CHECK_EQUAL(data0.max().get(), &node0a); + verify_tree(data0); + + CHECK(it_mod.second); + CHECK(it_mod.first.has_value()); + CHECK(it_mod.first != data0.end()); + CHECK_EQUAL(&node0a, it_mod.first.get()); + } + + // Find existing. + { + const auto it_mod = data0.find_or_insert(ItemNDCNode::CompareByValue{0}, [&node0b] { return &node0b; }); + + CHECK(!it_mod.second); + CHECK(it_mod.first.has_value()); + CHECK(it_mod.first != data0.end()); + CHECK_EQUAL(&node0a, it_mod.first.get()); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_find_or_insert_unsorted) + { + DataNDC0 data0; + verify_tree(data0); + + for (auto& item : unsorted_data) + { + data0.find_or_insert(ItemNDCNode::CompareByValue{item.data.value}, [&item] { return &item; }); + verify_tree(data0); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_find_or_insert_null_factory) + { + DataNDC0 data0; + + const auto it_mod = data0.find_or_insert(ItemNDCNode::always_greater, [] { return nullptr; }); + CHECK(data0.empty()); + CHECK_EQUAL(0, data0.size()); + verify_tree(data0); + + CHECK(!it_mod.second); + CHECK(!it_mod.first.has_value()); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_find_or_insert_already_linked) + { + DataNDC0 data0; + + ItemNDCNode node0(0, 0); + + auto insert = [&]() + { + return data0.find_or_insert(ItemNDCNode::always_greater, [&node0] { return &node0; }); + }; + + auto it_mod = insert(); + CHECK(!data0.empty()); + CHECK_EQUAL(1, data0.size()); + verify_tree(data0); + + // Insert the same node again -> should throw. + { + CHECK_THROW(insert(), etl::intrusive_avl_tree_value_is_already_linked); + CHECK(!data0.empty()); + CHECK_EQUAL(1, data0.size()); + verify_tree(data0); + } + + // But it's ok to erase it first, and then reinsert. + { + data0.erase(it_mod.first); + CHECK_EQUAL(0, data0.size()); + + it_mod = insert(); + CHECK_EQUAL(1, data0.size()); + + CHECK(it_mod.second); + CHECK(it_mod.first.has_value()); + CHECK(it_mod.first != data0.end()); + CHECK_EQUAL(&node0, it_mod.first.get()); + verify_tree(data0); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_find_or_insert_throwing) + { + DataNDC0 data0(sorted_data.begin(), sorted_data.end(), ItemNDCNode::compare::cmp); + CHECK_EQUAL(sorted_data.size(), data0.size()); + + // Try throwing factory. + { + auto throwing_action = [&]() + { + return data0.find_or_insert(ItemNDCNode::always_greater, []() -> ItemNDCNode* { throw etl::exception("123", __FILE__, __LINE__); }); + }; + CHECK_THROW(throwing_action(), etl::exception); + CHECK_EQUAL(sorted_data.size(), data0.size()); + verify_tree(data0); + CHECK(std::equal(data0.begin(), data0.end(), sorted_data.begin())); + } + + // Try throwing comparator. + { + auto throwing_action = [&]() + { + return data0.find_or_insert([](const ItemNDCNode&) -> int { throw etl::exception("321", __FILE__, __LINE__); }, + []() -> ItemNDCNode* { return nullptr; }); + }; + CHECK_THROW(throwing_action(), etl::exception); + CHECK_EQUAL(sorted_data.size(), data0.size()); + verify_tree(data0); + CHECK(std::equal(data0.begin(), data0.end(), sorted_data.begin())); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_erase) + { + DataNDC0 data0(sorted_data.begin(), sorted_data.end(), ItemNDCNode::compare::cmp); + verify_tree(data0); + + size_t expected_size = sorted_data.size(); + for (const auto& item : unsorted_data) + { + const DataNDC0::iterator it = data0.find(ItemNDCNode::CompareByValue{item.data.value}); + CHECK(it != data0.end()); + + CHECK_EQUAL(expected_size, data0.size()); + const DataNDC0::iterator next_it = data0.erase(it); + CHECK_EQUAL(--expected_size, data0.size()); + verify_tree(data0); + if (next_it != data0.end()) + { + CHECK(next_it.has_value()); + const auto& next = *next_it; + CHECK(item < next); + } + } + CHECK(data0.empty()); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_erase_const_it) + { + DataNDC0 data0(sorted_data.begin(), sorted_data.end(), ItemNDCNode::compare::cmp); + verify_tree(data0); + + size_t expected_size = sorted_data.size(); + for (const auto& item : unsorted_data) + { + const DataNDC0::const_iterator it = data0.find(ItemNDCNode::CompareByValue{item.data.value}); + CHECK(it != data0.end()); + + CHECK_EQUAL(expected_size, data0.size()); + const DataNDC0::iterator next_it = data0.erase(it); + CHECK_EQUAL(--expected_size, data0.size()); + verify_tree(data0); + if (next_it != data0.end()) + { + CHECK(next_it.has_value()); + const auto& next = *next_it; + CHECK(item < next); + } + } + CHECK(data0.empty()); + } + + //************************************************************************* + struct Inserter + { + InitialDataNDC& nodes; + + template + void operator()(Tree& tree, const size_t n) const + { + const ItemNDCNode::CompareByValue comp{static_cast(n)}; + const auto it = tree.find(comp); + if (it == tree.end()) + { + bool factory_was_called = false; + auto it_mod = tree.find_or_insert(comp, + [&] + { + factory_was_called = true; + return &nodes.at(n); + }); + CHECK(it_mod.second); + CHECK(factory_was_called); + CHECK_EQUAL(n, it_mod.first->data.value); + CHECK_EQUAL(n, it_mod.first->data.index); + } + else + { + CHECK_EQUAL(n, it->data.value); + CHECK_EQUAL(n, it->data.index); + auto it_mod = tree.find_or_insert(comp, + [] + { + CHECK_MESSAGE("Should not be called!") + CHECK(false); + return nullptr; + }); + CHECK(it == it_mod.first); + CHECK_FALSE(it_mod.second); + } + } + }; + struct Eraser + { + template + void operator()(Tree& tree, const size_t n) const + { + const ItemNDCNode::CompareByValue comp{static_cast(n)}; + auto it = tree.find(comp); + if (it != tree.end()) + { + CHECK_EQUAL(n, it->data.value); + CHECK_EQUAL(n, it->data.index); + tree.erase(it); + it = tree.find(comp); + CHECK(it == tree.end()); + } + } + }; + TEST_FIXTURE(SetupFixture, test_random_insert_and_erase) + { + // Deliberately seeded with fixed number, so that if it fails then always in the same way. + std::mt19937 mte(123); + + InitialDataNDC nodes; + constexpr size_t N = 256; + for (size_t i = 0; i < N; ++i) + { + nodes.emplace_back(static_cast(i), static_cast(i)); + } + + DataNDC0 data0; + DataNDC1 data1; + + auto makeRandomNumber = [&mte](const size_t n) -> size_t + { + return mte() % n; + }; + const Eraser erase_item; + const Inserter insert_item{nodes}; + + for (size_t i = 0; i < 10000; ++i) + { + const auto number = makeRandomNumber(N); + switch (makeRandomNumber(4)) + { + case 0: erase_item(data0, number); break; + case 1: erase_item(data1, number); break; + case 2: insert_item(data0, number); break; + case 3: insert_item(data1, number); break; + } + + verify_tree(data0); + verify_tree(data1); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_move_item) + { + const DataM data0(sorted_data_moveable.begin(), sorted_data_moveable.end(), ItemMNode::compare); + verify_tree(data0); + + const ItemMNode min_item{std::move(sorted_data_moveable.front())}; + CHECK(min_item.data.valid); + CHECK_FALSE(sorted_data_moveable.front().data.valid); + CHECK(min_item.data.value == 0); + auto it = data0.begin(); + CHECK(it.get() == &min_item); + CHECK_EQUAL(sorted_data_moveable.size(), data0.size()); + verify_tree(data0); + + const ItemMNode max_item{std::move(sorted_data_moveable.back())}; + CHECK(max_item.data.value == static_cast(sorted_data_moveable.size() - 1)); + it = --data0.end(); + CHECK(it.get() == &max_item); + CHECK_EQUAL(sorted_data_moveable.size(), data0.size()); + verify_tree(data0); + + auto item_idx = sorted_data_moveable.size() / 2; + const ItemMNode root_item{std::move(sorted_data_moveable.at(item_idx))}; + CHECK_EQUAL(root_item.data.value, item_idx); + it = data0.get_root(); + CHECK(it.has_value()); + CHECK(it.get() == &root_item); + CHECK_EQUAL(sorted_data_moveable.size(), data0.size()); + verify_tree(data0); + + item_idx = 11; + const ItemMNode item{std::move(sorted_data_moveable.at(item_idx))}; + CHECK(item.data.value == static_cast(item_idx)); + it = data0.find(ItemMNode::CompareByValue{static_cast(item_idx)}); + CHECK(it.get() == &item); + CHECK_EQUAL(sorted_data_moveable.size(), data0.size()); + verify_tree(data0); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_constructor_move) + { + DataM data0a(sorted_data_moveable.begin(), sorted_data_moveable.end(), ItemMNode::compare); + const DataM data0b(std::move(data0a)); + CHECK(data0a.empty()); + CHECK_EQUAL(0, data0a.size()); + CHECK_EQUAL(sorted_data_moveable.size(), data0b.size()); + verify_tree(data0b); + CHECK(std::equal(data0b.begin(), data0b.end(), sorted_data_moveable.begin())); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_constructor_move_empty) + { + DataM data0a; + const DataM data0b(std::move(data0a)); + + CHECK(data0a.empty()); + CHECK_EQUAL(0, data0a.size()); + CHECK(data0b.empty()); + CHECK_EQUAL(0, data0b.size()); + verify_tree(data0a); + verify_tree(data0b); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_constructor_move_single_and_mutate) + { + InitialDataM nodes; + nodes.emplace_back(1); + nodes.emplace_back(2); + + DataM data0a(nodes.begin(), nodes.begin() + 1, ItemMNode::compare); + verify_tree(data0a); + + DataM data0b(std::move(data0a)); + CHECK(data0a.empty()); + CHECK_EQUAL(0, data0a.size()); + CHECK_EQUAL(1, data0b.size()); + verify_tree(data0a); + verify_tree(data0b); + + const auto it_mod = data0b.find_or_insert(ItemMNode::CompareByValue{2}, [&nodes] { return &nodes.back(); }); + CHECK(it_mod.second); + CHECK(it_mod.first.has_value()); + CHECK_EQUAL(2, it_mod.first->data.value); + CHECK_EQUAL(2, data0b.size()); + verify_tree(data0b); + + const std::vector expected_values{1, 2}; + std::vector actual_values; + for (const auto& it : data0b) + { + actual_values.push_back(it.data.value); + } + CHECK(actual_values == expected_values); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_move_assignment_item) + { + DataM data0(sorted_data_moveable.begin(), sorted_data_moveable.end(), ItemMNode::compare); + verify_tree(data0); + + // Replace item 11 with a temp item. + { + ItemMNode replacement(100); + replacement = std::move(sorted_data_moveable.at(11)); + + CHECK(replacement.data.valid); + CHECK_FALSE(sorted_data_moveable.at(11).data.valid); + CHECK_TRUE(replacement.data.valid); + CHECK_EQUAL(11, replacement.data.value); + CHECK_EQUAL(sorted_data_moveable.size(), data0.size()); + CHECK(data0.find(ItemMNode::CompareByValue{11}).get() == &replacement); + verify_tree(data0); + } + CHECK_EQUAL(sorted_data_moveable.size() - 1, data0.size()); + verify_tree(data0); + + // Try to move (item 5) into already linked (item 3). + { + sorted_data_moveable.at(3) = std::move(sorted_data_moveable.at(5)); + CHECK_TRUE(sorted_data_moveable.at(3).data.valid); + CHECK_FALSE(sorted_data_moveable.at(5).data.valid); + CHECK_EQUAL(5, sorted_data_moveable.at(3).data.value); + CHECK_EQUAL(sorted_data_moveable.size() - 2, data0.size()); + verify_tree(data0); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_move_assignment_item_self) + { + DataM data0(sorted_data_moveable.begin(), sorted_data_moveable.end(), ItemMNode::compare); + verify_tree(data0); + + ItemMNode& self = sorted_data_moveable.at(11); + ItemMNode* const self_ptr = &self; + *self_ptr = std::move(self); + + CHECK(self.data.valid); + CHECK_EQUAL(11, self.data.value); + CHECK_EQUAL(sorted_data_moveable.size(), data0.size()); + CHECK(data0.find(ItemMNode::CompareByValue{11}).get() == self_ptr); + verify_tree(data0); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_move_assignment_item_over_linked_target) + { + DataM data0(sorted_data_moveable.begin(), sorted_data_moveable.end(), ItemMNode::compare); + ItemMNode replacement(100); + + const auto inserted = data0.find_or_insert(ItemMNode::CompareByValue{100}, [&replacement] { return &replacement; }); + CHECK(inserted.second); + CHECK_EQUAL(sorted_data_moveable.size() + 1U, data0.size()); + verify_tree(data0); + + replacement = std::move(sorted_data_moveable.at(11)); + + CHECK(replacement.data.valid); + CHECK_FALSE(sorted_data_moveable.at(11).data.valid); + CHECK_EQUAL(11, replacement.data.value); + CHECK_EQUAL(sorted_data_moveable.size(), data0.size()); + CHECK(data0.find(ItemMNode::CompareByValue{11}).get() == &replacement); + CHECK(data0.find(ItemMNode::CompareByValue{100}) == data0.end()); + verify_tree(data0); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_assignment_move) + { + InitialDataM lhs_nodes; + lhs_nodes.emplace_back(40); + lhs_nodes.emplace_back(41); + InitialDataM rhs_nodes; + rhs_nodes.emplace_back(1); + rhs_nodes.emplace_back(2); + rhs_nodes.emplace_back(3); + + DataM data0a(lhs_nodes.begin(), lhs_nodes.end(), ItemMNode::compare); + DataM data0b(rhs_nodes.begin(), rhs_nodes.end(), ItemMNode::compare); + verify_tree(data0a); + verify_tree(data0b); + + data0a = std::move(data0b); + + CHECK(data0b.empty()); + CHECK_EQUAL(0, data0b.size()); + CHECK_EQUAL(rhs_nodes.size(), data0a.size()); + verify_tree(data0a); + verify_tree(data0b); + CHECK(std::equal(data0a.begin(), data0a.end(), rhs_nodes.begin())); + + DataM recovery; + const auto it40 = recovery.find_or_insert(ItemMNode::CompareByValue{40}, [&lhs_nodes] { return &lhs_nodes[0]; }); + const auto it41 = recovery.find_or_insert(ItemMNode::CompareByValue{41}, [&lhs_nodes] { return &lhs_nodes[1]; }); + CHECK(it40.second); + CHECK(it41.second); + CHECK_EQUAL(2, recovery.size()); + verify_tree(recovery); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_assignment_move_self) + { + DataM data0(sorted_data_moveable.begin(), sorted_data_moveable.end(), ItemMNode::compare); + verify_tree(data0); + + DataM* const self_ptr = &data0; + *self_ptr = std::move(data0); + + CHECK_EQUAL(sorted_data_moveable.size(), data0.size()); + verify_tree(data0); + for (size_t i = 0; i < sorted_data_moveable.size(); ++i) + { + CHECK(data0.find(ItemMNode::CompareByValue{static_cast(i)}).get() == &sorted_data_moveable[i]); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_swap_member) + { + InitialDataM lhs_nodes; + lhs_nodes.emplace_back(40); + lhs_nodes.emplace_back(41); + InitialDataM rhs_nodes; + rhs_nodes.emplace_back(1); + rhs_nodes.emplace_back(2); + rhs_nodes.emplace_back(3); + + DataM data0a(lhs_nodes.begin(), lhs_nodes.end(), ItemMNode::compare); + DataM data0b(rhs_nodes.begin(), rhs_nodes.end(), ItemMNode::compare); + verify_tree(data0a); + verify_tree(data0b); + + data0a.swap(data0b); + + CHECK_EQUAL(rhs_nodes.size(), data0a.size()); + CHECK_EQUAL(lhs_nodes.size(), data0b.size()); + verify_tree(data0a); + verify_tree(data0b); + CHECK(std::equal(data0a.begin(), data0a.end(), rhs_nodes.begin())); + CHECK(std::equal(data0b.begin(), data0b.end(), lhs_nodes.begin())); + + // Try also self-swap. + data0a.swap(data0a); + verify_tree(data0a); + CHECK(std::equal(data0a.begin(), data0a.end(), rhs_nodes.begin())); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_swap_adl) + { + InitialDataM lhs_nodes; + lhs_nodes.emplace_back(50); + lhs_nodes.emplace_back(51); + InitialDataM rhs_nodes; + rhs_nodes.emplace_back(5); + rhs_nodes.emplace_back(6); + rhs_nodes.emplace_back(7); + + DataM data0a(lhs_nodes.begin(), lhs_nodes.end(), ItemMNode::compare); + DataM data0b(rhs_nodes.begin(), rhs_nodes.end(), ItemMNode::compare); + verify_tree(data0a); + verify_tree(data0b); + + swap(data0a, data0b); + + CHECK_EQUAL(rhs_nodes.size(), data0a.size()); + CHECK_EQUAL(lhs_nodes.size(), data0b.size()); + verify_tree(data0a); + verify_tree(data0b); + CHECK(std::equal(data0a.begin(), data0a.end(), rhs_nodes.begin())); + CHECK(std::equal(data0b.begin(), data0b.end(), lhs_nodes.begin())); + + // Try also self-swap. + swap(data0a, data0a); + verify_tree(data0a); + CHECK(std::equal(data0a.begin(), data0a.end(), rhs_nodes.begin())); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_clear) + { + DataM data0a(sorted_data_moveable.begin(), sorted_data_moveable.end(), ItemMNode::compare); + CHECK_EQUAL(sorted_data_moveable.size(), data0a.size()); + data0a.clear(); + CHECK(data0a.empty()); + CHECK_EQUAL(0, data0a.size()); + verify_tree(data0a); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, visit_in_order) + { + const std::vector ids{0, 1, 2, 3, 4, 5}; + InitialDataNDC data; + for (const auto idx : ids) + { + data.emplace_back(idx, idx); + } + DataNDC0 data0(data.begin(), data.end(), ItemNDCNode::compare::cmp); + + // In-order, LNR + { + std::vector order; + data0.visit_in_order(false, [&order](ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{0, 1, 2, 3, 4, 5}; + CHECK(order == expected); + } + + // Reverse In-order, RNL + { + std::vector order; + data0.visit_in_order(true, [&order](ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{5, 4, 3, 2, 1, 0}; + CHECK(order == expected); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, visit_in_order_const) + { + const std::vector ids{0, 1, 2, 3, 4, 5}; + InitialDataNDC data; + for (const auto idx : ids) + { + data.emplace_back(idx, idx); + } + const DataNDC0 data0(data.begin(), data.end(), ItemNDCNode::compare::cmp); + + // In-order, LNR + { + std::vector order; + data0.visit_in_order(false, [&order](const ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{0, 1, 2, 3, 4, 5}; + CHECK(order == expected); + } + + // Reverse In-order, RNL + { + std::vector order; + data0.visit_in_order(true, [&order](const ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{5, 4, 3, 2, 1, 0}; + CHECK(order == expected); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, visit_post_order) + { + const std::vector ids{0, 1, 2, 3, 4, 5}; + InitialDataNDC data; + for (const auto idx : ids) + { + data.emplace_back(idx, idx); + } + DataNDC0 data0(data.begin(), data.end(), ItemNDCNode::compare::cmp); + + // Post-order, LRN + { + std::vector order; + data0.visit_post_order(false, [&order](ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{0, 2, 1, 5, 4, 3}; + CHECK(order == expected); + } + + // Reverse post-order, RLN + { + std::vector order; + data0.visit_post_order(true, [&order](ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{5, 4, 2, 0, 1, 3}; + CHECK(order == expected); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, visit_post_order_const) + { + const std::vector ids{0, 1, 2, 3, 4, 5}; + InitialDataNDC data; + for (const auto idx : ids) + { + data.emplace_back(idx, idx); + } + const DataNDC0 data0(data.begin(), data.end(), ItemNDCNode::compare::cmp); + + // Post-order, LRN + { + std::vector order; + data0.visit_post_order(false, [&order](const ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{0, 2, 1, 5, 4, 3}; + CHECK(order == expected); + } + + // Reverse post-order, RLN + { + std::vector order; + data0.visit_post_order(true, [&order](const ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{5, 4, 2, 0, 1, 3}; + CHECK(order == expected); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, visit_pre_order) + { + const std::vector ids{0, 1, 2, 3, 4, 5}; + InitialDataNDC data; + for (const auto idx : ids) + { + data.emplace_back(idx, idx); + } + DataNDC0 data0(data.begin(), data.end(), ItemNDCNode::compare::cmp); + + // Pre-order, NLR + { + std::vector order; + data0.visit_pre_order(false, [&order](ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{3, 1, 0, 2, 4, 5}; + CHECK(order == expected); + } + + // Reverse pre-order, NRL + { + std::vector order; + data0.visit_pre_order(true, [&order](ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{3, 4, 5, 1, 2, 0}; + CHECK(order == expected); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, visit_pre_order_const) + { + const std::vector ids{0, 1, 2, 3, 4, 5}; + InitialDataNDC data; + for (const auto idx : ids) + { + data.emplace_back(idx, idx); + } + const DataNDC0 data0(data.begin(), data.end(), ItemNDCNode::compare::cmp); + + // Pre-order, NLR + { + std::vector order; + data0.visit_pre_order(false, [&order](const ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{3, 1, 0, 2, 4, 5}; + CHECK(order == expected); + } + + // Reverse pre-order, NRL + { + std::vector order; + data0.visit_pre_order(true, [&order](const ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{3, 4, 5, 1, 2, 0}; + CHECK(order == expected); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_lower_upper_bound) + { + // idx 1 5 0 2 4 6 8 9 7 3 + // vals = [7, 7, 10, 10, 10, 10, 10, 10, 12, 15] + InitialDataNDC duplicate_data; + duplicate_data.emplace_back(10, 0); + duplicate_data.emplace_back(7, 1); + duplicate_data.emplace_back(10, 2); + duplicate_data.emplace_back(15, 3); + duplicate_data.emplace_back(10, 4); + duplicate_data.emplace_back(7, 5); + duplicate_data.emplace_back(10, 6); + duplicate_data.emplace_back(12, 7); + duplicate_data.emplace_back(10, 8); + duplicate_data.emplace_back(10, 9); + + DataNDC0 data0(duplicate_data.begin(), duplicate_data.end(), ItemNDCNode::compare_append_dups); + verify_tree(data0); + + CHECK(data0.lower_bound(ItemNDCNode::always_greater) == data0.end()); + CHECK(data0.upper_bound(ItemNDCNode::always_greater) == data0.end()); + CHECK_EQUAL(data0.lower_bound(ItemNDCNode::always_less)->data.index, 1); // 7(1) + CHECK_EQUAL(data0.upper_bound(ItemNDCNode::always_less)->data.index, 1); // 7(1) + + // 0 + CHECK_EQUAL(data0.lower_bound(ItemNDCNode::CompareByValue(0))->data.index, 1); // 7(1) + CHECK_EQUAL(data0.upper_bound(ItemNDCNode::CompareByValue(0))->data.index, 1); // 7(1) + // 7 + CHECK_EQUAL(data0.lower_bound(ItemNDCNode::CompareByValue(7))->data.index, 1); // 7(1) + CHECK_EQUAL(data0.upper_bound(ItemNDCNode::CompareByValue(7))->data.index, 0); // 10(0) + // 8 + CHECK_EQUAL(data0.lower_bound(ItemNDCNode::CompareByValue(8))->data.index, 0); // 10(0) + CHECK_EQUAL(data0.upper_bound(ItemNDCNode::CompareByValue(8))->data.index, 0); // 10(0) + // 10 + const auto lower10 = data0.lower_bound(ItemNDCNode::CompareByValue(10)); + const auto upper10 = data0.upper_bound(ItemNDCNode::CompareByValue(10)); + CHECK_EQUAL(lower10->data.value, 10); // 10(0) + CHECK_EQUAL(lower10->data.index, 0); + CHECK_EQUAL(upper10->data.value, 12); // 12(7) + CHECK_EQUAL(upper10->data.index, 7); + CHECK_EQUAL(std::distance(lower10, upper10), 6); // duplicates of 10s + // 11 + CHECK_EQUAL(data0.lower_bound(ItemNDCNode::CompareByValue(11))->data.index, 7); // 12(7) + CHECK_EQUAL(data0.upper_bound(ItemNDCNode::CompareByValue(11))->data.index, 7); // 12(7) + // 12 + CHECK_EQUAL(data0.lower_bound(ItemNDCNode::CompareByValue(12))->data.index, 7); // 12(7) + CHECK_EQUAL(data0.upper_bound(ItemNDCNode::CompareByValue(12))->data.index, 3); // 15(3) + // 13 + CHECK_EQUAL(data0.lower_bound(ItemNDCNode::CompareByValue(13))->data.index, 3); // 15(3) + CHECK_EQUAL(data0.upper_bound(ItemNDCNode::CompareByValue(13))->data.index, 3); // 15(3) + // 15 + CHECK_EQUAL(data0.lower_bound(ItemNDCNode::CompareByValue(15))->data.index, 3); // 15(3) + CHECK(data0.upper_bound(ItemNDCNode::CompareByValue(15)) == data0.end()); + // 16 + CHECK(data0.lower_bound(ItemNDCNode::CompareByValue(16)) == data0.end()); + CHECK(data0.upper_bound(ItemNDCNode::CompareByValue(16)) == data0.end()); + + // Make sure that `lower_bound` and `upper_bound` work on const tree. + const DataNDC0& const_data0 = data0; + CHECK_EQUAL(const_data0.lower_bound(ItemNDCNode::CompareByValue(12))->data.index, 7); // 12(7) + CHECK_EQUAL(const_data0.upper_bound(ItemNDCNode::CompareByValue(12))->data.index, 3); // 15(3) + } + } + +} // namespace diff --git a/test/vs2022/etl.vcxproj b/test/vs2022/etl.vcxproj index 93ea62c4..fcb9eeae 100644 --- a/test/vs2022/etl.vcxproj +++ b/test/vs2022/etl.vcxproj @@ -3786,6 +3786,7 @@ + @@ -7200,6 +7201,26 @@ true true + + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true true @@ -10640,6 +10661,100 @@ + + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false false diff --git a/test/vs2022/etl.vcxproj.filters b/test/vs2022/etl.vcxproj.filters index 4921e669..566df780 100644 --- a/test/vs2022/etl.vcxproj.filters +++ b/test/vs2022/etl.vcxproj.filters @@ -369,6 +369,9 @@ ETL\Containers + + ETL\Containers + ETL\Containers @@ -1862,6 +1865,9 @@ Tests\Containers + + Tests\Containers + Tests\Containers @@ -3014,6 +3020,9 @@ Tests\Syntax Checks\Source + + Tests\Syntax Checks\Source + Tests\Syntax Checks\Source