From 5a0bad3b2fa4589169f653d9c8bbab8a6e4c1581 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 2 Apr 2024 11:37:50 +0200 Subject: [PATCH] util/IntrusiveTreeSet: new class --- src/util/IntrusiveTreeSet.hxx | 498 +++++++++++++++++++++++++++++ src/util/RedBlackTree.hxx | 356 +++++++++++++++++++++ test/util/TestIntrusiveTreeSet.cxx | 190 +++++++++++ test/util/meson.build | 1 + 4 files changed, 1045 insertions(+) create mode 100644 src/util/IntrusiveTreeSet.hxx create mode 100644 src/util/RedBlackTree.hxx create mode 100644 test/util/TestIntrusiveTreeSet.cxx diff --git a/src/util/IntrusiveTreeSet.hxx b/src/util/IntrusiveTreeSet.hxx new file mode 100644 index 000000000..8320529a3 --- /dev/null +++ b/src/util/IntrusiveTreeSet.hxx @@ -0,0 +1,498 @@ +// SPDX-License-Identifier: BSD-2-Clause +// Copyright CM4all GmbH +// author: Max Kellermann + +#pragma once + +#include "RedBlackTree.hxx" + +#include "Cast.hxx" +#include "Concepts.hxx" +#include "IntrusiveHookMode.hxx" +#include "MemberPointer.hxx" +#include "OptionalCounter.hxx" + +#include +#include // for std::weak_ordering +#include // for std::regular_invocable +#include +#include // for std::exchange() + +struct IntrusiveTreeSetOptions { + bool constant_time_size = false; +}; + +template +class IntrusiveTreeSetHook { + template friend struct IntrusiveTreeSetBaseHookTraits; + template friend struct IntrusiveTreeSetMemberHookTraits; + template friend class IntrusiveTreeSet; + +protected: + RedBlackTreeNode node; + +public: + static constexpr IntrusiveHookMode mode = _mode; + + constexpr IntrusiveTreeSetHook() noexcept { + if constexpr (mode >= IntrusiveHookMode::TRACK) + node.parent = nullptr; + } + + constexpr ~IntrusiveTreeSetHook() noexcept { + if constexpr (mode >= IntrusiveHookMode::AUTO_UNLINK) + if (is_linked()) + unlink(); + } + + IntrusiveTreeSetHook(const IntrusiveTreeSetHook &) = delete; + IntrusiveTreeSetHook &operator=(const IntrusiveTreeSetHook &) = delete; + + constexpr void unlink() noexcept { + if constexpr (mode >= IntrusiveHookMode::TRACK) { + assert(is_linked()); + } + + node.Unlink(); + + if constexpr (mode >= IntrusiveHookMode::TRACK) + node.parent = nullptr; + } + + bool is_linked() const noexcept { + static_assert(mode >= IntrusiveHookMode::TRACK); + + return node.parent != nullptr; + } + +private: + static constexpr auto &Cast(RedBlackTreeNode &node) noexcept { + return ContainerCast(node, &IntrusiveTreeSetHook::node); + } + + static constexpr const auto &Cast(const RedBlackTreeNode &node) noexcept { + return ContainerCast(node, &IntrusiveTreeSetHook::node); + } +}; + +/** + * Detect the hook type. + */ +template +struct IntrusiveTreeSetHookDetection { + /* TODO can this be simplified somehow, without checking for + all possible enum values? */ + using type = std::conditional_t, U>, + IntrusiveTreeSetHook, + std::conditional_t, U>, + IntrusiveTreeSetHook, + std::conditional_t, U>, + IntrusiveTreeSetHook, + void>>>; +}; + +/** + * For classes which embed #IntrusiveTreeSetHook as base class. + */ +template +struct IntrusiveTreeSetBaseHookTraits { + template + using Hook = typename IntrusiveTreeSetHookDetection::type; + + static constexpr T *Cast(RedBlackTreeNode *node) noexcept { + auto *hook = &Hook::Cast(*node); + return static_cast(hook); + } + + static constexpr auto &ToHook(T &t) noexcept { + return static_cast &>(t); + } +}; + +/** + * For classes which embed #IntrusiveTreeSetHook as member. + */ +template +struct IntrusiveTreeSetMemberHookTraits { + using T = MemberPointerContainerType; + using _Hook = MemberPointerType; + + template + using Hook = _Hook; + + static constexpr T *Cast(RedBlackTreeNode *node) noexcept { + auto &hook = Hook::Cast(*node); + return &ContainerCast(hook, member); + } + + static constexpr auto &ToHook(T &t) noexcept { + return t.*member; + } +}; + +/** + * @param GetKey a function object which extracts the "key" part of an + * item + */ +template GetKey=std::identity, + std::regular_invocable, + std::invoke_result_t> Compare=std::compare_three_way> +struct IntrusiveTreeSetOperators { + [[no_unique_address]] + GetKey get_key; + + [[no_unique_address]] + Compare compare; +}; + +/** + * A binary tree implementation which stores pointers to items which + * have an embedded #IntrusiveTreeSetHook. + */ +template, + typename HookTraits=IntrusiveTreeSetBaseHookTraits, + IntrusiveTreeSetOptions options=IntrusiveTreeSetOptions{}> +class IntrusiveTreeSet { + static constexpr bool constant_time_size = options.constant_time_size; + + [[no_unique_address]] + OptionalCounter counter; + + [[no_unique_address]] + Operators ops; + + RedBlackTreeNode head{RedBlackTreeNode::Head{}}; + +public: + using value_type = T; + using reference = T &; + using const_reference = const T &; + using pointer = T *; + using const_pointer = const T *; + using size_type = std::size_t; + + [[nodiscard]] + IntrusiveTreeSet() noexcept = default; + + [[nodiscard]] + constexpr bool empty() const noexcept { + return GetRoot() == nullptr; + } + + [[nodiscard]] + constexpr size_type size() const noexcept { + if constexpr (constant_time_size) + return counter; + else + return std::distance(begin(), end()); + } + + constexpr void clear() noexcept { + SetRoot(nullptr); + counter.reset(); + } + + constexpr void clear_and_dispose(Disposer auto disposer) noexcept { + dispose_all(GetRoot(), disposer); + clear(); + } + + class iterator { + friend IntrusiveTreeSet; + + RedBlackTreeNode *node; + + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = value_type *; + using reference = value_type &; + + explicit constexpr iterator(RedBlackTreeNode *_node) noexcept + :node(_node) {} + + constexpr bool operator==(const iterator &) const noexcept = default; + constexpr bool operator!=(const iterator &) const noexcept = default; + + constexpr reference operator*() const noexcept { + return *Cast(node); + } + + constexpr pointer operator->() const noexcept { + return Cast(node); + } + + constexpr auto &operator++() noexcept { + node = RedBlackTreeNode::GetNextNode(node); + return *this; + } + }; + + [[nodiscard]] + constexpr iterator begin() noexcept { + auto *root = GetRoot(); + return root != nullptr + ? iterator{&root->GetLeftMost()} + : end(); + } + + [[nodiscard]] + constexpr iterator end() noexcept { + return iterator{nullptr}; + } + + class const_iterator { + friend IntrusiveTreeSet; + + const RedBlackTreeNode *node; + + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = const T; + using difference_type = std::ptrdiff_t; + using pointer = value_type *; + using reference = value_type &; + + explicit constexpr const_iterator(RedBlackTreeNode *_node) noexcept + :node(_node) {} + + constexpr const_iterator(iterator i) noexcept + :node(i.node) {} + + constexpr bool operator==(const const_iterator &) const noexcept = default; + constexpr bool operator!=(const const_iterator &) const noexcept = default; + + constexpr reference operator*() const noexcept { + return *Cast(node); + } + + constexpr pointer operator->() const noexcept { + return Cast(node); + } + + constexpr auto &operator++() noexcept { + node = RedBlackTreeNode::GetNextNode(const_cast(node)); + return *this; + } + }; + + [[nodiscard]] + constexpr const_iterator begin() const noexcept { + auto *root = GetRoot(); + return root != nullptr + ? const_iterator{&root->GetLeftMost()} + : end(); + } + + [[nodiscard]] + constexpr const_iterator end() const noexcept { + return const_iterator{nullptr}; + } + + const_reference front() const noexcept { + auto i = begin(); + assert(i != end()); + + return *i; + } + + reference front() noexcept { + auto i = begin(); + assert(i != end()); + + return *i; + } + + [[nodiscard]] + static constexpr iterator iterator_to(reference item) noexcept { + return iterator{&ToNode(item)}; + } + + [[nodiscard]] + constexpr iterator find(const auto &key) const noexcept { + auto *node = GetRoot(); + +#ifndef NDEBUG + bool previous_red = false; +#endif + + while (node != nullptr) { +#ifndef NDEBUG + const bool current_red = node->color == RedBlackTreeNode::Color::RED; + assert(!previous_red || !current_red); + previous_red = current_red; +#endif + + const auto &item = *Cast(node); + + const std::weak_ordering compare_result = ops.compare(key, ops.get_key(item)); + if (compare_result == std::weak_ordering::less) + node = node->GetLeft(); + else if (compare_result == std::weak_ordering::greater) + node = node->GetRight(); + else + break; + } + + return iterator{node}; + } + + constexpr iterator insert(reference value) noexcept { + static_assert(!constant_time_size || + GetHookMode() < IntrusiveHookMode::AUTO_UNLINK, + "Can't use auto-unlink hooks with constant_time_size"); + + auto *root = GetRoot(); + if (root == nullptr) { + root = &ToNode(value); + root->Init(RedBlackTreeNode::Color::BLACK); + } else { + root = &insert(root, value); + } + + SetRoot(root); + + return iterator_to(value); + } + + iterator erase(iterator i) noexcept { + assert(i.node != nullptr); + assert(!empty()); + + auto *next = RedBlackTreeNode::GetNextNode(i.node); + Cast(i.node)->unlink(); + return iterator{next}; + } + + void pop_front() noexcept { + erase(begin()); + } + +private: + [[nodiscard]] + static constexpr auto GetHookMode() noexcept { + return HookTraits::template Hook::mode; + } + + [[nodiscard]] + static constexpr pointer Cast(RedBlackTreeNode *node) noexcept { + return HookTraits::Cast(node); + } + + [[nodiscard]] + static constexpr const_pointer Cast(const RedBlackTreeNode *node) noexcept { + return HookTraits::Cast(const_cast(node)); + } + + [[nodiscard]] + static constexpr auto &ToHook(T &t) noexcept { + return HookTraits::ToHook(t); + } + + [[nodiscard]] + static constexpr const auto &ToHook(const T &t) noexcept { + return HookTraits::ToHook(t); + } + + [[nodiscard]] + static constexpr RedBlackTreeNode &ToNode(T &t) noexcept { + return ToHook(t).node; + } + + [[nodiscard]] + static constexpr const RedBlackTreeNode &ToNode(const T &t) noexcept { + return ToHook(t).node; + } + + [[nodiscard]] + constexpr RedBlackTreeNode *GetRoot() const noexcept { + return head.GetLeft(); + } + + [[nodiscard]] + constexpr bool IsRoot(const RedBlackTreeNode &node) const noexcept { + return &node == GetRoot(); + } + + constexpr void SetRoot(RedBlackTreeNode *root) noexcept { + head.SetChild(RedBlackTreeNode::Direction::LEFT, root); + } + + [[gnu::pure]] + constexpr RedBlackTreeNode::Direction GetInsertDirection(RedBlackTreeNode &parent, + const_reference new_value) const noexcept { + const auto &parent_value = *Cast(&parent); + const std::weak_ordering compare_result = ops.compare(ops.get_key(new_value), ops.get_key(parent_value)); + return compare_result == std::weak_ordering::less + ? RedBlackTreeNode::Direction::LEFT + : RedBlackTreeNode::Direction::RIGHT; + } + + std::optional rotate1, rotate2; + + RedBlackTreeNode &insert(RedBlackTreeNode *base, + reference value) noexcept { + if (base == nullptr) { + auto &node = ToNode(value); + node.Init(RedBlackTreeNode::Color::RED); + return node; + } + + /* the actual insert is here */ + const auto insert_direction = GetInsertDirection(*base, value); + auto &new_child = insert(base->GetChild(insert_direction), value); + base->SetChild(insert_direction, &new_child); + const bool red_red_conflict = !IsRoot(*base) && + base->color == RedBlackTreeNode::Color::RED && + new_child.color == RedBlackTreeNode::Color::RED; + + /* rotate */ + if (rotate1) { + base->SetChild(*rotate1, &base->GetChild(*rotate1)->Rotate(*rotate1)); + rotate1.reset(); + } + + if (rotate2) { + base->color = RedBlackTreeNode::Color::RED; + base = &base->Rotate(*rotate2); + base->color = RedBlackTreeNode::Color::BLACK; + rotate2.reset(); + } + + if (red_red_conflict) { + const auto direction = base->GetDirectionInParent(); + const auto other_direction = RedBlackTreeNode::OtherDirection(direction); + + if (auto *sibling = base->parent->GetChild(other_direction); + sibling != nullptr && + sibling->color == RedBlackTreeNode::Color::RED) { + sibling->color = RedBlackTreeNode::Color::BLACK; + base->color = RedBlackTreeNode::Color::BLACK; + if (!IsRoot(*base->parent)) + base->parent->color = RedBlackTreeNode::Color::RED; + } else if (const auto *other_child = base->GetChild(other_direction); + other_child != nullptr && other_child->color == RedBlackTreeNode::Color::RED) { + rotate1 = direction; + rotate2 = other_direction; + } else if (const auto *child = base->GetChild(direction); + child != nullptr && child->color == RedBlackTreeNode::Color::RED) { + rotate2 = other_direction; + } + } + + return *base; + } + + void dispose_all(RedBlackTreeNode *node, Disposer auto disposer) noexcept { + if (node == nullptr) + return; + + for (auto *i : node->children) + dispose_all(i, disposer); + + disposer(Cast(node)); + } +}; diff --git a/src/util/RedBlackTree.hxx b/src/util/RedBlackTree.hxx new file mode 100644 index 000000000..8cb205a5a --- /dev/null +++ b/src/util/RedBlackTree.hxx @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: BSD-2-Clause +// Copyright CM4all GmbH +// author: Max Kellermann + +#pragma once + +#include // for std::any_of() +#include +#include +#include // for std::exchange() + +struct RedBlackTreeNode { + RedBlackTreeNode *parent; + + enum class Direction : std::size_t { LEFT, RIGHT }; + + std::array children; + + enum class Color { HEAD, BLACK, RED }; + + Color color; + + constexpr RedBlackTreeNode() noexcept = default; + + struct Head {}; + explicit constexpr RedBlackTreeNode(Head) noexcept + :children({}), + color(Color::HEAD) {} + + RedBlackTreeNode(const RedBlackTreeNode &) = delete; + RedBlackTreeNode &operator=(const RedBlackTreeNode &) = delete; + + constexpr void Init(Color _color) noexcept { + children = {}; + color = _color; + } + + [[nodiscard]] + constexpr bool IsHead() const noexcept { + return color == Color::HEAD; + } + + [[nodiscard]] + constexpr bool IsRoot() const noexcept { + assert(!IsHead()); + + return parent->IsHead(); + } + + [[nodiscard]] + static constexpr Direction OtherDirection(Direction direction) noexcept { + return static_cast(static_cast(direction) ^ 1); + } + + [[nodiscard]] + constexpr RedBlackTreeNode *GetChild(Direction direction) const noexcept { + return children[static_cast(direction)]; + } + + [[nodiscard]] + constexpr RedBlackTreeNode *GetLeft() const noexcept { + return GetChild(Direction::LEFT); + } + + [[nodiscard]] + constexpr RedBlackTreeNode *GetRight() const noexcept { + return GetChild(Direction::RIGHT); + } + + [[nodiscard]] + constexpr RedBlackTreeNode *GetOtherChild(Direction direction) const noexcept { + return GetChild(OtherDirection(direction)); + } + + /** + * Set a new child and return the old one. + */ + constexpr auto *SetChild(Direction direction, + RedBlackTreeNode *child) noexcept { + auto *old = std::exchange(children[static_cast(direction)], + child); + if (child != nullptr) + child->parent = this; + + return old; + } + + constexpr auto *SetChild(Direction direction, + RedBlackTreeNode &child) noexcept { + auto *old = std::exchange(children[static_cast(direction)], + &child); + child.parent = this; + + return old; + } + + constexpr auto *SetOtherChild(Direction direction, + RedBlackTreeNode *child) noexcept { + return SetChild(OtherDirection(direction), child); + } + + [[nodiscard]] + constexpr Direction GetChildDirection(const RedBlackTreeNode &child) const noexcept { + assert(child.parent == this); + assert(&child == GetChild(Direction::LEFT) || + &child == GetChild(Direction::RIGHT)); + + return &child == GetChild(Direction::LEFT) + ? Direction::LEFT + : Direction::RIGHT; + } + + constexpr void ReplaceChild(RedBlackTreeNode &old_child, + RedBlackTreeNode *new_child) noexcept { + SetChild(GetChildDirection(old_child), new_child); + } + + constexpr void ReplaceChild(RedBlackTreeNode &old_child, + RedBlackTreeNode &new_child) noexcept { + SetChild(GetChildDirection(old_child), new_child); + } + + [[nodiscard]] + constexpr Direction GetDirectionInParent() const noexcept { + assert(parent != nullptr); + assert(!IsHead()); + + return parent->GetChildDirection(*this); + } + + [[nodiscard]] + auto &Rotate(RedBlackTreeNode::Direction direction) noexcept { + assert(!IsHead()); + + auto *x = GetOtherChild(direction); + assert(x != nullptr); + + auto *y = x->SetChild(direction, this); + SetOtherChild(direction, y); + + return *x; + } + + void RotateInParent(RedBlackTreeNode::Direction direction) noexcept { + assert(parent != nullptr); + assert(!IsHead()); + + auto &p = *parent; + const auto direction_in_parent = p.GetChildDirection(*this); + + auto &new_node = Rotate(direction); + assert(new_node.parent == this); + + assert(p.GetChild(direction_in_parent) == this); + p.SetChild(direction_in_parent, new_node); + } + + [[nodiscard]] + constexpr static RedBlackTreeNode &GetLeftMost(RedBlackTreeNode *node) noexcept { + assert(node != nullptr); + assert(!node->IsHead()); + + while (auto *left = node->GetChild(Direction::LEFT)) { + assert(left->parent == node); + node = left; + } + + return *node; + } + + [[nodiscard]] + constexpr RedBlackTreeNode &GetLeftMost() noexcept { + return GetLeftMost(this); + } + +private: + [[nodiscard]] + constexpr static RedBlackTreeNode *GetLeftHandedParent(RedBlackTreeNode *node) noexcept { + assert(node != nullptr); + assert(!node->IsHead()); + + while (true) { + assert(node->parent != nullptr); + auto &p = *node->parent; + if (p.IsHead()) + return nullptr; + + assert(node->color != RedBlackTreeNode::Color::RED || + p.color != RedBlackTreeNode::Color::RED); + + if (p.GetChildDirection(*node) == Direction::LEFT) + return &p; + + node = &p; + } + } + +public: + [[nodiscard]] + constexpr static RedBlackTreeNode *GetNextNode(RedBlackTreeNode *node) noexcept { + assert(node != nullptr); + assert(!node->IsHead()); + + if (auto *right = node->GetChild(Direction::RIGHT)) { + assert(node->color != RedBlackTreeNode::Color::RED || + right->color != RedBlackTreeNode::Color::RED); + return &right->GetLeftMost(); + } + + assert(node->parent != nullptr); + auto &p = *node->parent; + if (p.IsHead()) + return nullptr; + + if (p.GetChildDirection(*node) == Direction::LEFT) + return &p; + + return GetLeftHandedParent(&p); + } + +private: + [[nodiscard]] + constexpr bool HasTwoChildren() const noexcept { + return children[0] != nullptr && children[1] != nullptr; + } + + constexpr RedBlackTreeNode *GetAnyChild() const noexcept { + return children[children[1] != nullptr]; + } + +public: + constexpr void Unlink() noexcept { + assert(parent != nullptr); + assert(!IsHead()); + + if (HasTwoChildren()) { + /* swap with successor, because it, by + definition, doesn't have two children; the + rest of this method assumes we have exactly + one child or none */ + + auto &right = *GetRight(); + auto &successor = right.GetLeftMost(); + + auto &p = *parent; + const auto direction_in_parent = p.GetChildDirection(*this); + + successor.SetChild(Direction::LEFT, GetLeft()); + SetChild(Direction::LEFT, nullptr); + SetChild(Direction::RIGHT, successor.GetRight()); + + if (&successor == &right) { + assert(successor.parent == this); + + successor.SetChild(Direction::RIGHT, *this); + } else { + assert(successor.parent != this); + + successor.parent->SetChild(Direction::LEFT, *this); + } + + p.SetChild(direction_in_parent, successor); + } else { + /* if there is exactly one child, it must be red */ + assert(GetAnyChild() == nullptr || GetAnyChild()->color == Color::RED); + } + + assert(!HasTwoChildren()); + + auto &p = *parent; + + if (auto *child = GetAnyChild()) { + p.ReplaceChild(*this, *child); + child->color = Color::BLACK; + } else if (IsRoot()) { + p.SetChild(Direction::LEFT, nullptr); + } else { + if (color == Color::BLACK) + FixDoubleBlack(); + + p.ReplaceChild(*this, nullptr); + } + } + +private: + constexpr std::pair GetRedChild() const noexcept { + if (auto *left = GetLeft(); left != nullptr && left->color == Color::RED) + return {Direction::LEFT, left}; + + if (auto *right = GetRight(); right != nullptr && right->color == Color::RED) + return {Direction::RIGHT, right}; + + return {}; + } + + constexpr void FixDoubleBlack() noexcept { + assert(parent != nullptr); + assert(!IsHead()); + assert(color == Color::BLACK); + + if (IsRoot()) + return; + + auto &p = *parent; + const auto direction = p.GetChildDirection(*this); + const auto other_direction = OtherDirection(direction); + auto *const sibling = p.GetChild(other_direction); + + if (sibling == nullptr) { + p.FixDoubleBlack(); + return; + } + + switch (sibling->color) { + case Color::RED: + p.color = Color::RED; + sibling->color = Color::BLACK; + + p.RotateInParent(direction); + FixDoubleBlack(); + break; + + case Color::BLACK: + if (const auto [red_direction, red] = sibling->GetRedChild(); red != nullptr) { + /* at least one red child */ + + if (direction == red_direction) { + red->color = p.color; + sibling->RotateInParent(other_direction); + } else { + red->color = sibling->color; + sibling->color = p.color; + } + + p.RotateInParent(direction); + p.color = Color::BLACK; + } else { + /* no red child (both children are + either black or nullptr) */ + + sibling->color = Color::RED; + if (p.color == Color::BLACK) + p.FixDoubleBlack(); + else + p.color = Color::BLACK; + } + + break; + + case Color::HEAD: + // unreachable + assert(false); + break; + } + } +}; diff --git a/test/util/TestIntrusiveTreeSet.cxx b/test/util/TestIntrusiveTreeSet.cxx new file mode 100644 index 000000000..e2fca6ca7 --- /dev/null +++ b/test/util/TestIntrusiveTreeSet.cxx @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: BSD-2-Clause +// Copyright CM4all GmbH +// author: Max Kellermann + +#include "util/IntrusiveTreeSet.hxx" + +#include + +#include + +namespace { + +struct IntItem final : IntrusiveTreeSetHook { + int value; + + IntItem(int _value) noexcept:value(_value) {} + + struct GetKey { + constexpr int operator()(const IntItem &item) const noexcept { + return item.value; + } + }; +}; + +} // anonymous namespace + +TEST(IntrusiveTreeSet, Basic) +{ + IntItem a{1}, b{2}, c{3}, d{4}, e{5}, f{1}; + + IntrusiveTreeSet> set; + + EXPECT_EQ(set.size(), 0U); + EXPECT_TRUE(set.empty()); + + EXPECT_FALSE(a.is_linked()); + EXPECT_FALSE(b.is_linked()); + + set.insert(b); + + EXPECT_FALSE(a.is_linked()); + EXPECT_TRUE(b.is_linked()); + + EXPECT_EQ(set.size(), 1U); + EXPECT_EQ(set.find(2), set.iterator_to(b)); + EXPECT_EQ(&set.front(), &b); + + set.insert(a); + EXPECT_EQ(&set.front(), &a); + + EXPECT_TRUE(a.is_linked()); + EXPECT_TRUE(b.is_linked()); + + set.insert(c); + EXPECT_EQ(&set.front(), &a); + + EXPECT_EQ(set.size(), 3U); + + EXPECT_NE(set.find(3), set.end()); + EXPECT_EQ(set.find(3), set.iterator_to(c)); + + EXPECT_EQ(set.find(4), set.end()); + + EXPECT_TRUE(c.is_linked()); + + set.erase(set.iterator_to(c)); + + EXPECT_FALSE(c.is_linked()); + + EXPECT_EQ(set.size(), 2U); + EXPECT_EQ(set.find(3), set.end()); + EXPECT_EQ(&set.front(), &a); + + set.insert(c); + set.insert(d); + set.insert(e); + + EXPECT_EQ(set.size(), 5U); + EXPECT_EQ(&set.front(), &a); + + EXPECT_EQ(set.find(1), set.iterator_to(a)); + EXPECT_EQ(set.find(2), set.iterator_to(b)); + EXPECT_EQ(set.find(3), set.iterator_to(c)); + EXPECT_EQ(set.find(4), set.iterator_to(d)); + EXPECT_EQ(set.find(5), set.iterator_to(e)); + + EXPECT_TRUE(a.is_linked()); + EXPECT_FALSE(f.is_linked()); + + set.erase(set.iterator_to(a)); + EXPECT_FALSE(a.is_linked()); + EXPECT_FALSE(f.is_linked()); + EXPECT_EQ(set.find(1), set.end()); + EXPECT_EQ(set.size(), 4U); + EXPECT_EQ(&set.front(), &b); + + set.insert(f); + EXPECT_FALSE(a.is_linked()); + EXPECT_TRUE(f.is_linked()); + EXPECT_EQ(set.find(1), set.iterator_to(f)); + EXPECT_EQ(set.size(), 5U); + EXPECT_EQ(&set.front(), &f); + + set.pop_front(); + EXPECT_FALSE(f.is_linked()); + + set.clear_and_dispose([](auto *i){ i->value = -1; }); + + EXPECT_EQ(set.size(), 0U); + EXPECT_TRUE(set.empty()); + + EXPECT_EQ(a.value, 1); + EXPECT_EQ(b.value, -1); + EXPECT_EQ(c.value, -1); + EXPECT_EQ(d.value, -1); + EXPECT_EQ(e.value, -1); + EXPECT_EQ(f.value, 1); +} + +template +static constexpr auto +MakeIntItems(std::integer_sequence) noexcept + -> std::array +{ + return {values...}; +} + +TEST(IntrusiveTreeSet, RandomOrder) +{ + auto items = MakeIntItems(std::make_integer_sequence()); + + IntrusiveTreeSet> set; + + set.insert(items[0]); + set.insert(items[5]); + set.insert(items[10]); + set.insert(items[15]); + set.insert(items[20]); + set.insert(items[25]); + set.insert(items[30]); + set.insert(items[1]); + set.insert(items[2]); + set.insert(items[3]); + set.insert(items[31]); + set.insert(items[4]); + set.insert(items[6]); + set.insert(items[7]); + set.insert(items[21]); + set.insert(items[22]); + set.insert(items[23]); + set.insert(items[24]); + set.insert(items[26]); + set.insert(items[8]); + set.insert(items[9]); + set.insert(items[11]); + set.insert(items[12]); + set.insert(items[13]); + set.insert(items[14]); + set.insert(items[27]); + set.insert(items[28]); + set.insert(items[29]); + set.insert(items[16]); + set.insert(items[17]); + set.insert(items[18]); + set.insert(items[19]); + + EXPECT_EQ(set.size(), items.size()); + + for (const auto &i : items) { + EXPECT_TRUE(i.is_linked()); + } + + int expected = 0; + for (const auto &i : set) { + EXPECT_EQ(i.value, expected++); + } + + for (std::size_t remove = 0; remove < items.size(); ++remove) { + EXPECT_TRUE(items[remove].is_linked()); + set.pop_front(); + EXPECT_FALSE(items[remove].is_linked()); + + expected = remove + 1; + for (const auto &i : set) { + EXPECT_EQ(i.value, expected++); + } + } +} diff --git a/test/util/meson.build b/test/util/meson.build index 26ca080d6..b0623dbcf 100644 --- a/test/util/meson.build +++ b/test/util/meson.build @@ -8,6 +8,7 @@ test( 'TestIntrusiveForwardList.cxx', 'TestIntrusiveHashSet.cxx', 'TestIntrusiveList.cxx', + 'TestIntrusiveTreeSet.cxx', 'TestMimeType.cxx', 'TestRingBuffer.cxx', 'TestSplitString.cxx',