diff --git a/AK/IntrusiveRedBlackTree.h b/AK/IntrusiveRedBlackTree.h new file mode 100644 index 0000000000..e4513471f6 --- /dev/null +++ b/AK/IntrusiveRedBlackTree.h @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2021, Idan Horowitz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include + +namespace AK { + +template +class IntrusiveRedBlackTreeNode; + +template V::*member> +class IntrusiveRedBlackTree : public BaseRedBlackTree { +public: + IntrusiveRedBlackTree() = default; + virtual ~IntrusiveRedBlackTree() override + { + clear(); + } + + using BaseTree = BaseRedBlackTree; + using TreeNode = IntrusiveRedBlackTreeNode; + + V* find(K key) + { + auto* node = static_cast(BaseTree::find(this->m_root, key)); + if (!node) + return nullptr; + return node_to_value(*node); + } + + V* find_largest_not_above(K key) + { + auto* node = static_cast(BaseTree::find_largest_not_above(this->m_root, key)); + if (!node) + return nullptr; + return node_to_value(*node); + } + + void insert(V& value) + { + auto& node = value.*member; + BaseTree::insert(&node); + node.m_in_tree = true; + } + + template + class BaseIterator { + public: + BaseIterator() = default; + bool operator!=(const BaseIterator& other) const { return m_node != other.m_node; } + BaseIterator& operator++() + { + if (!m_node) + return *this; + m_prev = m_node; + // the complexity is O(logn) for each successor call, but the total complexity for all elements comes out to O(n), meaning the amortized cost for a single call is O(1) + m_node = static_cast(BaseTree::successor(m_node)); + return *this; + } + BaseIterator& operator--() + { + if (!m_prev) + return *this; + m_node = m_prev; + m_prev = static_cast(BaseTree::predecessor(m_prev)); + return *this; + } + ElementType& operator*() + { + VERIFY(m_node); + return *node_to_value(*m_node); + } + ElementType* operator->() + { + VERIFY(m_node); + return node_to_value(*m_node); + } + [[nodiscard]] bool is_end() const { return !m_node; } + [[nodiscard]] bool is_begin() const { return !m_prev; } + + private: + friend class IntrusiveRedBlackTree; + explicit BaseIterator(TreeNode* node, TreeNode* prev = nullptr) + : m_node(node) + , m_prev(prev) + { + } + TreeNode* m_node { nullptr }; + TreeNode* m_prev { nullptr }; + }; + + using Iterator = BaseIterator; + Iterator begin() { return Iterator(static_cast(this->m_minimum)); } + Iterator end() { return {}; } + Iterator begin_from(K key) { return Iterator(static_cast(BaseTree::find(this->m_root, key))); } + + using ConstIterator = BaseIterator; + ConstIterator begin() const { return ConstIterator(static_cast(this->m_minimum)); } + ConstIterator end() const { return {}; } + ConstIterator begin_from(K key) const { return ConstIterator(static_cast(BaseTree::find(this->m_rootF, key))); } + + bool remove(K key) + { + auto* node = static_cast(BaseTree::find(this->m_root, key)); + if (!node) + return false; + + BaseTree::remove(node); + + node->right_child = nullptr; + node->left_child = nullptr; + node->m_in_tree = false; + + return true; + } + + void clear() + { + clear_nodes(static_cast(this->m_root)); + this->m_root = nullptr; + this->m_minimum = nullptr; + this->m_size = 0; + } + +private: + static void clear_nodes(TreeNode* node) + { + if (!node) + return; + clear_nodes(static_cast(node->right_child)); + node->right_child = nullptr; + clear_nodes(static_cast(node->left_child)); + node->left_child = nullptr; + node->m_in_tree = false; + } + + static V* node_to_value(TreeNode& node) + { + return (V*)((u8*)&node - ((u8*)&(((V*)nullptr)->*member) - (u8*)nullptr)); + } +}; + +template +class IntrusiveRedBlackTreeNode : public BaseRedBlackTree::Node { +public: + IntrusiveRedBlackTreeNode(K key) + : BaseRedBlackTree::Node(key) + { + } + + ~IntrusiveRedBlackTreeNode() + { + VERIFY(!is_in_tree()); + } + + bool is_in_tree() + { + return m_in_tree; + } + +private: + template V::*member> + friend class IntrusiveRedBlackTree; + bool m_in_tree { false }; +}; + +} + +using AK::IntrusiveRedBlackTree; +using AK::IntrusiveRedBlackTreeNode; diff --git a/AK/Tests/CMakeLists.txt b/AK/Tests/CMakeLists.txt index b12c459907..6948c50875 100644 --- a/AK/Tests/CMakeLists.txt +++ b/AK/Tests/CMakeLists.txt @@ -28,6 +28,7 @@ set(AK_TEST_SOURCES TestHashTable.cpp TestIPv4Address.cpp TestIndexSequence.cpp + TestIntrusiveRedBlackTree.cpp TestJSON.cpp TestLexicalPath.cpp TestMACAddress.cpp diff --git a/AK/Tests/TestIntrusiveRedBlackTree.cpp b/AK/Tests/TestIntrusiveRedBlackTree.cpp new file mode 100644 index 0000000000..4dccd9c28e --- /dev/null +++ b/AK/Tests/TestIntrusiveRedBlackTree.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2021, Idan Horowitz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include + +class IntrusiveTest { +public: + IntrusiveTest(int key, int value) + : m_tree_node(key) + , m_some_value(value) + { + } + + IntrusiveRedBlackTreeNode m_tree_node; + int m_some_value; +}; + +TEST_CASE(construct) +{ + IntrusiveRedBlackTree empty; + EXPECT(empty.is_empty()); + EXPECT(empty.size() == 0); +} + +TEST_CASE(ints) +{ + IntrusiveRedBlackTree test; + IntrusiveTest first { 1, 10 }; + test.insert(first); + IntrusiveTest second { 3, 20 }; + test.insert(second); + IntrusiveTest third { 2, 30 }; + test.insert(third); + EXPECT_EQ(test.size(), 3u); + EXPECT_EQ(test.find(3)->m_some_value, 20); + EXPECT_EQ(test.find(2)->m_some_value, 30); + EXPECT_EQ(test.find(1)->m_some_value, 10); + EXPECT(!test.remove(4)); + EXPECT(test.remove(2)); + EXPECT(test.remove(1)); + EXPECT(test.remove(3)); + EXPECT_EQ(test.size(), 0u); +} + +TEST_CASE(largest_smaller_than) +{ + IntrusiveRedBlackTree test; + IntrusiveTest first { 1, 10 }; + test.insert(first); + IntrusiveTest second { 11, 20 }; + test.insert(second); + IntrusiveTest third { 21, 30 }; + test.insert(third); + EXPECT_EQ(test.size(), 3u); + EXPECT_EQ(test.find_largest_not_above(3)->m_some_value, 10); + EXPECT_EQ(test.find_largest_not_above(17)->m_some_value, 20); + EXPECT_EQ(test.find_largest_not_above(22)->m_some_value, 30); + EXPECT_EQ(test.find_largest_not_above(-5), nullptr); + VERIFY(test.remove(1)); + VERIFY(test.remove(11)); + VERIFY(test.remove(21)); +} + +TEST_CASE(key_ordered_iteration) +{ + constexpr auto amount = 10000; + IntrusiveRedBlackTree test; + NonnullOwnPtrVector m_entries; + Array keys {}; + + // generate random key order + for (int i = 0; i < amount; i++) { + keys[i] = i; + } + for (size_t i = 0; i < amount; i++) { + swap(keys[i], keys[get_random() % amount]); + } + + // insert random keys + for (size_t i = 0; i < amount; i++) { + auto entry = make(keys[i], keys[i]); + test.insert(*entry); + m_entries.append(move(entry)); + } + + // check key-ordered iteration + int index = 0; + for (auto& value : test) { + EXPECT(value.m_some_value == index++); + } + + // ensure we can remove all of them (aka, tree structure is not destroyed somehow) + for (size_t i = 0; i < amount; i++) { + EXPECT(test.remove(i)); + } +} + +TEST_CASE(clear) +{ + IntrusiveRedBlackTree test; + NonnullOwnPtrVector m_entries; + for (size_t i = 0; i < 1000; i++) { + auto entry = make(i, i); + test.insert(*entry); + m_entries.append(move(entry)); + } + test.clear(); + EXPECT_EQ(test.size(), 0u); +} + +TEST_MAIN(RedBlackTree)