From 2f323d8561502e0824e878860ed8d29e77718310 Mon Sep 17 00:00:00 2001 From: Pagwin Date: Thu, 21 Nov 2024 16:28:49 -0500 Subject: [PATCH] replaced array based binary tree for pointer based binary tree and got a draft of insertion done and a good chunk of a draft for removal done --- Map.hpp | 610 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 416 insertions(+), 194 deletions(-) diff --git a/Map.hpp b/Map.hpp index f5748fa..bac7fea 100644 --- a/Map.hpp +++ b/Map.hpp @@ -1,8 +1,8 @@ // commenting everything out when I commit so all commits my code technically // compiles #include +#include #include -#include #include #include #include @@ -18,27 +18,84 @@ template class Map; namespace { enum class Color { Red, Black }; +enum class Direction { Left, Right }; +Direction operator!(Direction dir) { + switch (dir) { + case Direction::Left: + return Direction::Right; + case Direction::Right: + return Direction::Left; + default: + // unreachable the only directions are left and right + assert(false); + } +} template struct BookKeeping { - friend class Map; + using Self = BookKeeping; using ValueType = std::pair; - Map &parent; + friend class Map; + Map &container; ValueType value; - std::size_t self; Color color; + // nullptr indicates empty + Self *parent; + Self *left; + Self *right; + Self *prev; + Self *next; + // reference to a pointer because the alternatives were worse + inline Self *&child(Direction dir) { + switch (dir) { + case Direction::Left: + return left; + break; + case Direction::Right: + return right; + break; + } + } + // this is root/P for this method + // copying from wikipedia RotateDirRoot with translation into my own idioms + // https://en.wikipedia.org/wiki/Red%E2%80%93black_tree#Operations + inline void rotate(Direction dir) { + // wikipedia version uses alphabet soup, might fix later + Self *P = this; + auto &T = container; + Self *G = P->parent; + Self *S = P->child(!dir); + Self *C; - std::optional next; - std::optional prev; + // this method shouldn't be called in cases where this assert will trip + assert(S != nullptr); + + // + C = S->child(dir); + P->child(!dir) = C; + + if (C != nullptr) { + C->parent = P; + } + S->child(dir) = P; + P->parent = S; + S->parent = G; + if (G != nullptr) { + if (P == G->right) { + G->right = S; + } else { + G->left = S; + } + } else { + T->root = S; + } + } }; } // namespace // https://en.wikipedia.org/wiki/Red%E2%80%93black_tree template class Map { private: using ValueType = std::pair; - // idx 0 = root - // left = parent * 2 + 1 - // right = parent * 2 + 2 - std::vector>> store; - std::vector coloration; + using Node = BookKeeping; + using Map_T = Map; public: class Iterator; @@ -48,59 +105,70 @@ public: friend class Iterator; friend class ConstIterator; friend class ReverseIterator; - friend struct BookKeeping; - // TODO: Iterator functionality + friend Node; class Iterator { - friend class Map; - friend struct BookKeeping; + friend Map_T; + friend Node; - public: private: - enum class PastElem { start, end, neither }; // pointer needed so we can replace as needed - BookKeeping *parent; - - // TODO: next/prev found in bookkeeping - // Note: only used when past first/last element - PastElem use; - std::optional next_or_prev; - Iterator(std::optional> &parent) - : parent{&parent}, use{PastElem::neither}, next_or_prev{std::nullopt} {} + Node *ref; + Node *escape; + Iterator(Node *ref, Node *escape = nullptr) : ref{ref}, escape{escape} {} public: Iterator() = delete; - ConstIterator to_const() const { return ConstIterator(*this); } - Iterator &operator++() { return *this; } + Iterator &operator++() { + if (ref == nullptr) { + ref = escape; + return *this; + } + if (ref->next == nullptr) { + escape = ref; + } + ref = ref->next; + return *this; + } Iterator operator++(int) { Iterator tmp = *this; ++(*this); return tmp; } - Iterator &operator--() { return *this; } + Iterator &operator--() { + if (ref == nullptr) { + ref = escape; + return *this; + } + if (ref->prev == nullptr) { + escape = ref; + } + ref = ref->prev; + return *this; + } Iterator operator--(int) { Iterator tmp = *this; --(*this); return tmp; } - ValueType &operator*() const { return parent.parent.at(parent.self); } - ValueType *operator->() const { return &**this; } + ValueType &operator*() const { return this->ref->value; } + ValueType *operator->() const { return &this->ref->value; } friend bool operator==(Iterator const &lhs, Iterator const &rhs) { - return lhs.store_iter == rhs.store_iter; + return lhs.ref == rhs.ref; } friend bool operator!=(Iterator const &lhs, Iterator const &rhs) { - return lhs.store_iter != rhs.store_iter; + return lhs.ref != rhs.ref; } friend bool operator==(ConstIterator const &lhs, Iterator const &rhs) { - return lhs == rhs.to_const(); + return lhs.store_iter.ref == rhs.ref; } friend bool operator!=(ConstIterator const &lhs, Iterator const &rhs) { - return lhs != rhs.to_const(); + return lhs.store_iter.ref != rhs.ref; } friend bool operator==(Iterator const &lhs, ConstIterator const &rhs) { - return lhs.to_const() == rhs; + return lhs.ref == rhs.store_iter.ref; } friend bool operator!=(Iterator const &lhs, ConstIterator const &rhs) { - return lhs.to_const() != rhs; + return lhs.ref != rhs.store_iter.ref; } }; class ConstIterator { @@ -138,7 +206,6 @@ public: } const ValueType &operator*() const { return *this->store_iter; } const ValueType *operator->() const { - // I find this rather funny return this->store_iter.operator->(); } friend bool operator!=(ConstIterator const &lhs, ConstIterator const &rhs) { @@ -148,7 +215,8 @@ public: class ReverseIterator { public: friend class Map; - using underlying = typename std::vector>::iterator; + friend class Iterator; + using underlying = Iterator; private: underlying store_iter; @@ -156,12 +224,26 @@ public: public: ReverseIterator() = delete; ReverseIterator(underlying store_iter) : store_iter{store_iter} {} - ReverseIterator &operator++() {} - ReverseIterator operator++(int) {} - ReverseIterator &operator--() {} - ReverseIterator operator--(int) {} - ValueType &operator*() const {} - ValueType *operator->() const {} + ReverseIterator &operator++() { + --store_iter; + return *this; + } + ReverseIterator operator++(int) { + ReverseIterator ret = *this; + ++(*this); + return ret; + } + ReverseIterator &operator--() { + ++store_iter; + return *this; + } + ReverseIterator operator--(int) { + ReverseIterator ret = *this; + --(*this); + return ret; + } + ValueType &operator*() const { return this->store_iter.ref->value; } + ValueType *operator->() const { return &this->store_iter.ref->value; } friend bool operator==(ReverseIterator const &lhs, ReverseIterator const &rhs) { return lhs.store_iter == rhs.store_iter; @@ -170,155 +252,328 @@ public: return lhs.store_iter != rhs.store_iter; } }; - Map() : store{} {} - Map(const Map &rhs) : store{rhs.store} {} - Map &operator=(const Map &rhs) { this->store = rhs.store; } - Map(std::initializer_list elems) : store{} { + +private: + Node *root; + Node *min; + Node *max; + std::vector nodes; + +public: + Map() : root{nullptr}, min{nullptr}, max{nullptr}, nodes{} {} + Map(const Map &rhs) + : root{rhs.root}, min{nullptr}, max{nullptr}, nodes{rhs.nodes} {} + Map &operator=(const Map &rhs) { + this->root = rhs.root; + this->min = rhs.min; + this->max = rhs.max; + this->nodes = rhs.nodes; + } + Map(std::initializer_list elems) : root{nullptr}, nodes{} { this->insert(elems.begin(), elems.end()); } - // who cares we're using vector + ~Map() {} size_t size() const { - std::size_t count = 0; - for (auto &m_pair : this->store) { - count += m_pair.has_value() ? 1 : 0; - } - return count; + root = nullptr; + return this->nodes.size(); } - bool empty() const { return this->store.empty(); } - // TODO: Iterator creation - Iterator begin() {} - Iterator end() {} - ConstIterator begin() const {} - ConstIterator end() const {} - ConstIterator cbegin() const {} - ConstIterator cend() const {} - ReverseIterator rbegin() {} - ReverseIterator rend() {} - // TODO: actually return an iterator from find and deal with error cases - // correctly, also need to update for new bookkeeping type + bool empty() const { return this->size() == 0; } + Iterator begin() { return Iterator{min}; } + Iterator end() { return Iterator{nullptr, max}; } + ConstIterator begin() const { return ConstIterator{this->begin()}; } + ConstIterator end() const { return ConstIterator{this->end()}; } + ConstIterator cbegin() const { return this->begin(); } + ConstIterator cend() const { return this->end(); } + ReverseIterator rbegin() { return ReverseIterator{Iterator{this->max}}; } + ReverseIterator rend() { return ReverseIterator{Iterator{nullptr, min}}; } Iterator find(const Key_T &key) { - std::size_t idx = 0; - while (store[idx].first != key) { - if (idx >= store.size()) { - return this->end(); - } - if (store[idx].first < key) { - idx = idx * 2 + 1; - } else { - idx = idx * 2 + 2; - } + // we need a locate slot function for insert regardless so might as well use + // it here + auto [parent, dir] = this->locate_slot(key); + if (parent == nullptr) { + return this->end(); } - } - ConstIterator find(const Key_T &key) const { - std::size_t idx = 0; - while (store[idx].first != key) { - if (idx >= store.size()) { - return this->end(); - } - if (store[idx].first < key) { - idx = idx * 2 + 1; - } else { - idx = idx * 2 + 2; - } + if (parent->child(dir) == nullptr) { + return this->end(); } + return Iterator{parent->child(dir)}; } + // implicit cast to ConstIterator from Iterator + ConstIterator find(const Key_T &key) const { return this->find(key); } - Mapped_T &at(const Key_T &key) { - std::size_t i = 0; - while (this->store.at(i).has_value()) { - switch (true) { - case this->store.at(i).first == key: - return this->store.at(i).second; - case this->store.at(i).first < key: - i = 2 * i + 1; - break; - case this->store.at(i).first > key: - i = 2 * i + 2; - break; - } - } - throw std::out_of_range{""}; - } + Mapped_T &at(const Key_T &key) { return (this->find(key))->second; } const Mapped_T &at(const Key_T &key) const { return this->at(key); } Mapped_T &operator[](const Key_T &key) { return this->at(key); } private: - Color getColor(std::size_t i) { - if (this->store.size() <= i) { - return Color::Black; + void handle_root_rotation(Node *grandparent, Node *parent, Node *inserting, + Direction dir) { + // making inner grandchild into outer grandchild + if (inserting == parent->child(!dir)) { + parent->rotate(dir); + inserting = parent; + parent = grandparent->child(dir); } - if (!this->store.at(i).has_value()) { - return Color::Black; - } - return this->store.at(i).value().color; - } - std::size_t find_null(const Key_T &key) { - std::size_t idx = 0; - while (store[idx].first != key) { - if (idx >= store.size()) { - return this->end(); - } - if (store[idx].first < key) { - if (idx * 2 + 1 > store.size() || !store.at(idx * 2 + 1).has_value()) { - idx = idx * 2 + 1; - break; - } - idx = idx * 2 + 1; + // RotateDirRoot(T,G,1-dir); + Node *gr_grandparent = grandparent->parent; + Node *sibling = grandparent->child(!dir); + assert(sibling != nullptr); + Node *child = sibling->child(dir); + grandparent->child(!dir) = child; + sibling->child(dir) = grandparent; + grandparent->parent = sibling; + sibling->parent = gr_grandparent; + if (gr_grandparent != nullptr) { + Direction grandparent_direction; + if (gr_grandparent->left == grandparent) { + grandparent_direction = Direction::Left; } else { - if (idx * 2 + 2 > store.size() || !store.at(idx * 2 + 2).has_value()) { - idx = idx * 2 + 2; - break; - } - idx = idx * 2 + 2; + grandparent_direction = Direction::Right; + } + gr_grandparent->child(grandparent_direction) = sibling; + } else { + this->root = sibling; + } + + parent->color = Color::Black; + grandparent->color = Color::Red; + } + // heavily referencing the wikipedia implementation for this + // https://en.wikipedia.org/wiki/Red%E2%80%93black_tree#Insertion + void insert_helper(Node *to_insert, Node *parent, Direction dir) { + // initialize the element we're inserting + to_insert->color = Color::Red; + to_insert->left = nullptr; + to_insert->right = nullptr; + to_insert->parent = parent; + switch (dir) { + case Direction::Left: + to_insert->next = parent; + to_insert->prev = parent->prev; + parent->prev = to_insert; + break; + case Direction::Right: + to_insert->prev = parent; + to_insert->next = parent->next; + parent->next = to_insert; + break; + } + + // if this is the first element to be inserted it's root + if (to_insert->parent == nullptr) { + this->root = to_insert; + return; + } + + switch (dir) { + case Direction::Left: + parent->left = to_insert; + break; + case Direction::Right: + parent->right = to_insert; + break; + } + + do { + // don't need to keep track of these in between loops they get + // recalculated + Node *grandparent; + Node *uncle; + if (parent->color == Color::Black) { + // black parent means invariants definitely hold + return; + } + + grandparent = parent->parent; + + if (grandparent == nullptr) { + // parent is root, just need to recolor it to black + parent->color = Color::Black; + return; + } + + Direction parent_direction; + if (grandparent->left == parent) { + parent_direction = Direction::Left; + uncle = grandparent->right; + } else { + parent_direction = Direction::Right; + uncle = grandparent->left; + } + + if (uncle == nullptr || uncle->color == Color::Black) { + // case 5 and 6 + this->handle_root_rotation(grandparent, parent, to_insert, + parent_direction); + return; + } + + // now we know parent and uncle are both red so red-black coloring can be + // pushed down from grandparent + parent->color = Color::Black; + uncle->color = Color::Black; + grandparent->color = Color::Red; + + to_insert = grandparent; + parent = to_insert->parent; + } while (parent != nullptr); + + // case 3: current node is red root so we're done + } + // returns nullptr iff map is empty + std::pair locate_slot(const Key_T &key) { + Node *current = this->root; + Node *parent = nullptr; + Direction dir; + while (current != nullptr && current->value.first != key) { + parent = current; + if (current->value.fist < key) { + dir = Direction::Left; + current = current->left; + } else { + dir = Direction::Right; + current = current->right; } } - return idx; - } - enum class Direction { left, right }; - void insert_helper(std::size_t idx, BookKeeping to_insert) { - // might as well make sure - to_insert.color = Color::Red; - std::size_t parent_idx; - Direction relation; - if (idx % 2 == 1) { - parent_idx = (idx - 1) / 2; - relation = Direction::left; - } else { - parent_idx = (idx - 1) / 2; - relation = Direction::right; - } - BookKeeping &parent = this->store.at(parent_idx).value(); + return std::make_pair(parent, dir); } public: - // TODO: single insert - // OH NO IT SHOULD BE RED-BLACK + // If the key does not already exist in the map, it returns an iterator + // pointing to the new element, and true. If the key already exists, no + // insertion is performed nor is the mapped object changed, and it returns + // an iterator pointing to the element with the same key, and false. std::pair insert(const ValueType &val) { - - BookKeeping new_node; - new_node.color = Color::Red; - new_node.value = val; - new_node.parent = *this; - - if (this->store.size() == 0) { - new_node.self = 0; - this->store.push_back(new_node); + auto [parent, dir] = locate_slot(val.first); + bool ret = parent->child(dir) == nullptr; + if (!ret) { + return std::make_pair(Iterator{parent->child(dir)}, ret); } + Node to_insert; + to_insert.value = val; + this->nodes.push_back(std::move(to_insert)); + insert_helper(&nodes.back(), parent, dir); + + if (min == nullptr || val.first < min->value.first) { + min = &nodes.back(); + } + if (max == nullptr || val.first > max->value.first) { + max = &nodes.back(); + } + + return std::make_pair(Iterator{&nodes.back()}, ret); } template void insert(IT_T range_beg, IT_T range_end) { std::for_each(range_beg, range_end, [&](ValueType &val) { this->insert(val); }); } + +private: + void case5(Node *parent, Node *sibling, Node *close_nephew, + Node *distant_nephew, Direction dir) { + sibling->rotate(!dir); + sibling->color = Color::Red; + close_nephew->color = Color::Black; + distant_nephew = sibling; + sibling = close_nephew; + case6(parent, sibling, distant_nephew, dir); + } + void case6(Node *parent, Node *sibling, Node *distant_nephew, Direction dir) { + parent->rotate(dir); + sibling->color = parent->color; + parent->color = Color::Black; + distant_nephew->color = Color::Black; + } + // heavily referring to + // https://en.wikipedia.org/wiki/Red%E2%80%93black_tree#Removal_of_a_black_non-root_leaf + void complex_erase(Iterator pos) { + + Node *to_delete = pos.ref; + Node *parent = to_delete->parent; + assert(parent != nullptr); + + Direction dir = + parent->right == to_delete ? Direction::Right : Direction::Left; + + Node *sibling; + ; + Node *close_nephew; + Node *distant_nephew; + + parent->child(dir) = nullptr; + + do { + dir = parent->right == to_delete ? Direction::Right : Direction::Left; + + sibling = parent->child(!dir); + distant_nephew = sibling->child(!dir); + close_nephew = sibling->child(dir); + + if (sibling->color == Color::Red) { + // case 3 + parent->rotate(dir); + parent->color = Color::Red; + sibling->color = Color::Black; + sibling = close_nephew; + // redundant? + distant_nephew = sibling->child(!dir); + if (distant_nephew != nullptr && distant_nephew->color == Color::Red) { + case6(parent, sibling, distant_nephew, dir); + return; + } + close_nephew = sibling->child(dir); + if (close_nephew != nullptr && close_nephew->color == Color::Red) { + case5(parent, sibling, close_nephew, distant_nephew, dir); + return; + } + sibling->color = Color::Red; + parent->color = Color::Black; + return; + } + + if (distant_nephew != nullptr && distant_nephew->color == Color::Red) { + case6(parent, sibling, distant_nephew, dir); + return; + } + + if (close_nephew != nullptr && close_nephew->color == Color::Red) { + case5(parent, sibling, close_nephew, distant_nephew, dir); + return; + } + + if (parent->color == Color::Red) { + // case 4 + sibling->color = Color::Red; + parent->color = Color::Black; + return; + } + + // case 2 + sibling->color = Color::Red; + to_delete = parent; + parent = to_delete->parent; + } while (parent != nullptr); + } + +public: // TODO: erase via iterator void erase(Iterator pos) { - // RED BLACK TREE oh no + // simple cases + Node *ref = pos.ref; + + // 2 children just copy over the in order successor and remove successor + + this->complex_erase(pos); } void erase(const Key_T &key) { this->erase(this->find(key)); } - void clear() { this->store = {}; } + void clear() { + this->root = nullptr; + this->nodes.clear(); + } friend bool operator==(const Map &lhs, const Map &rhs) { - if (lhs.store.size() != rhs.store.size()) { + if (lhs.nodes.size() != rhs.nodes.size()) { return false; } auto liter = lhs.cbegin(); @@ -336,40 +591,7 @@ public: friend bool operator!=(const Map &lhs, const Map &rhs) { return !(lhs == rhs); } - friend bool operator<(const Map &lhs, const Map &rhs) { - std::size_t lhs_i = 0; - std::size_t rhs_i = 0; - for (; lhs_i < lhs.store.size() && rhs_i < rhs.store.size(); - lhs_i++, rhs_i++) { - bool lhs_exhaust = false; - while (!lhs.store[lhs_i].has_value()) { - lhs_i++; - if (lhs.store.size() >= lhs_i) { - lhs_exhaust = true; - break; - } - } - - bool rhs_exhaust = false; - while (!rhs.store[rhs_i].has_value()) { - rhs_i++; - if (rhs.store.size() >= rhs_i) { - rhs_exhaust = true; - break; - } - } - - if (lhs_exhaust && !rhs_exhaust) { - return true; - } - if (lhs_exhaust || rhs_exhaust) { - break; - } - if (lhs.store[lhs_i] != rhs.store[rhs_i]) { - return lhs.store[lhs_i] < rhs.store[rhs_i]; - } - } - return false; - } + // TODO + friend bool operator<(const Map &lhs, const Map &rhs) { return false; } }; } // namespace cs440