cs440-assignment2/Map.hpp

797 lines
25 KiB
C++

// commenting everything out when I commit so all commits my code technically
// compiles
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <initializer_list>
#include <optional>
#include <stdexcept>
#include <utility>
#include <vector>
// everything is super interconnected so some forward declarations are needed at
// various points
namespace cs440 {
template <typename Key_T, typename Mapped_T> 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 <typename Key_T, typename Mapped_T> struct BookKeeping {
using Self = BookKeeping<Key_T, Mapped_T>;
using ValueType = std::pair<Key_T, Mapped_T>;
using Ptr = typename std::vector<Self>::iterator;
friend class Map<Key_T, Mapped_T>;
Map<Key_T, Mapped_T> &container;
ValueType value;
// Ptr self;
Color color;
// nullptr indicates empty
std::optional<std::size_t> parent;
std::optional<std::size_t> left;
std::optional<std::size_t> right;
std::optional<std::size_t> prev;
std::optional<std::size_t> next;
BookKeeping(Map<Key_T, Mapped_T> &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},
// TODO: recalc this
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;
// TODO: recalc this
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<std::size_t> 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<std::size_t>{static_cast<std::size_t>(
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<std::size_t>{static_cast<std::size_t>(
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<std::size_t>{static_cast<std::size_t>(
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<std::size_t>{static_cast<std::size_t>(
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<std::size_t>{static_cast<std::size_t>(
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 <typename Key_T, typename Mapped_T> class Map {
private:
using ValueType = std::pair<Key_T, Mapped_T>;
using Node = BookKeeping<Key_T, Mapped_T>;
using Map_T = Map<Key_T, Mapped_T>;
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<std::size_t>;
Map<Key_T, Mapped_T> &parent;
Ref_T ref;
Ref_T escape;
Iterator(Map<Key_T, Mapped_T> &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<Key_T, Mapped_T>;
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<Key_T, Mapped_T>;
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<std::size_t> root;
std::optional<std::size_t> min;
std::optional<std::size_t> max;
std::vector<Node> nodes;
std::size_t size_diff;
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<ValueType> 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<std::size_t> g,
std::optional<std::size_t> p,
std::optional<std::size_t> 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<std::size_t>;
// 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:
// TODO: recalc this
this->nodes[to_insert.value()].next = parent;
this->nodes[to_insert.value()].prev = this->nodes[parent.value()].prev;
this->nodes[parent.value()].prev = to_insert;
this->nodes[parent.value()].left = to_insert;
break;
case Direction::Right:
// TODO: recalc this
this->nodes[to_insert.value()].prev = parent;
this->nodes[to_insert.value()].next = this->nodes[parent.value()].next;
// TODO: recalc this
this->nodes[parent.value()].next = to_insert;
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<std::size_t> grandparent;
std::optional<std::size_t> 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<std::optional<std::size_t>, Direction>
locate_slot(const Key_T &key) {
using Ref_T = std::optional<std::size_t>;
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<Iterator, bool> 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;
nodes.back().prev = std::nullopt;
}
if (max == std::nullopt ||
val.first > this->nodes[max.value()].value.first) {
max = nodes.size() - 1;
nodes.back().next = std::nullopt;
}
return std::make_pair(Iterator(*this, nodes.size() - 1), ret);
}
template <typename IT_T> 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 = &this->nodes[pos.ref.value()];
Node *parent = to_delete->par();
assert(parent != nullptr);
Direction dir =
parent->r() == to_delete ? Direction::Right : Direction::Left;
Node *sibling;
;
Node *close_nephew;
Node *distant_nephew;
parent->c_trans(dir, nullptr);
do {
dir = parent->r() == 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->par();
} while (parent != nullptr);
}
public:
// TODO: check that the way of reconnecting next and prev works
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) {
// TODO: recalc this
Ref_T next = ref->next;
Ref_T prev = ref->prev;
*ref = this->nodes[next.value()];
// TODO: recalc this
this->nodes[prev.value()].next = next;
this->nodes[next.value()].prev = prev;
this->erase(Iterator{*this, next});
}
// single child which is left
else if (ref->l() != nullptr && ref->r() == nullptr) {
// TODO: recalc this
Ref_T next = ref->next;
Ref_T prev = ref->prev;
*ref = *ref->l();
this->nodes[prev.value()].next = next;
this->nodes[next.value()].prev = prev;
}
// single child which is right
else if (ref->l() == nullptr && ref->r() != nullptr) {
// TODO: recalc this
Ref_T next = ref->next;
Ref_T prev = ref->prev;
*ref = *ref->r();
if (prev.has_value()) {
// TODO: recalc this
this->nodes[prev.value()].next = next;
}
if (next.has_value()) {
// TODO: recalc this
this->nodes[next.value()].prev = prev;
}
}
// no children and root
else if (ref->l() == nullptr && ref->r() == nullptr) {
this->root = std::nullopt;
}
// no children and red
else if (ref->l() == nullptr && ref->r() == nullptr) {
Node *next =
ref->next.has_value() ? &this->nodes[ref->next.value()] : nullptr;
Node *prev =
ref->prev.has_value() ? &this->nodes[ref->prev.value()] : nullptr;
// TODO: recalc this
prev->next = next != nullptr ? std::optional{next - &this->nodes[0]}
: std::nullopt;
// TODO: recalc this
next->prev = prev != nullptr ? std::optional{prev - &this->nodes[0]}
: std::nullopt;
}
// complicated case of black node with no kids
else {
this->complex_erase(pos);
}
}
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