From 236fddd3fdc828298f2ad8fc21bc2d21321b0a71 Mon Sep 17 00:00:00 2001 From: agent Date: Tue, 27 Oct 2026 02:16:18 +0300 Subject: [PATCH 1/3] Containers: AVL tree ordered inserts, removal by node and neighbour lookup insertAfter() places a node next to a known predecessor, removeNode() removes a node the caller already holds, refreshNode() rebalances after a key changed in place, and next()/prev() walk the tree in order. findSubTree() now descends with the transformed query instead of the original key. --- Containers/private/AVLTree.ipp | 88 ++++++++++++++++++++++++++++------ Containers/public/Tree.hpp | 7 +++ 2 files changed, 81 insertions(+), 14 deletions(-) diff --git a/Containers/private/AVLTree.ipp b/Containers/private/AVLTree.ipp index 40a64d84..9b5446e4 100644 --- a/Containers/private/AVLTree.ipp +++ b/Containers/private/AVLTree.ipp @@ -45,20 +45,56 @@ void tp::AvlTree::insert(KeyArg key, DataArg data) { } } +template +auto tp::AvlTree::insertAfter(Node* predecessor, KeyArg key, DataArg data) -> Node* { + Node* inserted = newNode(key, data); + + if (mRoot) { + attachAfter(predecessor, inserted); + } + + restoreInvariants(inserted); + return inserted; +} + +template +void tp::AvlTree::attachAfter(Node* predecessor, Node* inserted) { + if (!predecessor) { + Node* first = minNode(mRoot); + first->mLeft = inserted; + inserted->mParent = first; + return; + } + + if (predecessor->mRight == &gNullNode) { + predecessor->mRight = inserted; + inserted->mParent = predecessor; + return; + } + + Node* successor = minNode(predecessor->mRight); + successor->mLeft = inserted; + inserted->mParent = successor; +} + +template +void tp::AvlTree::refreshNode(Node* node) { + restoreInvariants(node); +} + template void tp::AvlTree::remove(KeyArg key) { + if (auto node = findSubTree(mRoot, key)) { + removeNode(node); + } +} - auto node = findSubTree(mRoot, key); - - if (!node) return; +template +void tp::AvlTree::removeNode(Node* node) { + DEBUG_ASSERT(node); if (node->mRight != &gNullNode && node->mLeft != &gNullNode) { - Node* min = minNode(node->mRight); - injectNodeInstead(node, min); - std::swap(node, min); - // auto const& newKey = min->key.getFindKey(head->mRight); - node = findSubTree(node->mRight, key); - DEBUG_ASSERT(node); + injectNodeInstead(node, minNode(node->mRight)); } if (node->mRight != &gNullNode) { @@ -115,6 +151,28 @@ auto tp::AvlTree::minNode(Node* head) const -> Node* { return head; } +template +auto tp::AvlTree::next(Node* node) const -> Node* { + if (node->mRight != &gNullNode) return minNode(node->mRight); + + while (node->mParent && node->mParent->mRight == node) { + node = node->mParent; + } + + return node->mParent; +} + +template +auto tp::AvlTree::prev(Node* node) const -> Node* { + if (node->mLeft != &gNullNode) return maxNode(node->mLeft); + + while (node->mParent && node->mParent->mLeft == node) { + node = node->mParent; + } + + return node->mParent; +} + template auto tp::AvlTree::find(KeyArg key) const -> Node* { return findSubTree(mRoot, key); @@ -122,14 +180,16 @@ auto tp::AvlTree::find(KeyArg key) const -> Node* { template auto tp::AvlTree::findSubTree(Node* iter, KeyArg key) const -> Node* { + Key query = key; + while (true) { - if (iter == &gNullNode) return nullptr; - if (iter->key.exactNode(key)) return iter; - if (iter->key.descentRight(key)) { - // key = iter->key.keyInRightSubtree(key); + if (!iter || iter == &gNullNode) return nullptr; + if (iter->key.exactNode(query)) return iter; + if (iter->key.descentRight(query)) { + query = iter->key.keyInRightSubtree(query); iter = iter->mRight; } else { - // key = iter->key.keyInLeftSubtree(key); + query = iter->key.keyInLeftSubtree(query); iter = iter->mLeft; } } diff --git a/Containers/public/Tree.hpp b/Containers/public/Tree.hpp index 9cf702cc..3c6fb908 100644 --- a/Containers/public/Tree.hpp +++ b/Containers/public/Tree.hpp @@ -65,12 +65,18 @@ namespace tp { Node* head() const; void insert(KeyArg key, DataArg data); + Node* insertAfter(Node* predecessor, KeyArg key, DataArg data); void remove(KeyArg key); + void removeNode(Node* node); void removeAll(); + void refreshNode(Node* node); Node* maxNode(Node* head) const; Node* minNode(Node* head) const; + Node* next(Node* node) const; + Node* prev(Node* node) const; + Node* find(KeyArg key) const; Node* findSubTree(Node* iter, KeyArg key) const; Node* findLessOrEq(KeyArg key) const; @@ -97,6 +103,7 @@ namespace tp { inline auto insertNode(Node* head, KeyArg key, DataArg data) -> Node*; inline auto findInsertParent(Node* head, KeyArg key) -> Node*; + inline void attachAfter(Node* predecessor, Node* inserted); inline void injectNodeInstead(Node* target, Node* from); -- 2.49.1 From 13967ea5c68373a69a9989226ae6b412a7916287 Mon Sep 17 00:00:00 2001 From: agent Date: Tue, 27 Oct 2026 02:16:18 +0300 Subject: [PATCH 2/3] Containers: map reuses deleted slots and bounds the tombstone load The table tracks deleted slots: probing hands a tombstone back instead of walking past it, the load factor counts tombstones as well as entries, and a rehash also runs when the live entries become sparse. The tests cover reinsert after erase and the growth policy. --- Containers/public/Map.hpp | 34 ++++++++++++++++++++-------- Containers/tests/MapTest.cpp | 44 +++++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 10 deletions(-) diff --git a/Containers/public/Map.hpp b/Containers/public/Map.hpp index df1c5d83..edeabe1e 100644 --- a/Containers/public/Map.hpp +++ b/Containers/public/Map.hpp @@ -50,10 +50,11 @@ namespace tp { tAllocator mAlloc; Node** mTable = nullptr; ualni mNSlots = 0; + ualni mNDeleted = 0; ualni mNEntries = 0; private: - constexpr halnf maxLoadFactor() { return halnf(MAP_MAX_LOAD_PERCENTAGE) / 100.f; } + static constexpr halnf maxLoadFactor() { return halnf(MAP_MAX_LOAD_PERCENTAGE) / 100.f; } inline Node** newTable(const ualni len) { return new (mAlloc.allocate(sizeof(Node*) * len)) Node*[len](); } @@ -79,6 +80,7 @@ namespace tp { mNSlots = next2pow((uhalni) ((1.f / (maxLoadFactor())) * mNEntries + 1)); mTable = newTable(mNSlots); mNEntries = 0; + mNDeleted = 0; for (alni i = 0; i < nSlotsOld; i++) { if (!tableOld[i] || isDeletedNode(tableOld[i])) { @@ -142,27 +144,37 @@ namespace tp { ualni const mask = mNSlots - 1; ualni const shift = (hashed_key >> MAP_PERTURB_SHIFT) & ~1; ualni idx = hashed_key & mask; + alni reusable = -1; NEXT: - if (isDeletedNode(mTable[idx]) || !mTable[idx]) { - return idx; + if (!mTable[idx]) { + return reusable >= 0 ? (ualni) reusable : idx; } - if (mTable[idx]->key == key) { + if (isDeletedNode(mTable[idx])) { + if (reusable < 0) reusable = (alni) idx; + } else if (mTable[idx]->key == key) { return idx; } idx = ((5 * idx) + 1 + shift) & mask; goto NEXT; } + void occupy(ualni idx) { + if (isDeletedNode(mTable[idx])) mNDeleted--; + mNEntries++; + } + + [[nodiscard]] bool overloaded() const { return (halnf) (mNEntries + mNDeleted) / mNSlots > maxLoadFactor(); } + void put(Node* node) { const ualni idx = findSlotWrite(node->key); if (!mTable[idx] || isDeletedNode(mTable[idx])) { - mNEntries++; + occupy(idx); } mTable[idx] = node; - if ((halnf) mNEntries / mNSlots > maxLoadFactor()) { + if (overloaded()) { rehash(); } } @@ -186,12 +198,12 @@ namespace tp { void put(KeyArg key, ValArg val) { const ualni idx = findSlotWrite(key); if (!mTable[idx] || isDeletedNode(mTable[idx])) { + occupy(idx); mTable[idx] = newNode(key, val); - mNEntries++; } else { mTable[idx]->val = val; } - if ((halnf) mNEntries / mNSlots > maxLoadFactor()) { + if (overloaded()) { rehash(); } } @@ -218,7 +230,9 @@ namespace tp { markDeletedSlot(idx); mNEntries--; - if (halnf(mNEntries / mNSlots) < 1.f - maxLoadFactor()) { + mNDeleted++; + const bool sparse = mNSlots > MAP_MIN_SIZE * 4 && (halnf) mNEntries < (halnf) mNSlots * maxLoadFactor() * 0.25f; + if (sparse || overloaded()) { rehash(); } } @@ -261,6 +275,7 @@ namespace tp { } } mNEntries = 0; + mNDeleted = 0; mNSlots = in.mNSlots; deleteTable(mTable); mTable = newTable(mNSlots); @@ -297,6 +312,7 @@ namespace tp { mTable = newTable(tTableInitialSize); mNSlots = tTableInitialSize; mNEntries = 0; + mNDeleted = 0; } [[nodiscard]] alni slotIdx(alni entry_idx_in) const { diff --git a/Containers/tests/MapTest.cpp b/Containers/tests/MapTest.cpp index feca1f70..ce23563f 100644 --- a/Containers/tests/MapTest.cpp +++ b/Containers/tests/MapTest.cpp @@ -2,6 +2,8 @@ #include "Map.hpp" #include "Tests.hpp" +#include + using namespace tp; SUITE(HashTable) { @@ -121,4 +123,44 @@ SUITE(HashTable) { map.removeAll(); } -} \ No newline at end of file + + TEST(RemovingKeysTakesLinearTime) { + tp::Map map; + const tp::ualni count = 200000; + for (tp::ualni key = 0; key < count; key++) + map.put(key, key); + const auto start = std::chrono::steady_clock::now(); + for (tp::ualni key = 0; key < count / 2; key++) + map.remove(key); + const double ms = std::chrono::duration(std::chrono::steady_clock::now() - start).count(); + CHECK(ms < 200.0); + CHECK_EQUAL(count / 2, map.size()); + for (tp::ualni key = count / 2; key < count; key += 997) + CHECK_EQUAL(key, map.get(key)); + } + + TEST(MostlyEmptyMapShrinks) { + tp::Map map; + for (tp::ualni key = 0; key < 4096; key++) + map.put(key, key); + const tp::ualni slots = map.end(); + for (tp::ualni key = 0; key < 4000; key++) + map.remove(key); + CHECK(map.end() < slots); + for (tp::ualni key = 4000; key < 4096; key++) + CHECK_EQUAL(key, map.get(key)); + } + + TEST(LookupsTerminateAfterMassRemoval) { + tp::Map map; + for (tp::ualni key = 0; key < 1000; key++) + map.put(key, true); + for (tp::ualni key = 0; key < 990; key++) + map.remove(key); + for (tp::ualni key = 990; key < 1000; key++) + map.remove(key); + for (tp::ualni key = 0; key < 100; key++) + CHECK(!map.presents(key + 5000).isValid()); + CHECK_EQUAL(0u, map.size()); + } +} -- 2.49.1 From db120562c36efec4021526eb34e2c3a76d78a053 Mon Sep 17 00:00:00 2001 From: agent Date: Tue, 27 Oct 2026 02:16:18 +0300 Subject: [PATCH 3/3] Containers: include the utility header for the ring buffer --- Containers/private/linear_ring.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Containers/private/linear_ring.cpp b/Containers/private/linear_ring.cpp index 3b6091c0..777415dc 100644 --- a/Containers/private/linear_ring.cpp +++ b/Containers/private/linear_ring.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace lrbm { static int create_memfd(const char* name) { -- 2.49.1