util/IntrusiveTreeSet: new class
This commit is contained in:
parent
6a99f20828
commit
5a0bad3b2f
|
@ -0,0 +1,498 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright CM4all GmbH
|
||||
// author: Max Kellermann <mk@cm4all.com>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RedBlackTree.hxx"
|
||||
|
||||
#include "Cast.hxx"
|
||||
#include "Concepts.hxx"
|
||||
#include "IntrusiveHookMode.hxx"
|
||||
#include "MemberPointer.hxx"
|
||||
#include "OptionalCounter.hxx"
|
||||
|
||||
#include <cassert>
|
||||
#include <compare> // for std::weak_ordering
|
||||
#include <concepts> // for std::regular_invocable
|
||||
#include <optional>
|
||||
#include <utility> // for std::exchange()
|
||||
|
||||
struct IntrusiveTreeSetOptions {
|
||||
bool constant_time_size = false;
|
||||
};
|
||||
|
||||
template<IntrusiveHookMode _mode=IntrusiveHookMode::NORMAL>
|
||||
class IntrusiveTreeSetHook {
|
||||
template<typename T> friend struct IntrusiveTreeSetBaseHookTraits;
|
||||
template<auto member> friend struct IntrusiveTreeSetMemberHookTraits;
|
||||
template<typename T, typename Operators, typename HookTraits, IntrusiveTreeSetOptions> 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<typename U>
|
||||
struct IntrusiveTreeSetHookDetection {
|
||||
/* TODO can this be simplified somehow, without checking for
|
||||
all possible enum values? */
|
||||
using type = std::conditional_t<std::is_base_of_v<IntrusiveTreeSetHook<IntrusiveHookMode::NORMAL>, U>,
|
||||
IntrusiveTreeSetHook<IntrusiveHookMode::NORMAL>,
|
||||
std::conditional_t<std::is_base_of_v<IntrusiveTreeSetHook<IntrusiveHookMode::TRACK>, U>,
|
||||
IntrusiveTreeSetHook<IntrusiveHookMode::TRACK>,
|
||||
std::conditional_t<std::is_base_of_v<IntrusiveTreeSetHook<IntrusiveHookMode::AUTO_UNLINK>, U>,
|
||||
IntrusiveTreeSetHook<IntrusiveHookMode::AUTO_UNLINK>,
|
||||
void>>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* For classes which embed #IntrusiveTreeSetHook as base class.
|
||||
*/
|
||||
template<typename T>
|
||||
struct IntrusiveTreeSetBaseHookTraits {
|
||||
template<typename U>
|
||||
using Hook = typename IntrusiveTreeSetHookDetection<U>::type;
|
||||
|
||||
static constexpr T *Cast(RedBlackTreeNode *node) noexcept {
|
||||
auto *hook = &Hook<T>::Cast(*node);
|
||||
return static_cast<T *>(hook);
|
||||
}
|
||||
|
||||
static constexpr auto &ToHook(T &t) noexcept {
|
||||
return static_cast<Hook<T> &>(t);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* For classes which embed #IntrusiveTreeSetHook as member.
|
||||
*/
|
||||
template<auto member>
|
||||
struct IntrusiveTreeSetMemberHookTraits {
|
||||
using T = MemberPointerContainerType<decltype(member)>;
|
||||
using _Hook = MemberPointerType<decltype(member)>;
|
||||
|
||||
template<typename Dummy>
|
||||
using Hook = _Hook;
|
||||
|
||||
static constexpr T *Cast(RedBlackTreeNode *node) noexcept {
|
||||
auto &hook = Hook<T>::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<typename T,
|
||||
std::regular_invocable<const T &> GetKey=std::identity,
|
||||
std::regular_invocable<std::invoke_result_t<GetKey, const T &>,
|
||||
std::invoke_result_t<GetKey, const 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 T,
|
||||
typename Operators=IntrusiveTreeSetOperators<T>,
|
||||
typename HookTraits=IntrusiveTreeSetBaseHookTraits<T>,
|
||||
IntrusiveTreeSetOptions options=IntrusiveTreeSetOptions{}>
|
||||
class IntrusiveTreeSet {
|
||||
static constexpr bool constant_time_size = options.constant_time_size;
|
||||
|
||||
[[no_unique_address]]
|
||||
OptionalCounter<constant_time_size> 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<value_type> 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<RedBlackTreeNode *>(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<T>::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<RedBlackTreeNode *>(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<RedBlackTreeNode::Direction> 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<value_type> auto disposer) noexcept {
|
||||
if (node == nullptr)
|
||||
return;
|
||||
|
||||
for (auto *i : node->children)
|
||||
dispose_all(i, disposer);
|
||||
|
||||
disposer(Cast(node));
|
||||
}
|
||||
};
|
|
@ -0,0 +1,356 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright CM4all GmbH
|
||||
// author: Max Kellermann <mk@cm4all.com>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // for std::any_of()
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <utility> // for std::exchange()
|
||||
|
||||
struct RedBlackTreeNode {
|
||||
RedBlackTreeNode *parent;
|
||||
|
||||
enum class Direction : std::size_t { LEFT, RIGHT };
|
||||
|
||||
std::array<RedBlackTreeNode *, 2> 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<Direction>(static_cast<std::size_t>(direction) ^ 1);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr RedBlackTreeNode *GetChild(Direction direction) const noexcept {
|
||||
return children[static_cast<std::size_t>(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<std::size_t>(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<std::size_t>(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<Direction, RedBlackTreeNode *> 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;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,190 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
// Copyright CM4all GmbH
|
||||
// author: Max Kellermann <mk@cm4all.com>
|
||||
|
||||
#include "util/IntrusiveTreeSet.hxx"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
struct IntItem final : IntrusiveTreeSetHook<IntrusiveHookMode::TRACK> {
|
||||
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<IntItem,
|
||||
IntrusiveTreeSetOperators<IntItem, IntItem::GetKey>> 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<int... values>
|
||||
static constexpr auto
|
||||
MakeIntItems(std::integer_sequence<int, values...>) noexcept
|
||||
-> std::array<IntItem, sizeof...(values)>
|
||||
{
|
||||
return {values...};
|
||||
}
|
||||
|
||||
TEST(IntrusiveTreeSet, RandomOrder)
|
||||
{
|
||||
auto items = MakeIntItems(std::make_integer_sequence<int, 32>());
|
||||
|
||||
IntrusiveTreeSet<IntItem,
|
||||
IntrusiveTreeSetOperators<IntItem, IntItem::GetKey>> 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++);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ test(
|
|||
'TestIntrusiveForwardList.cxx',
|
||||
'TestIntrusiveHashSet.cxx',
|
||||
'TestIntrusiveList.cxx',
|
||||
'TestIntrusiveTreeSet.cxx',
|
||||
'TestMimeType.cxx',
|
||||
'TestRingBuffer.cxx',
|
||||
'TestSplitString.cxx',
|
||||
|
|
Loading…
Reference in New Issue