From d8278a195a10ff21f9d528b7d0b9ee929c5c6430 Mon Sep 17 00:00:00 2001 From: Pagwin Date: Sat, 23 Nov 2024 16:50:55 -0500 Subject: [PATCH] starting over to avoid the mess I made earliest commit I'm 100% confident Map.hpp doesn't have some compilation error waiting to pop up from initializing the template --- Map.hpp | 1051 ++++++++++++++++----------------------------------- Map.hpp.old | 768 +++++++++++++++++++++++++++++++++++++ 2 files changed, 1099 insertions(+), 720 deletions(-) create mode 100644 Map.hpp.old diff --git a/Map.hpp b/Map.hpp index 0e2604a..78cd5a8 100644 --- a/Map.hpp +++ b/Map.hpp @@ -1,22 +1,14 @@ -// commenting everything out when I commit so all commits my code technically -// compiles +#ifndef _POWELL_CS440 +#define _POWELL_CS440 #include #include -#include -#include +#include +#include #include -#include #include -#include - -// everything is super interconnected so some forward declarations are needed at -// various points namespace cs440 { - -template class Map; - +// universal type defs here namespace { -enum class Color { Red, Black }; enum class Direction { Left, Right }; Direction operator!(Direction dir) { switch (dir) { @@ -25,746 +17,365 @@ Direction operator!(Direction dir) { case Direction::Right: return Direction::Left; default: - // unreachable the only directions are left and right assert(false); } } -template struct BookKeeping { - using Self = BookKeeping; - using ValueType = std::pair; - using Ptr = typename std::vector::iterator; - friend class Map; - Map &container; - ValueType value; - // Ptr self; - Color color; - // nullptr indicates empty - std::optional parent; - std::optional left; - std::optional right; - std::optional prev; - std::optional next; - BookKeeping(Map &container) : container{container} {} - BookKeeping(BookKeeping const &rhs) - : container{rhs.container}, value{rhs.value}, // self{rhs.self}, - color{rhs.color}, parent{rhs.parent}, left{rhs.left}, right{rhs.right}, - prev{rhs.prev}, next{rhs.next} {} - // if pointing to different containers throws - BookKeeping &operator=(BookKeeping const &rhs) { - if (&this->container != &rhs.container) { - throw std::invalid_argument{"can only reassign Bookkeeping " - "values/iterators from the same map object"}; - } - this->value = rhs.value; - // this->self = rhs.self; - this->color = rhs.color; - this->parent = rhs.parent; - this->left = rhs.left; - this->right = rhs.right; - this->prev = rhs.prev; - this->next = rhs.next; - return *this; - } - // reference to a pointer because the alternatives were worse - inline Self *child(Direction dir) { - auto ret = c_select(dir); - return ret.has_value() ? &container.nodes[ret.value()] : nullptr; - } - inline std::optional c_select(Direction dir) { - switch (dir) { - case Direction::Left: - return left; - break; - case Direction::Right: - return right; - break; - default: - assert(false); - } - } - inline void c_trans(Direction dir, Self *v) { - switch (dir) { - case Direction::Left: - this->set_l(v); - break; - case Direction::Right: - this->set_r(v); - break; - default: - assert(false); - } - } - - inline Self *n() { - return next.has_value() ? &container.nodes[next.value()] : nullptr; - } - inline void set_n(Self *ptr) { - this->next = ptr == nullptr - ? std::nullopt - : std::optional{static_cast( - ptr - &this->container.nodes[0])}; - } - inline Self *p() { - return prev.has_value() ? &container.nodes[prev.value()] : nullptr; - } - inline void set_p(Self *ptr) { - this->prev = ptr == nullptr - ? std::nullopt - : std::optional{static_cast( - ptr - &this->container.nodes[0])}; - } - inline Self *r() { - return right.has_value() ? &container.nodes[right.value()] : nullptr; - } - inline void set_r(Self *ptr) { - this->right = ptr == nullptr - ? std::nullopt - : std::optional{static_cast( - ptr - &this->container.nodes[0])}; - } - inline Self *l() { - return left.has_value() ? &container.nodes[left.value()] : nullptr; - } - inline void set_l(Self *ptr) { - this->left = ptr == nullptr - ? std::nullopt - : std::optional{static_cast( - ptr - &this->container.nodes[0])}; - } - inline Self *par() { - return parent.has_value() ? &container.nodes[parent.value()] : nullptr; - } - inline void set_par(Self *ptr) { - this->parent = ptr == nullptr - ? std::nullopt - : std::optional{static_cast( - ptr - &this->container.nodes[0])}; - } - // 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->par(); - Self *S = P->child(!dir); - Self *C; - - // this tidbit is wrong and wikipedia is wrong to have this assert it seems - // this method shouldn't be called in cases where this assert will trip - // assert(S != nullptr); - - C = S->child(dir); - P->c_trans(!dir, C); - - if (C != nullptr) { - C->set_par(P); - } - S->c_trans(dir, P); - P->set_par(S); - S->set_par(G); - if (G != nullptr) { - if (P == G->r()) { - G->set_r(S); - } else { - G->set_l(S); - } - } else { - T.root = S - &T.nodes[0]; - } - } -}; +enum class Color { Red, Black }; } // namespace -// https://en.wikipedia.org/wiki/Red%E2%80%93black_tree template class Map { -private: - using ValueType = std::pair; - using Node = BookKeeping; - using Map_T = Map; + // Type definitions here + using ValueType = std::pair; + using internal_ValueType = std::pair; + + struct Node { + Node *parent; + internal_ValueType val; + std::unique_ptr left; + std::unique_ptr right; + Color color; + Node *prev; + Node *next; + Node(internal_ValueType val) + : parent{nullptr}, val{val}, left{}, right{}, color{Color::Red}, + prev{nullptr}, next{nullptr} {} + Node(const Node &rhs) + : parent{nullptr}, val{rhs.val}, + left{std::make_unique(*rhs.left)}, + right{std::make_unique(*rhs.right)}, color{rhs.color}, + prev{nullptr}, next{nullptr} { + this->left->parent = this; + this->right->parent = this; + } + Node(Node &&rhs) + : parent{nullptr}, val{std::move(rhs.val)}, left{std::move(rhs.left)}, + right{std::move(rhs.right)}, color{rhs.color}, prev{nullptr}, + next{nullptr} { + this->left->parent = this; + this->right->parent = this; + } + Node &operator=(const Node &rhs) { + // retain parent as is, common case is the copy or move is happening due + // to a rotation where parent can get wonky + // this->parent + this->val = rhs.val; + this->left = std::make_unique(*rhs.left); + this->right = std::make_unique(*rhs.right); + + this->left->parent = this; + this->right->parent = this; + this->left->restore_ordering(); + this->right->restore_ordering(); + return *this; + } + Node &operator=(Node &&rhs) { + // retain parent as is, common case is the copy or move is happening due + // to a rotation where parent can get wonky + // this->parent + this->val = rhs.val; + this->left = std::move(rhs.left); + this->right = std::move(rhs.right); + this->left->parent = this; + this->right->parent = this; + this->left->restore_ordering(); + this->right->restore_ordering(); + return *this; + } + Node *child(Direction dir) { + switch (dir) { + case Direction::Left: + return this->left.get(); + case Direction::Right: + return this->right.get(); + default: + assert(false); + } + } + std::unique_ptr uchild(Direction dir) { + switch (dir) { + case Direction::Left: + return std::move(this->left); + case Direction::Right: + return std::move(this->right); + default: + assert(false); + } + } + std::unique_ptr uchild(Node *child) { + return this->uchild(this->which_child(child)); + } + std::unique_ptr &set_child(Direction dir, + std::unique_ptr new_child) { + + new_child->parent = this; + + switch (dir) { + case Direction::Left: + return this->left = std::move(new_child); + case Direction::Right: + return this->right = std::move(new_child); + default: + assert(false); + } + } + Direction which_child(Node *n) { + if (this->left.get() == n) { + return Direction::Left; + } + if (this->right.get() == n) { + return Direction::Right; + } + assert(false); + } + void erase_child(Node *n) { this->erase_child(this->which_child(n)); } + void erase_child(Direction dir) { + // bringing ownership to this function scope so Deleter gets called at end + // of function and we can do reordering things + std::unique_ptr dropping; + switch (dir) { + case Direction::Right: + dropping = std::move(this->right); + break; + case Direction::Left: + dropping = std::move(this->left); + break; + default: + assert(false); + } + + // intuitively should be correct but might need to do restore ordering on + // both instead + dropping->prev->next = dropping->next; + dropping->next->prev = dropping->prev; + } + void restore_ordering() { + this->prev = this->calc_pred(); + this->next = this->calc_succ(); + this->prev->next = this; + this->next->prev = this; + } + Node *calc_pred() { + if (this->left) { + Node *ret = this->left.get(); + while (ret->right) { + ret = ret->right.get(); + } + return ret; + } else { + Node *ret = this->parent; + Node *prev_ret = this; + while (ret && ret->which_child(prev_ret) != Direction::Right) { + prev_ret = ret; + ret = prev_ret->parent; + } + return ret; + } + } + Node *calc_succ() { + if (this->right) { + Node *ret = this->right.get(); + while (ret->left) { + ret = ret->left.get(); + } + return ret; + } else { + Node *ret = this->parent; + Node *prev_ret = this; + while (ret && ret->which_child(prev_ret) != Direction::Left) { + prev_ret = ret; + ret = prev_ret->parent; + } + return ret; + } + } + void rotate(Direction dir) { + + // cannot rotate nullptr + assert(this != nullptr); + + // we can't be root for this rotate operation + assert(this->parent != nullptr); + + // if we're missing the child on the opposite direction this is an invalid + // rotation + assert(this->child(!dir)); + + // gotta pull outselves out of parent to avoid accidentally overwriting + // outselves + std::unique_ptr self = this->parent->uchild(!dir); + + // make sure this is actually us + assert(self.get() == this); + + // make our former position the position of the relevant child + this->parent->set_child(!dir, this->uchild(!dir)); + + // steal our former child's child + this->set_child(!dir, this->parent->child(!dir)->uchild(dir)); + + // make ourselves our former child's child + this->parent->child(!dir)->set_child(dir, std::move(self)); + } + // TODO: + void restore_red_black_insert() {} + // TODO: + void restore_red_black_erase() {} + }; + + // data needed for implementation + std::optional root; + std::size_t _size; + Node *min; + Node *max; public: - class Iterator; - class ConstIterator; - class ReverseIterator; - - friend class Iterator; - friend class ConstIterator; - friend class ReverseIterator; - friend Node; + // public type definitions class Iterator { - friend Map_T; - friend Node; - - private: - using Ref_T = std::optional; - Map &parent; - Ref_T ref; - Ref_T escape; - Iterator(Map &parent, Ref_T ref, - Ref_T escape = std::nullopt) - : parent{parent}, ref{ref}, escape{escape} {} + Node *underlying; + Node *store; + Iterator(Node *ptr, Node *potential = nullptr) + : underlying{ptr}, store{potential} {} public: + friend Map; Iterator() = delete; - Iterator &operator++() { - if (ref == std::nullopt) { - ref = escape; - return *this; - } - if (parent.nodes[ref.value()].next == std::nullopt) { - escape = ref; - } - ref = parent.nodes[ref.value()].next; - return *this; - } - Iterator operator++(int) { - Iterator tmp = *this; - ++(*this); - return tmp; - } - Iterator &operator--() { - if (ref == std::nullopt) { - ref = escape; - return *this; - } - if (parent.nodes[ref.value()].prev == std::nullopt) { - escape = ref; - } - ref = parent.nodes[ref.value()].prev; - return *this; - } - Iterator operator--(int) { - Iterator tmp = *this; - --(*this); - return tmp; - } - ValueType &operator*() const { - return this->parent.nodes[this->ref.value()].value; - } - ValueType *operator->() const { return &this->operator*(); } - friend bool operator==(Iterator const &lhs, Iterator const &rhs) { - return lhs.ref == rhs.ref; - } - friend bool operator!=(Iterator const &lhs, Iterator const &rhs) { - return lhs.ref != rhs.ref; - } - friend bool operator==(ConstIterator const &lhs, Iterator const &rhs) { - return lhs.store_iter.ref == rhs.ref; - } - friend bool operator!=(ConstIterator const &lhs, Iterator const &rhs) { - return lhs.store_iter.ref != rhs.ref; - } - friend bool operator==(Iterator const &lhs, ConstIterator const &rhs) { - return lhs.ref == rhs.store_iter.ref; - } - friend bool operator!=(Iterator const &lhs, ConstIterator const &rhs) { - return lhs.ref != rhs.store_iter.ref; - } }; - class ConstIterator { - public: - friend class Map; - friend class Iterator; - using underlying = Iterator; - - private: - underlying store_iter; - ConstIterator(underlying iter) : store_iter{iter} {} - - public: - ConstIterator() = delete; - friend bool operator==(ConstIterator const &lhs, ConstIterator const &rhs) { - return lhs.store_iter == rhs.store_iter; - } - ConstIterator &operator++() { - ++this->store_iter; - return *this; - } - ConstIterator operator++(int) { - ConstIterator tmp = *this; - this->store_iter++; - return tmp; - } - ConstIterator &operator--() { - --this->store_iter; - return *this; - } - ConstIterator operator--(int) { - ConstIterator tmp = *this; - this->store_iter--; - return tmp; - } - const ValueType &operator*() const { return *this->store_iter; } - const ValueType *operator->() const { - return this->store_iter.operator->(); - } - friend bool operator!=(ConstIterator const &lhs, ConstIterator const &rhs) { - return lhs.store_iter != rhs.store_iter; - } - }; - class ReverseIterator { - public: - friend class Map; - friend class Iterator; - using underlying = Iterator; - - private: - underlying store_iter; - - public: - ReverseIterator() = delete; - ReverseIterator(underlying store_iter) : store_iter{store_iter} {} - 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; - } - friend bool operator!=(ConstIterator const &lhs, ConstIterator const &rhs) { - return lhs.store_iter != rhs.store_iter; - } - }; - -private: - std::optional root; - std::optional min; - std::optional max; - std::vector nodes; - std::size_t size_diff; - - std::optional pred(std::size_t node) { - if (this->nodes[node].left.has_value()) { - std::size_t store = this->nodes[node].left.value(); - while (this->nodes[store].right.has_value()) { - store = this->nodes[node].right.value(); - } - return store; - } else { - if (!this->nodes[node].parent.has_value()) { - return std::nullopt; - } - - std::size_t prev_store = node; - std::size_t store = this->nodes[node].parent.value(); - while (this->nodes[store].parent.has_value()) { - if (this->nodes[store].right == prev_store) { - return store; - } - prev_store = store; - store = this->nodes[store].parent.value(); - } - return std::nullopt; - } - } - std::optional succ(std::size_t node) { - if (this->nodes[node].right.has_value()) { - std::size_t store = this->nodes[node].right.value(); - while (this->nodes[store].left.has_value()) { - store = this->nodes[node].left.value(); - } - return store; - } else { - if (!this->nodes[node].parent.has_value()) { - return std::nullopt; - } - - std::size_t prev_store = node; - std::size_t store = this->nodes[prev_store].parent.value(); - while (this->nodes[store].parent.has_value()) { - if (this->nodes[store].left == prev_store) { - return store; - } - prev_store = store; - store = this->nodes[store].parent.value(); - } - return std::nullopt; - } - } - -public: - Map() - : root{std::nullopt}, min{std::nullopt}, max{std::nullopt}, nodes{}, - size_diff{0} {} - Map(const Map &rhs) - : root{rhs.root}, min{rhs.min}, max{rhs.max}, nodes{rhs.nodes}, - size_diff{0} {} + Map() : root{}, _size{0} {} + Map(const Map &rhs) : root{rhs.root}, _size{rhs._size} {} + Map(Map &&rhs) : root{std::move(rhs.root)}, _size{rhs._size} {} Map &operator=(const Map &rhs) { this->root = rhs.root; - this->min = rhs.min; - this->max = rhs.max; - this->nodes = rhs.nodes; + this->_size = rhs._size; + return *this; } - Map(std::initializer_list elems) - : root{std::nullopt}, min{std::nullopt}, max{std::nullopt}, nodes{} { - this->insert(elems.begin(), elems.end()); + Map &operator=(Map &&rhs) { + this->root = std::move(rhs.root); + this->_size = rhs._size; + return *this; } + std::size_t size() { return this->_size; } - ~Map() {} - - size_t size() const { return this->nodes.size() - this->size_diff; } - bool empty() const { return this->size() == 0; } - - Iterator begin() { return Iterator{*this, min}; } - Iterator end() { return Iterator{*this, std::nullopt, max}; } - ConstIterator begin() const { return ConstIterator{*this, this->begin()}; } - ConstIterator end() const { return ConstIterator{*this, this->end()}; } - ConstIterator cbegin() const { return this->begin(); } - ConstIterator cend() const { return this->end(); } - ReverseIterator rbegin() { - return ReverseIterator{Iterator{*this, this->max}}; - } - ReverseIterator rend() { - return ReverseIterator{Iterator{*this, nullptr, min}}; - } - Iterator find(const Key_T &key) { - // 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.has_value()) { - if (this->root.has_value() && - this->nodes[this->root.value()].value.first == key) { - return Iterator{*this, root}; +private: + template + std::pair locate(const Key_T &key) { + Node *ret_parent; + Direction ret_dir; + // map is empty + if (!this->root.has_value()) { + if constexpr (trace) { + std::cerr << "(map empty)" << std::endl; + } + return std::make_pair(nullptr, ret_dir); + } + if constexpr (trace) { + std::cerr << "root->"; + } + // value is in root + if (this->root.value().val.first == key) { + return std::make_pair(nullptr, ret_dir); + } + ret_parent = &this->root.value(); + if (key < ret_parent->val.first) { + if constexpr (trace) { + std::cerr << "left->"; + } + ret_dir = Direction::Left; + } else { + if constexpr (trace) { + std::cerr << "right->"; + } + ret_dir = Direction::Right; + } + while (ret_parent->child(ret_dir) && + ret_parent->child(ret_dir)->val.first != key) { + ret_parent = ret_parent->child(ret_dir); + if (key < ret_parent->val.first) { + if constexpr (trace) { + std::cerr << "left->"; + } + ret_dir = Direction::Left; } else { + if constexpr (trace) { + std::cerr << "right->"; + } + ret_dir = Direction::Right; + } + } + if constexpr (trace) { + std::cerr << "found" << std::endl; + } + return std::make_pair(ret_parent, ret_dir); + } + +public: + // baseline find using locate + Iterator find(const Key_T &key) { + auto [parent, dir] = locate(key); + if (parent == nullptr) { + if (this->root.has_value()) { + if (this->root.value().val.first == key) { + return Iterator{&this->root.value()}; + } return this->end(); } } - if (!this->nodes[parent.value()].c_select(dir).has_value()) { - return this->end(); + if (parent->child(dir) != nullptr) { + return Iterator{parent->child(dir), nullptr}; } - return Iterator{*this, this->nodes[parent.value()].c_select(dir)}; + return this->end(); } - // implicit cast to ConstIterator from Iterator - ConstIterator find(const Key_T &key) const { return this->find(key); } - - Mapped_T &at(const Key_T &key) { - auto ret = this->find(key); - if (ret == this->end()) { - throw std::out_of_range{"Key not in map"}; - } - return ret->second; - } - const Mapped_T &at(const Key_T &key) const { - auto ret = this->find(key); - if (ret == this->end()) { - throw std::out_of_range{"Key not in map"}; - } - return ret->second; - } - Mapped_T &operator[](const Key_T &key) { - Mapped_T v; - auto insert_val = std::make_pair(key, v); - auto [iter, key_no_exist] = this->insert(insert_val); - return iter->second; - } - -private: - void handle_root_rotation(std::optional g, - std::optional p, - std::optional i, Direction dir) { - handle_root_rotation(g.has_value() ? &this->nodes[g.value()] : nullptr, - p.has_value() ? &this->nodes[p.value()] : nullptr, - i.has_value() ? &this->nodes[i.value()] : nullptr, - dir); - } - 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); - } - grandparent->rotate(!dir); - - parent->color = Color::Black; - grandparent->color = Color::Red; - } - using Ref_T = std::optional; - // heavily referencing the wikipedia implementation for this - // https://en.wikipedia.org/wiki/Red%E2%80%93black_tree#Insertion - void insert_helper(Ref_T to_insert, Ref_T parent, Direction dir) { - // initialize the element we're inserting - this->nodes[to_insert.value()].color = Color::Red; - this->nodes[to_insert.value()].left = std::nullopt; - this->nodes[to_insert.value()].right = std::nullopt; - this->nodes[to_insert.value()].next = std::nullopt; - this->nodes[to_insert.value()].prev = std::nullopt; - this->nodes[to_insert.value()].parent = parent; - - // if this is the first element to be inserted it's root - if (!this->nodes[to_insert.value()].parent.has_value()) { - this->root = to_insert; - this->nodes[to_insert.value()].color = Color::Black; - return; - } - - switch (dir) { - case Direction::Left: - this->nodes[parent.value()].left = to_insert; - break; - case Direction::Right: - this->nodes[parent.value()].right = to_insert; - break; - } - - do { - // don't need to keep track of these in between loops they get - // recalculated - std::optional grandparent; - std::optional uncle; - if (this->nodes[parent.value()].color == Color::Black) { - // black parent means invariants definitely hold - return; - } - - grandparent = this->nodes[parent.value()].parent; - - if (!grandparent.has_value()) { - // parent is root, just need to recolor it to black - this->nodes[parent.value()].color = Color::Black; - return; - } - - Direction parent_direction; - if (this->nodes[grandparent.value()].left == parent) { - parent_direction = Direction::Left; - uncle = this->nodes[grandparent.value()].right; - } else { - parent_direction = Direction::Right; - uncle = this->nodes[grandparent.value()].left; - } - - if (!uncle.has_value() || - this->nodes[uncle.value()].color == Color::Black) { - if (to_insert == this->nodes[parent.value()].c_select(!dir)) { - // case 5 - this->nodes[parent.value()].rotate(dir); - to_insert = parent; - parent = this->nodes[grandparent.value()].c_select(dir); + Iterator find_trace(const Key_T &key) { + auto [parent, dir] = locate(key); + if (parent == nullptr) { + if (this->root.has_value()) { + if (this->root.value().val.first == key) { + return Iterator{&this->root.value()}; } - // case 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 - this->nodes[parent.value()].color = Color::Black; - this->nodes[uncle.value()].color = Color::Black; - this->nodes[grandparent.value()].color = Color::Red; - - to_insert = grandparent; - parent = this->nodes[to_insert.value()].parent; - } while (parent.has_value()); - - // case 3: current node is red root so we're done - } - // returns nullptr iff map is empty - std::pair, Direction> - locate_slot(const Key_T &key) { - using Ref_T = std::optional; - Ref_T current = this->root; - Ref_T parent = std::nullopt; - Direction dir; - while (current.has_value() && - this->nodes[current.value()].value.first != key) { - parent = current; - if (key < this->nodes[current.value()].value.first) { - dir = Direction::Left; - current = this->nodes[current.value()].left; - } else { - dir = Direction::Right; - current = this->nodes[current.value()].right; + return this->end(); } } - return std::make_pair(parent, dir); + if (parent->child(dir) != nullptr) { + return Iterator{parent->child(dir), nullptr}; + } + return this->end(); } - -public: - // 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. + // baseline modification operations std::pair insert(const ValueType &val) { - auto [parent, dir] = locate_slot(val.first); - bool ret = !parent.has_value() || - !this->nodes[parent.value()].c_select(dir).has_value(); - if (!ret) { - return std::make_pair( - Iterator{*this, this->nodes[parent.value()].c_select(dir)}, ret); - } - Node to_insert{*this}; - to_insert.value = val; - this->nodes.push_back(std::move(to_insert)); - // this->nodes.back().self = (--this->nodes.end()); - insert_helper(nodes.size() - 1, parent, dir); + // for convenience + auto &[key, map] = val; - if (min == std::nullopt || - val.first < this->nodes[min.value()].value.first) { - min = nodes.size() - 1; + auto [parent, dir] = locate(key); + // located root node + if (parent == nullptr) { + if (this->root.has_value()) { + return std::make_pair(Iterator{&root.value()}, false); + } else { + this->root = Node{val}; + return std::make_pair(Iterator{&root.value()}, true); + } } - if (max == std::nullopt || - val.first > this->nodes[max.value()].value.first) { - max = nodes.size() - 1; - } - Ref_T successor = this->succ(this->nodes.size() - 1); - Ref_T predessor = this->pred(this->nodes.size() - 1); - if (successor.has_value()) { - this->nodes[successor.value()].prev = this->nodes.size() - 1; - } - this->nodes[this->nodes.size() - 1].next = successor; - if (predessor.has_value()) { - this->nodes[predessor.value()].next = this->nodes.size() - 1; - } - this->nodes[this->nodes.size() - 1].prev = successor; - return std::make_pair(Iterator(*this, nodes.size() - 1), 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; - } - // black leaf node with no kids - void complex_erase(Iterator pos) {} + // non-root node + if (!parent->child(dir)) { + // node already present + return std::make_pair(Iterator{parent->child(dir)}, false); + } -public: + // need to insert non-root node + Node *new_node = + parent->set_child(dir, std::make_unique(Node{val})).get(); + new_node->restore_red_black_insert(); + new_node->restore_ordering(); + return std::make_pair(Iterator{new_node}, true); + } void erase(Iterator pos) { - this->size_diff++; - // simple cases - Node *ref = &this->nodes[pos.ref.value()]; - if (ref == - (this->min.has_value() ? &this->nodes[this->min.value()] : nullptr)) { - this->min = ref->next; - } - if (ref == - (this->max.has_value() ? &this->nodes[this->max.value()] : nullptr)) { - this->max = ref->prev; - } - // 2 children - if (ref->l() != nullptr && ref->r() != nullptr) { - Ref_T next = ref->next; - *ref = this->nodes[next.value()]; - this->erase(Iterator{*this, next}); - } - // single child which is left - else if (ref->l() != nullptr && ref->r() == nullptr) { - if (ref->par()->l() == ref) { - ref->par()->set_l(ref->l()); - } else { - ref->par()->set_r(ref->l()); - } - if (ref->color == Color::Black) { - ref->l()->color = Color::Black; - } - } - // single child which is right - else if (ref->l() == nullptr && ref->r() != nullptr) { - if (ref->par()->l() == ref) { - ref->par()->set_l(ref->r()); - } else { - ref->par()->set_r(ref->r()); - } - if (ref->color == Color::Black) { - ref->r()->color = Color::Black; - } - } - // no children and root - else if (this->root == pos.ref && ref->l() == nullptr && - ref->r() == nullptr) { - this->root = std::nullopt; - } - // no children and red - else if (ref->color == Color::Red && ref->l() == nullptr && - ref->r() == nullptr) { - if (ref->parent.has_value()) { - if (this->nodes[ref->parent.value()].right == pos.ref.value()) { - this->nodes[ref->parent.value()].right = std::nullopt; - } else { - this->nodes[ref->parent.value()].left = std::nullopt; - } - } - } - // complicated case of black node with no kids - else { - this->complex_erase(pos); - } - if (ref->next.has_value()) { - this->nodes[ref->next.value()].prev = this->pred(ref->next.value()); - } - if (ref->prev.has_value()) { - this->nodes[ref->next.value()].next = this->succ(ref->next.value()); - } - } - void erase(const Key_T &key) { this->erase(this->find(key)); } - void clear() { - this->root = std::nullopt; - this->nodes.clear(); - } - friend bool operator==(const Map &lhs, const Map &rhs) { - if (lhs.nodes.size() != rhs.nodes.size()) { - return false; - } - auto liter = lhs.cbegin(); - auto riter = rhs.cbegin(); - // both must be the same length so this is fine - while (liter != lhs.cend()) { - if (*liter != *riter) { - return false; - } - liter++; - riter++; - } - return true; - } - friend bool operator!=(const Map &lhs, const Map &rhs) { - return !(lhs == rhs); - } - friend bool operator<(const Map &lhs, const Map &rhs) { - auto l_iter = lhs.cbegin(); - auto r_iter = rhs.cbegin(); - for (; l_iter != lhs.cend() && r_iter != rhs.cend(); l_iter++, r_iter++) { - if (*l_iter < *r_iter) { - return true; - } - } - return lhs.size() < rhs.size(); + Node *p = pos.underlying->parent; + p->erase_child(pos.underlying); + p->restore_red_black_erase(); } + + // baseline iterator creation + Iterator begin() { return Iterator{min, nullptr}; } + Iterator end() { return Iterator{nullptr, max}; } }; } // namespace cs440 +#endif diff --git a/Map.hpp.old b/Map.hpp.old new file mode 100644 index 0000000..1f87b01 --- /dev/null +++ b/Map.hpp.old @@ -0,0 +1,768 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// everything is super interconnected so some forward declarations are needed at +// various points +namespace cs440 { + +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 { + using Self = BookKeeping; + using ValueType = std::pair; + using Ptr = typename std::vector::iterator; + friend class Map; + Map &container; + ValueType value; + // Ptr self; + Color color; + // nullptr indicates empty + std::optional parent; + std::optional left; + std::optional right; + std::optional prev; + std::optional next; + BookKeeping(Map &container) : container{container} {} + BookKeeping(BookKeeping const &rhs) + : container{rhs.container}, value{rhs.value}, // self{rhs.self}, + color{rhs.color}, parent{rhs.parent}, left{rhs.left}, right{rhs.right}, + prev{rhs.prev}, next{rhs.next} {} + // if pointing to different containers throws + BookKeeping &operator=(BookKeeping const &rhs) { + if (&this->container != &rhs.container) { + throw std::invalid_argument{"can only reassign Bookkeeping " + "values/iterators from the same map object"}; + } + this->value = rhs.value; + // this->self = rhs.self; + this->color = rhs.color; + this->parent = rhs.parent; + this->left = rhs.left; + this->right = rhs.right; + this->prev = rhs.prev; + this->next = rhs.next; + return *this; + } + // reference to a pointer because the alternatives were worse + inline Self *child(Direction dir) { + auto ret = c_select(dir); + return ret.has_value() ? &container.nodes[ret.value()] : nullptr; + } + inline std::optional c_select(Direction dir) { + switch (dir) { + case Direction::Left: + return left; + break; + case Direction::Right: + return right; + break; + default: + assert(false); + } + } + inline void c_trans(Direction dir, Self *v) { + switch (dir) { + case Direction::Left: + this->set_l(v); + break; + case Direction::Right: + this->set_r(v); + break; + default: + assert(false); + } + } + + inline Self *n() { + return next.has_value() ? &container.nodes[next.value()] : nullptr; + } + inline void set_n(Self *ptr) { + this->next = ptr == nullptr + ? std::nullopt + : std::optional{static_cast( + ptr - &this->container.nodes[0])}; + } + inline Self *p() { + return prev.has_value() ? &container.nodes[prev.value()] : nullptr; + } + inline void set_p(Self *ptr) { + this->prev = ptr == nullptr + ? std::nullopt + : std::optional{static_cast( + ptr - &this->container.nodes[0])}; + } + inline Self *r() { + return right.has_value() ? &container.nodes[right.value()] : nullptr; + } + inline void set_r(Self *ptr) { + this->right = ptr == nullptr + ? std::nullopt + : std::optional{static_cast( + ptr - &this->container.nodes[0])}; + } + inline Self *l() { + return left.has_value() ? &container.nodes[left.value()] : nullptr; + } + inline void set_l(Self *ptr) { + this->left = ptr == nullptr + ? std::nullopt + : std::optional{static_cast( + ptr - &this->container.nodes[0])}; + } + inline Self *par() { + return parent.has_value() ? &container.nodes[parent.value()] : nullptr; + } + inline void set_par(Self *ptr) { + this->parent = ptr == nullptr + ? std::nullopt + : std::optional{static_cast( + ptr - &this->container.nodes[0])}; + } + // 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->par(); + Self *S = P->child(!dir); + Self *C; + + // this tidbit is wrong and wikipedia is wrong to have this assert it seems + // this method shouldn't be called in cases where this assert will trip + // assert(S != nullptr); + + C = S->child(dir); + P->c_trans(!dir, C); + + if (C != nullptr) { + C->set_par(P); + } + S->c_trans(dir, P); + P->set_par(S); + S->set_par(G); + if (G != nullptr) { + if (P == G->r()) { + G->set_r(S); + } else { + G->set_l(S); + } + } else { + T.root = S - &T.nodes[0]; + } + } +}; +} // namespace +// https://en.wikipedia.org/wiki/Red%E2%80%93black_tree +template class Map { +private: + using ValueType = std::pair; + using Node = BookKeeping; + using Map_T = Map; + +public: + class Iterator; + class ConstIterator; + class ReverseIterator; + + friend class Iterator; + friend class ConstIterator; + friend class ReverseIterator; + friend Node; + class Iterator { + friend Map_T; + friend Node; + + private: + using Ref_T = std::optional; + Map &parent; + Ref_T ref; + Ref_T escape; + Iterator(Map &parent, Ref_T ref, + Ref_T escape = std::nullopt) + : parent{parent}, ref{ref}, escape{escape} {} + + public: + Iterator() = delete; + Iterator &operator++() { + if (ref == std::nullopt) { + ref = escape; + return *this; + } + if (parent.nodes[ref.value()].next == std::nullopt) { + escape = ref; + } + ref = parent.nodes[ref.value()].next; + return *this; + } + Iterator operator++(int) { + Iterator tmp = *this; + ++(*this); + return tmp; + } + Iterator &operator--() { + if (ref == std::nullopt) { + ref = escape; + return *this; + } + if (parent.nodes[ref.value()].prev == std::nullopt) { + escape = ref; + } + ref = parent.nodes[ref.value()].prev; + return *this; + } + Iterator operator--(int) { + Iterator tmp = *this; + --(*this); + return tmp; + } + ValueType &operator*() const { + return this->parent.nodes[this->ref.value()].value; + } + ValueType *operator->() const { return &this->operator*(); } + friend bool operator==(Iterator const &lhs, Iterator const &rhs) { + return lhs.ref == rhs.ref; + } + friend bool operator!=(Iterator const &lhs, Iterator const &rhs) { + return lhs.ref != rhs.ref; + } + friend bool operator==(ConstIterator const &lhs, Iterator const &rhs) { + return lhs.store_iter.ref == rhs.ref; + } + friend bool operator!=(ConstIterator const &lhs, Iterator const &rhs) { + return lhs.store_iter.ref != rhs.ref; + } + friend bool operator==(Iterator const &lhs, ConstIterator const &rhs) { + return lhs.ref == rhs.store_iter.ref; + } + friend bool operator!=(Iterator const &lhs, ConstIterator const &rhs) { + return lhs.ref != rhs.store_iter.ref; + } + }; + class ConstIterator { + public: + friend class Map; + friend class Iterator; + using underlying = Iterator; + + private: + underlying store_iter; + ConstIterator(underlying iter) : store_iter{iter} {} + + public: + ConstIterator() = delete; + friend bool operator==(ConstIterator const &lhs, ConstIterator const &rhs) { + return lhs.store_iter == rhs.store_iter; + } + ConstIterator &operator++() { + ++this->store_iter; + return *this; + } + ConstIterator operator++(int) { + ConstIterator tmp = *this; + this->store_iter++; + return tmp; + } + ConstIterator &operator--() { + --this->store_iter; + return *this; + } + ConstIterator operator--(int) { + ConstIterator tmp = *this; + this->store_iter--; + return tmp; + } + const ValueType &operator*() const { return *this->store_iter; } + const ValueType *operator->() const { + return this->store_iter.operator->(); + } + friend bool operator!=(ConstIterator const &lhs, ConstIterator const &rhs) { + return lhs.store_iter != rhs.store_iter; + } + }; + class ReverseIterator { + public: + friend class Map; + friend class Iterator; + using underlying = Iterator; + + private: + underlying store_iter; + + public: + ReverseIterator() = delete; + ReverseIterator(underlying store_iter) : store_iter{store_iter} {} + 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; + } + friend bool operator!=(ConstIterator const &lhs, ConstIterator const &rhs) { + return lhs.store_iter != rhs.store_iter; + } + }; + +private: + std::optional root; + std::optional min; + std::optional max; + std::vector nodes; + std::size_t size_diff; + + std::optional pred(std::size_t node) { + if (this->nodes[node].left.has_value()) { + std::size_t store = this->nodes[node].left.value(); + while (this->nodes[store].right.has_value()) { + store = this->nodes[node].right.value(); + } + return store; + } else { + if (!this->nodes[node].parent.has_value()) { + return std::nullopt; + } + + std::size_t prev_store = node; + std::size_t store = this->nodes[node].parent.value(); + while (this->nodes[store].parent.has_value()) { + if (this->nodes[store].right == prev_store) { + return store; + } + prev_store = store; + store = this->nodes[store].parent.value(); + } + return std::nullopt; + } + } + std::optional succ(std::size_t node) { + if (this->nodes[node].right.has_value()) { + std::size_t store = this->nodes[node].right.value(); + while (this->nodes[store].left.has_value()) { + store = this->nodes[node].left.value(); + } + return store; + } else { + if (!this->nodes[node].parent.has_value()) { + return std::nullopt; + } + + std::size_t prev_store = node; + std::size_t store = this->nodes[prev_store].parent.value(); + while (this->nodes[store].parent.has_value()) { + if (this->nodes[store].left == prev_store) { + return store; + } + prev_store = store; + store = this->nodes[store].parent.value(); + } + return std::nullopt; + } + } + +public: + Map() + : root{std::nullopt}, min{std::nullopt}, max{std::nullopt}, nodes{}, + size_diff{0} {} + Map(const Map &rhs) + : root{rhs.root}, min{rhs.min}, max{rhs.max}, nodes{rhs.nodes}, + size_diff{0} {} + 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{std::nullopt}, min{std::nullopt}, max{std::nullopt}, nodes{} { + this->insert(elems.begin(), elems.end()); + } + + ~Map() {} + + size_t size() const { return this->nodes.size() - this->size_diff; } + bool empty() const { return this->size() == 0; } + + Iterator begin() { return Iterator{*this, min}; } + Iterator end() { return Iterator{*this, std::nullopt, max}; } + ConstIterator begin() const { return ConstIterator{*this, this->begin()}; } + ConstIterator end() const { return ConstIterator{*this, this->end()}; } + ConstIterator cbegin() const { return this->begin(); } + ConstIterator cend() const { return this->end(); } + ReverseIterator rbegin() { + return ReverseIterator{Iterator{*this, this->max}}; + } + ReverseIterator rend() { + return ReverseIterator{Iterator{*this, nullptr, min}}; + } + Iterator find(const Key_T &key) { + // 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.has_value()) { + if (this->root.has_value() && + this->nodes[this->root.value()].value.first == key) { + return Iterator{*this, root}; + } else { + return this->end(); + } + } + if (!this->nodes[parent.value()].c_select(dir).has_value()) { + return this->end(); + } + return Iterator{*this, this->nodes[parent.value()].c_select(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) { + auto ret = this->find(key); + if (ret == this->end()) { + throw std::out_of_range{"Key not in map"}; + } + return ret->second; + } + const Mapped_T &at(const Key_T &key) const { + auto ret = this->find(key); + if (ret == this->end()) { + throw std::out_of_range{"Key not in map"}; + } + return ret->second; + } + Mapped_T &operator[](const Key_T &key) { + Mapped_T v; + auto insert_val = std::make_pair(key, v); + auto [iter, key_no_exist] = this->insert(insert_val); + return iter->second; + } + +private: + void handle_root_rotation(std::optional g, + std::optional p, + std::optional i, Direction dir) { + handle_root_rotation(g.has_value() ? &this->nodes[g.value()] : nullptr, + p.has_value() ? &this->nodes[p.value()] : nullptr, + i.has_value() ? &this->nodes[i.value()] : nullptr, + dir); + } + 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); + } + grandparent->rotate(!dir); + + parent->color = Color::Black; + grandparent->color = Color::Red; + } + using Ref_T = std::optional; + // heavily referencing the wikipedia implementation for this + // https://en.wikipedia.org/wiki/Red%E2%80%93black_tree#Insertion + void insert_helper(Ref_T to_insert, Ref_T parent, Direction dir) { + // initialize the element we're inserting + this->nodes[to_insert.value()].color = Color::Red; + this->nodes[to_insert.value()].left = std::nullopt; + this->nodes[to_insert.value()].right = std::nullopt; + this->nodes[to_insert.value()].next = std::nullopt; + this->nodes[to_insert.value()].prev = std::nullopt; + this->nodes[to_insert.value()].parent = parent; + + // if this is the first element to be inserted it's root + if (!this->nodes[to_insert.value()].parent.has_value()) { + this->root = to_insert; + this->nodes[to_insert.value()].color = Color::Black; + return; + } + + switch (dir) { + case Direction::Left: + this->nodes[parent.value()].left = to_insert; + break; + case Direction::Right: + this->nodes[parent.value()].right = to_insert; + break; + } + + do { + // don't need to keep track of these in between loops they get + // recalculated + std::optional grandparent; + std::optional uncle; + if (this->nodes[parent.value()].color == Color::Black) { + // black parent means invariants definitely hold + return; + } + + grandparent = this->nodes[parent.value()].parent; + + if (!grandparent.has_value()) { + // parent is root, just need to recolor it to black + this->nodes[parent.value()].color = Color::Black; + return; + } + + Direction parent_direction; + if (this->nodes[grandparent.value()].left == parent) { + parent_direction = Direction::Left; + uncle = this->nodes[grandparent.value()].right; + } else { + parent_direction = Direction::Right; + uncle = this->nodes[grandparent.value()].left; + } + + if (!uncle.has_value() || + this->nodes[uncle.value()].color == Color::Black) { + if (to_insert == this->nodes[parent.value()].c_select(!dir)) { + // case 5 + this->nodes[parent.value()].rotate(dir); + to_insert = parent; + parent = this->nodes[grandparent.value()].c_select(dir); + } + // case 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 + this->nodes[parent.value()].color = Color::Black; + this->nodes[uncle.value()].color = Color::Black; + this->nodes[grandparent.value()].color = Color::Red; + + to_insert = grandparent; + parent = this->nodes[to_insert.value()].parent; + } while (parent.has_value()); + + // case 3: current node is red root so we're done + } + // returns nullptr iff map is empty + std::pair, Direction> + locate_slot(const Key_T &key) { + using Ref_T = std::optional; + Ref_T current = this->root; + Ref_T parent = std::nullopt; + Direction dir; + while (current.has_value() && + this->nodes[current.value()].value.first != key) { + parent = current; + if (key < this->nodes[current.value()].value.first) { + dir = Direction::Left; + current = this->nodes[current.value()].left; + } else { + dir = Direction::Right; + current = this->nodes[current.value()].right; + } + } + return std::make_pair(parent, dir); + } + +public: + // 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) { + auto [parent, dir] = locate_slot(val.first); + bool ret = !parent.has_value() || + !this->nodes[parent.value()].c_select(dir).has_value(); + if (!ret) { + return std::make_pair( + Iterator{*this, this->nodes[parent.value()].c_select(dir)}, ret); + } + Node to_insert{*this}; + to_insert.value = val; + this->nodes.push_back(std::move(to_insert)); + // this->nodes.back().self = (--this->nodes.end()); + insert_helper(nodes.size() - 1, parent, dir); + + if (min == std::nullopt || + val.first < this->nodes[min.value()].value.first) { + min = nodes.size() - 1; + } + if (max == std::nullopt || + val.first > this->nodes[max.value()].value.first) { + max = nodes.size() - 1; + } + Ref_T successor = this->succ(this->nodes.size() - 1); + Ref_T predessor = this->pred(this->nodes.size() - 1); + if (successor.has_value()) { + this->nodes[successor.value()].prev = this->nodes.size() - 1; + } + this->nodes[this->nodes.size() - 1].next = successor; + if (predessor.has_value()) { + this->nodes[predessor.value()].next = this->nodes.size() - 1; + } + this->nodes[this->nodes.size() - 1].prev = successor; + return std::make_pair(Iterator(*this, nodes.size() - 1), 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; + } + // black leaf node with no kids + void complex_erase(Iterator pos) {} + +public: + void erase(Iterator pos) { + this->size_diff++; + // simple cases + Node *ref = &this->nodes[pos.ref.value()]; + if (ref == + (this->min.has_value() ? &this->nodes[this->min.value()] : nullptr)) { + this->min = ref->next; + } + if (ref == + (this->max.has_value() ? &this->nodes[this->max.value()] : nullptr)) { + this->max = ref->prev; + } + // 2 children + if (ref->l() != nullptr && ref->r() != nullptr) { + Ref_T next = ref->next; + *ref = this->nodes[next.value()]; + this->erase(Iterator{*this, next}); + } + // single child which is left + else if (ref->l() != nullptr && ref->r() == nullptr) { + if (ref->par()->l() == ref) { + ref->par()->set_l(ref->l()); + } else { + ref->par()->set_r(ref->l()); + } + if (ref->color == Color::Black) { + ref->l()->color = Color::Black; + } + } + // single child which is right + else if (ref->l() == nullptr && ref->r() != nullptr) { + if (ref->par()->l() == ref) { + ref->par()->set_l(ref->r()); + } else { + ref->par()->set_r(ref->r()); + } + if (ref->color == Color::Black) { + ref->r()->color = Color::Black; + } + } + // no children and root + else if (this->root == pos.ref && ref->l() == nullptr && + ref->r() == nullptr) { + this->root = std::nullopt; + } + // no children and red + else if (ref->color == Color::Red && ref->l() == nullptr && + ref->r() == nullptr) { + if (ref->parent.has_value()) { + if (this->nodes[ref->parent.value()].right == pos.ref.value()) { + this->nodes[ref->parent.value()].right = std::nullopt; + } else { + this->nodes[ref->parent.value()].left = std::nullopt; + } + } + } + // complicated case of black node with no kids + else { + this->complex_erase(pos); + } + if (ref->next.has_value()) { + this->nodes[ref->next.value()].prev = this->pred(ref->next.value()); + } + if (ref->prev.has_value()) { + this->nodes[ref->next.value()].next = this->succ(ref->next.value()); + } + } + void erase(const Key_T &key) { this->erase(this->find(key)); } + void clear() { + this->root = std::nullopt; + this->nodes.clear(); + } + friend bool operator==(const Map &lhs, const Map &rhs) { + if (lhs.nodes.size() != rhs.nodes.size()) { + return false; + } + auto liter = lhs.cbegin(); + auto riter = rhs.cbegin(); + // both must be the same length so this is fine + while (liter != lhs.cend()) { + if (*liter != *riter) { + return false; + } + liter++; + riter++; + } + return true; + } + friend bool operator!=(const Map &lhs, const Map &rhs) { + return !(lhs == rhs); + } + friend bool operator<(const Map &lhs, const Map &rhs) { + auto l_iter = lhs.cbegin(); + auto r_iter = rhs.cbegin(); + for (; l_iter != lhs.cend() && r_iter != rhs.cend(); l_iter++, r_iter++) { + if (*l_iter < *r_iter) { + return true; + } + } + return lhs.size() < rhs.size(); + } +}; +} // namespace cs440