Compare commits

..

10 commits

Author SHA1 Message Date
Pagwin
ae0b45df73
Fixed issues and setup a makefile for submission 2024-11-25 23:49:54 -05:00
Pagwin
30babdb9dc
some fixes 2024-11-25 20:06:43 -05:00
Pagwin
ce0f5f7ef0
slowly working through given tests to get Map fully working in all functionality 2024-11-25 16:25:47 -05:00
Pagwin
315a200c9b
committing more tests 2024-11-24 22:25:54 -05:00
Pagwin
9271042e78
<, == and != done 2024-11-24 22:18:00 -05:00
Pagwin
09d2323cbf
did most of the misc stuff just need < and == 2024-11-24 20:42:13 -05:00
Pagwin
27409cc0b0
fixed memory issues, and bugs in insertion and deletion, added checks on the way 2024-11-24 15:20:10 -05:00
Pagwin
9cdecb790b
some progress was made 2024-11-24 02:08:39 -05:00
Pagwin
c3eaa8783b
I keep finding issues but none of them are "the" issue 2024-11-24 01:23:19 -05:00
Pagwin
94f53b7156
critical clue 2024-11-24 00:53:21 -05:00
10 changed files with 2078 additions and 142 deletions

17
Makefile Normal file
View file

@ -0,0 +1,17 @@
.PHONY: all
all: minimal morse perf test test-kec
minimal: Map.hpp minimal.cpp
g++ minimal.cpp -o minimal
morse: Map.hpp morseex.cpp
g++ morseex.cpp -o morse
perf: Map.hpp test-scaling.cpp
g++ test-scaling.cpp -o perf
test: Map.hpp test.cpp
g++ test.cpp -o test
test-kec: Map.hpp test-kec.cpp
g++ test-kec.cpp -o test-kec

558
Map.hpp
View file

@ -4,13 +4,16 @@
// uncomment on submission/performance test
// #define NDEBUG
#include <cassert>
#include <iostream>
#include <memory>
#include <optional>
#include <stdexcept>
#include <utility>
namespace cs440 {
// universal type defs here
namespace {
// technically having this stuff here instead of in a C++ file is wasteful but
// 1) I'm lazy
// 2) idk how to make the Color enum value an implementation detail which the
// template can use but external code can't other than this
enum class Direction { Left, Right };
Direction operator!(Direction dir) {
switch (dir) {
@ -31,8 +34,8 @@ template <typename Key_T, typename Mapped_T> class Map {
struct Node {
int valid = 0x13371337;
Node *parent;
internal_ValueType val;
Node *parent = nullptr;
std::unique_ptr<internal_ValueType> val;
std::unique_ptr<Node> left;
std::unique_ptr<Node> right;
Color color;
@ -40,44 +43,59 @@ template <typename Key_T, typename Mapped_T> class Map {
Node *next;
Map *map;
Node(internal_ValueType val, Map *map)
: parent{nullptr}, val{val}, left{}, right{}, color{Color::Red},
prev{nullptr}, next{nullptr}, map{map} {}
: parent{nullptr}, val{new internal_ValueType{val}}, left{}, right{},
color{Color::Red}, prev{nullptr}, next{nullptr}, map{map} {}
Node(const Node &rhs)
: parent{nullptr}, val{rhs.val},
left{std::make_unique<Node>(*rhs.left)},
right{std::make_unique<Node>(*rhs.right)}, color{rhs.color},
prev{nullptr}, next{nullptr}, map{rhs.map} {
: parent{nullptr},
val{rhs.val ? new internal_ValueType{*rhs.val} : nullptr},
left{rhs.left ? std::make_unique<Node>(*rhs.left) : nullptr},
right{rhs.right ? std::make_unique<Node>(*rhs.right) : nullptr},
color{rhs.color}, prev{nullptr}, next{nullptr}, map{rhs.map} {
this->valid = 0x13371337;
if (this->left) {
this->left->parent = this;
}
if (this->right) {
this->right->parent = this;
}
this->next = rhs.next;
this->prev = rhs.prev;
}
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}, map{rhs.map} {
if (rhs.valid != 0x13371337) {
std::cerr << "(" << rhs.val.first << ")" << std::endl;
}
rhs.valid = 0;
this->valid = 0x13371337;
if (this->left) {
this->left->parent = this;
}
if (this->right) {
this->right->parent = this;
}
this->next = rhs.next;
this->prev = rhs.prev;
}
~Node() {}
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<Node>(*rhs.left);
this->right = std::make_unique<Node>(*rhs.right);
this->val =
rhs.val ? std::unique_ptr<internal_ValueType>{new internal_ValueType{
*rhs.val}}
: nullptr;
this->left = rhs.left ? std::make_unique<Node>(*rhs.left) : nullptr;
this->right = rhs.right ? std::make_unique<Node>(*rhs.right) : nullptr;
this->color = rhs.color;
this->valid = 0x13371337;
if (this->left) {
this->left->parent = this;
this->left->restore_ordering();
@ -86,7 +104,7 @@ template <typename Key_T, typename Mapped_T> class Map {
this->right->parent = this;
this->right->restore_ordering();
}
this->restore_ordering();
this->map = rhs.map;
return *this;
}
@ -94,10 +112,12 @@ template <typename Key_T, typename Mapped_T> class Map {
// 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->val = std::move(rhs.val);
this->left = std::move(rhs.left);
this->right = std::move(rhs.right);
this->color = rhs.color;
this->valid = 0x13371337;
rhs.valid = 0;
if (this->left) {
this->left->parent = this;
this->left->restore_ordering();
@ -106,6 +126,7 @@ template <typename Key_T, typename Mapped_T> class Map {
this->right->parent = this;
this->right->restore_ordering();
}
this->restore_ordering();
this->map = rhs.map;
return *this;
}
@ -119,6 +140,16 @@ template <typename Key_T, typename Mapped_T> class Map {
assert(false);
}
}
Node const *child(Direction dir) const {
switch (dir) {
case Direction::Left:
return this->left.get();
case Direction::Right:
return this->right.get();
default:
assert(false);
}
}
std::unique_ptr<Node> uchild(Direction dir) {
switch (dir) {
case Direction::Left:
@ -135,15 +166,20 @@ template <typename Key_T, typename Mapped_T> class Map {
std::unique_ptr<Node> &set_child(Direction dir,
std::unique_ptr<Node> new_child) {
if (new_child) {
new_child->parent = this;
}
switch (dir) {
case Direction::Left:
return this->left = std::move(new_child);
this->left = std::move(new_child);
if (this->left) {
this->left->parent = this;
}
return this->left;
case Direction::Right:
return this->right = std::move(new_child);
this->right = std::move(new_child);
if (this->right) {
this->right->parent = this;
}
return this->right;
default:
assert(false);
}
@ -159,6 +195,8 @@ template <typename Key_T, typename Mapped_T> class Map {
}
void erase_child(Node *n) { this->erase_child(this->which_child(n)); }
void erase_child(Direction dir) {
bool minmax = this->child(dir) == this->map->min ||
this->child(dir) == this->map->max;
// bringing ownership to this function scope so Deleter gets called at end
// of function and we can do reordering things
std::unique_ptr<Node> dropping;
@ -182,6 +220,18 @@ template <typename Key_T, typename Mapped_T> class Map {
if (dropping->next != nullptr) {
dropping->next->prev = dropping->prev;
}
if (minmax) {
switch (dir) {
case Direction::Left:
this->map->min = this;
break;
case Direction::Right:
this->map->max = this;
break;
assert(false);
}
}
}
void restore_ordering() {
this->prev = this->calc_pred();
@ -231,29 +281,28 @@ template <typename Key_T, typename Mapped_T> class Map {
// 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));
Direction m_dir = this->parent->which_child(this);
// gotta pull outselves out of parent to avoid accidentally overwriting
// outselves
std::unique_ptr<Node> self = this->parent->uchild(!dir);
std::unique_ptr<Node> self = this->parent->uchild(m_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));
this->parent->set_child(m_dir, this->uchild(!dir));
// steal our former child's child
this->set_child(!dir, this->parent->child(!dir)->uchild(dir));
this->set_child(!dir, this->parent->child(m_dir)->uchild(dir));
// make ourselves our former child's child
this->parent->child(!dir)->set_child(dir, std::move(self));
this->parent->child(m_dir)->set_child(dir, std::move(self));
}
// Referencing
// https://en.wikipedia.org/wiki/Red%E2%80%93black_tree#Notes_to_the_insert_diagrams
@ -266,9 +315,11 @@ template <typename Key_T, typename Mapped_T> class Map {
Node *parent = self->parent;
// we're root, no-op (case 3)
if (!parent) {
self->color = Color::Black;
return;
}
dir = parent->which_child(self);
// if this is violated it's a bug
assert(parent->child(dir) == self);
@ -285,6 +336,7 @@ template <typename Key_T, typename Mapped_T> class Map {
return;
}
dir = parent->which_child(self);
// table showing transforms on wikipedia doesn't have this so if it
// happens it's probably a bug
assert(grandparent->color == Color::Black);
@ -294,7 +346,7 @@ template <typename Key_T, typename Mapped_T> class Map {
if (parent->which_child(self) != grandparent->which_child(parent)) {
// we're an inner child
// case 5
parent->rotate(dir);
parent->rotate(!dir);
self = parent;
parent = self->parent;
}
@ -303,10 +355,10 @@ template <typename Key_T, typename Mapped_T> class Map {
// recolor first so we aren't recoloring a dropped reference or smth
parent->color = Color::Black;
grandparent->color = Color::Red;
if (grandparent->parent == nullptr) {
map->rotate_root(!dir);
if (grandparent->parent) {
grandparent->rotate(!grandparent->which_child(parent));
} else {
grandparent->rotate(!dir);
map->rotate_root(!grandparent->which_child(parent));
}
return;
}
@ -321,13 +373,15 @@ template <typename Key_T, typename Mapped_T> class Map {
};
// data needed for implementation
std::optional<Node> root;
std::unique_ptr<Node> root;
std::size_t _size;
Node *min;
Node *max;
public:
friend Node;
class ConstIterator;
class ReverseIterator;
// public type definitions
class Iterator {
Node *underlying;
@ -337,102 +391,282 @@ public:
public:
friend Map;
friend ConstIterator;
friend ReverseIterator;
Iterator() = delete;
void check() {
assert(underlying->val.first < 200);
if (underlying->prev != nullptr) {
assert(underlying->prev->val.first < 200);
Iterator(const Iterator &rhs) = default;
Iterator &operator=(const Iterator &) = default;
~Iterator() = default;
// precrement
Iterator &operator++() {
if (this->underlying == nullptr) {
this->underlying = this->store;
this->store = nullptr;
return *this;
}
if (underlying->next != nullptr) {
assert(underlying->next->val.first < 200);
if (this->underlying->next == nullptr) {
this->store = this->underlying;
}
this->underlying = this->underlying->next;
return *this;
}
// postcrement
Iterator operator++(int) {
auto copy = *this;
this->operator++();
return copy;
}
// precrement
Iterator &operator--() {
if (this->underlying == nullptr) {
this->underlying = this->store;
this->store = nullptr;
return *this;
}
if (this->underlying->prev == nullptr) {
this->store = this->underlying;
}
this->underlying = this->underlying->prev;
return *this;
}
// postcrement
Iterator operator--(int) {
auto copy = *this;
this->operator--();
return copy;
}
ValueType &operator*() const {
ValueType *ret = (ValueType *)(this->underlying->val.get());
return *ret;
}
ValueType *operator->() const { return &this->operator*(); }
friend bool operator==(const Iterator &lhs, const Iterator &rhs) {
return lhs.underlying == rhs.underlying;
}
friend bool operator!=(const Iterator &lhs, const Iterator &rhs) {
return !(lhs == rhs);
}
};
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} {}
class ConstIterator {
Iterator underlying;
public:
friend Map;
ConstIterator() = delete;
ConstIterator(const Iterator &underlying) : underlying{underlying} {}
ConstIterator(const ConstIterator &rhs) = default;
ConstIterator &operator=(const ConstIterator &) = default;
~ConstIterator() = default;
ConstIterator &operator++() {
++underlying;
return *this;
}
ConstIterator operator++(int) {
auto copy = *this;
this->operator++();
return copy;
}
ConstIterator &operator--() {
--underlying;
return *this;
}
ConstIterator operator--(int) {
auto copy = *this;
this->operator--();
return copy;
}
const ValueType &operator*() const { return this->underlying.operator*(); }
const ValueType *operator->() const { return &this->operator*(); }
friend bool operator==(const ConstIterator &lhs, const ConstIterator &rhs) {
return lhs.underlying == rhs.underlying;
}
friend bool operator!=(const ConstIterator &lhs, const ConstIterator &rhs) {
return !(lhs == rhs);
}
friend bool operator==(const Iterator &lhs, const ConstIterator &rhs) {
return lhs == rhs.underlying;
}
friend bool operator!=(const Iterator &lhs, const ConstIterator &rhs) {
return !(lhs == rhs);
}
friend bool operator==(const ConstIterator &lhs, const Iterator &rhs) {
return lhs.underlying == rhs;
}
friend bool operator!=(const ConstIterator &lhs, const Iterator &rhs) {
return !(lhs == rhs);
}
};
class ReverseIterator {
Iterator underlying;
ReverseIterator(const Iterator &underlying) : underlying{underlying} {}
public:
friend Map;
ReverseIterator() = delete;
ReverseIterator(const ReverseIterator &) = default;
~ReverseIterator() = default;
ReverseIterator &operator=(const ReverseIterator &) = default;
ReverseIterator &operator++() {
--underlying;
return *this;
}
ReverseIterator operator++(int) {
auto copy = *this;
this->operator++();
return copy;
}
ReverseIterator &operator--() {
++underlying;
return *this;
}
ReverseIterator operator--(int) {
auto copy = *this;
this->operator++();
return copy;
}
const ValueType &operator*() const { return this->underlying.operator*(); }
const ValueType *operator->() const { return &this->operator*(); }
friend bool operator==(const ReverseIterator &lhs,
const ReverseIterator &rhs) {
return lhs.underlying == rhs.underlying;
}
friend bool operator!=(const ReverseIterator &lhs,
const ReverseIterator &rhs) {
return !(lhs == rhs);
}
};
Map() : root{}, _size{0}, min{nullptr}, max{nullptr} {}
Map(const Map &rhs)
: root{std::make_unique<Node>(*rhs.root)}, _size{rhs._size} {
this->min = this->root.get();
this->max = this->root.get();
while (min->left) {
min = min->left.get();
}
while (max->right) {
max = max->right.get();
}
}
Map(Map &&rhs) : root{std::move(rhs.root)}, _size{rhs._size} {
this->min = this->root.get();
this->max = this->root.get();
while (min->left) {
min = min->left.get();
}
while (max->right) {
max = max->right.get();
}
}
Map &operator=(const Map &rhs) {
this->root = rhs.root;
this->root = std::make_unique<Node>(*rhs.root);
this->_size = rhs._size;
this->min = this->root.get();
this->max = this->root.get();
while (min->left) {
min = min->left.get();
}
while (max->right) {
max = max->right.get();
}
return *this;
}
Map &operator=(Map &&rhs) {
this->root = std::move(rhs.root);
this->_size = rhs._size;
this->min = this->root.get();
this->max = this->root.get();
while (min->left) {
min = min->left.get();
}
while (max->right) {
max = max->right.get();
}
return *this;
}
std::size_t size() { return this->_size; }
Map(std::initializer_list<std::pair<const Key_T, Mapped_T>> items) : Map{} {
this->insert(items.begin(), items.end());
}
std::size_t size() const { return this->_size; }
bool empty() const { return this->size() == 0; }
private:
// private helpers
void rotate_root(Direction dir) {
assert(root.has_value());
assert(root);
std::unique_ptr<Node> new_root = root.value().uchild(!dir);
std::unique_ptr<Node> new_root = root->uchild(!dir);
// can't make null the new root
assert(new_root);
std::unique_ptr<Node> old_root =
std::make_unique<Node>(std::move(root.value()));
std::unique_ptr<Node> old_root = std::move(root);
root.value() = std::move(*new_root);
root = std::move(new_root);
root->parent = nullptr;
old_root->set_child(!dir, root.value().uchild(dir));
root.value().set_child(dir, std::move(old_root));
old_root->set_child(!dir, root->uchild(dir));
if (old_root->left) {
old_root->left->parent = old_root.get();
}
if (old_root->right) {
old_root->right->parent = old_root.get();
}
// if (old_root->next) {
// old_root->next->prev = old_root.get();
// }
// if (old_root->prev) {
// old_root->prev->next = old_root.get();
// }
root->set_child(dir, std::move(old_root));
root->child(dir)->restore_ordering();
if (root->left) {
root->left->parent = root.get();
if (min == root.get()) {
min = root->left.get();
}
}
if (root->right) {
root->right->parent = root.get();
if (max == root.get()) {
max = root->right.get();
}
}
}
template <bool trace = false>
std::pair<Node *, Direction> locate(const Key_T &key) {
Node *ret_parent;
std::pair<Node const *, Direction> locate(const Key_T &key) const {
Node const *ret_parent;
Direction ret_dir;
// map is empty
if (!this->root.has_value()) {
if constexpr (trace) {
std::cerr << "(map empty)" << std::endl;
}
if (!this->root) {
return std::make_pair(nullptr, ret_dir);
}
if constexpr (trace) {
std::cerr << "root";
}
// value is in root
if (this->root.value().val.first == key) {
if constexpr (trace) {
std::cerr << "->found" << std::endl;
}
if (this->root->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_parent = this->root.get();
if (key < ret_parent->val->first) {
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->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";
}
if (key < ret_parent->val->first) {
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);
}
void hard_erase(Node *n) {
@ -466,7 +700,7 @@ private:
// bookkeeping
redcheck(sibling_color) {
// case 3
if (parent->parent != nullptr) {
if (parent->parent) {
parent->rotate(dir);
} else {
parent->map->rotate_root(dir);
@ -503,7 +737,7 @@ private:
assert(parent);
assert(sibling);
assert(distant);
if (parent->parent != nullptr) {
if (parent->parent) {
parent->rotate(dir);
} else {
parent->map->rotate_root(dir);
@ -532,35 +766,38 @@ private:
}
}
bool core_erase(Node *erasing) {
Color c = erasing->color;
// 2 children
if (erasing->left && erasing->right) {
Node *succ = erasing->next;
erasing->val = succ->val;
erasing->val = std::move(succ->val);
this->core_erase(succ);
}
// 1 child
else if (erasing->left) {
*erasing = std::move(*erasing->left);
*erasing = std::move(*erasing->left.release());
if (erasing->prev != nullptr) {
erasing->prev->next = erasing;
}
if (erasing->next != nullptr) {
erasing->next->prev = erasing;
}
erasing->color = c;
return true;
} else if (erasing->right) {
*erasing = std::move(*erasing->right);
*erasing = std::move(*erasing->right.release());
if (erasing->prev != nullptr) {
erasing->prev->next = erasing;
}
if (erasing->next != nullptr) {
erasing->next->prev = erasing;
}
erasing->color = c;
return true;
}
// no children and root
else if (!erasing->parent) {
erasing->map->root = std::nullopt;
else if (erasing->parent == nullptr) {
erasing->map->root = nullptr;
}
// no children and red
else if (erasing->color == Color::Red) {
@ -578,30 +815,30 @@ public:
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()};
if (this->root) {
if (this->root->val->first == key) {
return Iterator{this->root.get()};
}
}
return this->end();
}
if (parent->child(dir) != nullptr) {
return Iterator{parent->child(dir), nullptr};
return Iterator{const_cast<Node *>(parent->child(dir)), nullptr};
}
return this->end();
}
Iterator find_trace(const Key_T &key) {
auto [parent, dir] = locate<true>(key);
ConstIterator find(const Key_T &key) const {
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()};
if (this->root) {
if (this->root->val->first == key) {
return Iterator{const_cast<Node *>(this->root.get())};
}
}
return this->end();
}
if (parent->child(dir) != nullptr) {
return Iterator{parent->child(dir), nullptr};
return Iterator{const_cast<Node *>(parent->child(dir)), nullptr};
}
return this->end();
}
@ -613,45 +850,71 @@ public:
auto [parent, dir] = locate(key);
// located root node
if (parent == nullptr) {
if (this->root.has_value()) {
return std::make_pair(Iterator{&root.value()}, false);
if (this->root) {
return std::make_pair(Iterator{root.get()}, false);
} else {
this->root = Node{val, this};
return std::make_pair(Iterator{&root.value()}, true);
this->root = std::make_unique<Node>(Node{val, this});
this->root->color = Color::Black;
this->min = this->root.get();
this->max = this->root.get();
this->_size++;
return std::make_pair(Iterator{root.get()}, true);
}
}
// non-root node
if (parent->child(dir)) {
// node already present
return std::make_pair(Iterator{parent->child(dir)}, false);
return std::make_pair(Iterator{const_cast<Node *>(parent->child(dir))},
false);
}
// need to insert non-root node
Node *new_node =
parent->set_child(dir, std::make_unique<Node>(Node{val, this})).get();
Map *m = const_cast<Map *>(this);
Node *new_node = const_cast<Node *>(parent)
->set_child(dir, std::make_unique<Node>(Node{val, m}))
.get();
new_node->restore_red_black_insert(dir);
// if the new_node_ptr isn't valid anymore then it has to be root
if (new_node->valid != 0x13371337) {
new_node = this->root.get();
}
new_node->restore_ordering();
if (this->min == parent && dir == Direction::Left) {
this->min = new_node;
}
if (this->max == parent && dir == Direction::Right) {
this->max = new_node;
}
this->_size++;
return std::make_pair(Iterator{new_node}, true);
}
void erase(Iterator pos) {
if (pos.underlying == nullptr) {
return;
}
this->_size--;
Node *before = pos.underlying->prev;
Node *after = pos.underlying->next;
if (core_erase(pos.underlying)) {
pos.underlying->restore_ordering();
} else {
if (before != nullptr) {
if (before != nullptr && before->valid == 0x13371337) {
before->next = before->calc_succ();
if (before->next != nullptr) {
before->next->prev = before;
}
} else {
this->min = after;
}
if (after != nullptr) {
if (after != nullptr && after->valid == 0x13371337) {
after->prev = after->calc_pred();
if (after->prev != nullptr) {
after->prev->next = after;
}
} else {
this->max = before;
}
}
}
@ -659,6 +922,75 @@ public:
// baseline iterator creation
Iterator begin() { return Iterator{min, nullptr}; }
Iterator end() { return Iterator{nullptr, max}; }
ConstIterator begin() const { return Iterator{min, nullptr}; }
ConstIterator end() const { return Iterator{nullptr, max}; }
ReverseIterator rbegin() { return Iterator{max, nullptr}; }
ReverseIterator rend() { return Iterator{nullptr, min}; }
// misc that can be implemented with the above or trivially
void clear() {
this->root = std::move(nullptr);
this->_size = 0;
}
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) {
this->insert({key, {}});
return this->at(key);
}
template <typename IT_T> void insert(IT_T range_beg, IT_T range_end) {
while (range_beg != range_end) {
auto [first, second] = *range_beg;
this->insert(std::make_pair(first, second));
++range_beg;
}
}
void erase(const Key_T &key) { this->erase(this->find(key)); }
friend bool operator==(const Map &lhs, const Map &rhs) {
if (lhs.size() != rhs.size()) {
return false;
}
auto lhs_iter = lhs.begin();
auto rhs_iter = rhs.begin();
while (lhs_iter != lhs.end()) {
if (*lhs_iter != *rhs_iter) {
return false;
}
++lhs_iter;
++rhs_iter;
}
return true;
}
friend bool operator!=(const Map &lhs, const Map &rhs) {
return !(lhs == rhs);
}
friend bool operator<(const Map &lhs, const Map &rhs) {
auto lhs_iter = lhs.begin();
auto rhs_iter = rhs.begin();
while (lhs_iter != lhs.end() && rhs_iter != rhs.end()) {
if (*lhs_iter < *rhs_iter) {
return true;
}
if (*lhs_iter != *rhs_iter) {
return false;
}
++lhs_iter;
++rhs_iter;
}
return lhs.size() < rhs.size();
}
};
} // namespace cs440
#endif

119
minimal.cpp Normal file
View file

@ -0,0 +1,119 @@
#include "Map.hpp"
#include <cassert>
// basically an int wrapper
class MyKeyType {
private:
int val;
public:
// not default constructable, not copy assignable, not move assignable
MyKeyType() = delete;
MyKeyType &operator=(const MyKeyType &) = delete;
MyKeyType &operator=(MyKeyType &&) = delete;
// copy constructable and move assignable
MyKeyType(MyKeyType &&) = default;
MyKeyType(const MyKeyType &) = default;
~MyKeyType() = default;
MyKeyType(int i) : val(i) {}
bool operator<(const MyKeyType &other) const { return this->val < other.val; }
bool operator==(const MyKeyType &other) const {
return this->val == other.val;
}
};
// same as keytype except no operator<
class MyValueType {
private:
int val;
public:
// not default constructable, not copy assignable, not move assignable
MyValueType() = delete;
MyValueType &operator=(const MyValueType &) = delete;
MyValueType &operator=(MyValueType &&) = delete;
// copy constructable and move assignable
MyValueType(MyValueType &&) = default;
MyValueType(const MyValueType &) = default;
~MyValueType() = default;
MyValueType(int i) : val(i) {}
bool operator==(const MyValueType &other) const {
return this->val == other.val;
}
};
class MyDefaultConstructible {
friend bool operator<(const MyDefaultConstructible &o1,
const MyDefaultConstructible &o2) {
return o1.val < o2.val;
}
private:
int val = 0;
public:
// not copy assignable, not move assignable
MyDefaultConstructible &operator=(const MyDefaultConstructible &) = delete;
MyDefaultConstructible &operator=(MyDefaultConstructible &&) = delete;
// default constructable, copy constructable and move assignable
MyDefaultConstructible() = default;
MyDefaultConstructible(MyDefaultConstructible &&) = default;
MyDefaultConstructible(const MyDefaultConstructible &) = default;
~MyDefaultConstructible() = default;
MyDefaultConstructible(int i) : val(i) {}
bool operator==(const MyDefaultConstructible &other) const {
return this->val == other.val;
}
};
class MyAssignable {
private:
int val = 0;
public:
MyAssignable() = default;
MyAssignable(int i) : val(i) {}
bool operator==(const MyAssignable &other) const {
return this->val == other.val;
}
};
// manual instantiation, instantiates every member function instead of
// just the ones called
template class cs440::Map<MyKeyType, MyDefaultConstructible>;
int main() {
cs440::Map<MyKeyType, MyValueType> m{{3, 5}};
m.insert({{2}, {3}});
m.insert({{1}, {3}});
m.insert({{5}, {3}});
m.insert({{7}, {3}});
m.at(2);
auto iter = m.find(2);
m.erase(iter);
auto m_copy = m;
assert(m_copy == m);
cs440::Map<MyKeyType, MyDefaultConstructible> m2{{8, 9}};
m2[10]; // should default construct these values
m2[15];
cs440::Map<MyKeyType, MyAssignable> m3{{6, 7}};
m3[20] = {5}; // move assign
MyAssignable ma{1};
m3[10] = ma; // copy assign
return 0;
}

67
morseex.cpp Normal file
View file

@ -0,0 +1,67 @@
#include "Map.hpp"
#include <iostream>
#include <string>
#include <stdexcept>
#include <cctype>
cs440::Map<char, std::string> morse {
{',', "--..--"},
{'.', ".-.-.-"},
{'?', "..--.."},
{'0', "----- "},
{'1', ".---- "},
{'2', "..--- "},
{'3', "...-- "},
{'4', "....- "},
{'5', "..... "},
{'6', "-.... "},
{'7', "--... "},
{'8', "---.. "},
{'9', "----. "},
{'A', ".-"},
{'B', "-..."},
{'C', "-.-."},
{'D', "-.. "},
{'E', "."},
{'F', "..-."},
{'G', "--."},
{'H', "...."},
{'I', ".."},
{'J', ".---"},
{'K', "-.-"},
{'L', ".-.."},
{'M', "--"},
{'N', "-."},
{'O', "---"},
{'P', ".--."},
{'Q', "--.-"},
{'R', ".-."},
{'S', "..."},
{'T', "-"},
{'U', "..-"},
{'V', "...-"},
{'W', ".--"},
{'X', "-..-"},
{'Y', "-.--"},
{'Z', "--.."},
};
int main() {
std::cout << "Enter messages to be translated (CTRL+D to exit)\n";
std::string message;
while(std::cin >> message) {
for (auto c : message) {
try {
std::cout << morse.at(toupper(c)) << '\n';
} catch (std::out_of_range) {
std::cout << "invalid character: " << c << '\n';
}
}
}
return 0;
}

View file

@ -1,13 +1 @@
suspects:
core_erase
erase_child
hard_erase
rotate
Soft okay:
assignment operator for node specifically dealing with the parent pointer if we assign to something and then don't immediately set the parent appropriately bad things happen, just setting to rhs didn't fix but it also didn't break anything so it's soft okay
rotation is definitely wrong somewhere, knowing me rotate_root, somehow 1 ended up right of 2

30
t.cpp
View file

@ -1,22 +1,20 @@
#include "Map.hpp"
#include <utility>
template class cs440::Map<int, int>;
int main(void) {
cs440::Map<int, int> a;
a.insert({1, 1});
a.insert({2, 2});
a.insert({3, 3});
a.insert({4, 4});
a.insert({5, 5});
a.insert({6, 6});
a.insert({7, 7});
a.insert({8, 8});
a.insert({9, 9});
a.insert({10, 10});
for (std::size_t i = 1; i <= 10; i++) {
// a.find(i).check();
std::cout << i << std::endl;
a.erase(a.find(i));
}
// cs440::Map<int, int> a;
// for (std::size_t i = 10; i >= 1; i--) {
// a.insert({i, i});
// for (std::size_t j = 10; j >= i; j--) {
// }
// }
// for (std::size_t i = 10; i >= 5; i--) {
// std::cout << i << std::endl;
// auto b = a.find(i);
// a.erase(b);
// for (std::size_t j = 1; j <= i; j++) {
// }
// }
return 0;
}

35
ta.cpp Normal file
View file

@ -0,0 +1,35 @@
#include "Map.hpp"
#include <sstream>
#include <string>
template class cs440::Map<int, int>;
int main(void) {
cs440::Map<std::string, int> a;
std::stringstream ss;
std::string s;
for (std::size_t i = 10; i >= 1; i--) {
ss << i;
ss >> s;
a.insert({s, i});
for (std::size_t j = 10; j >= i; j--) {
ss << j;
ss >> s;
}
}
for (std::size_t i = 1; i <= 10; i++) {
ss << i;
ss >> s;
}
for (std::size_t i = 10; i >= 5; i--) {
std::cout << i << std::endl;
ss << i;
ss >> s;
auto b = a.find(s);
a.erase(b);
for (std::size_t j = 1; j <= i; j++) {
ss << j;
ss >> s;
}
}
return 0;
}

620
test-kec.cpp Normal file
View file

@ -0,0 +1,620 @@
/*
* Run with
*
* -i iterations
*
* to do a stress test for the given number of iterations.
*
* -p
*
* to print correct output.
*/
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
#include <set>
#include <vector>
#include <map>
#include <utility>
#include "Map.hpp"
/*
* Wrapper class around std::map to handle slight difference in return value and also
* provide an Iterator nested name.
*/
template <typename K, typename V>
class test_map : public std::map<K, V> {
private:
using base_t = std::map<K, V>;
public:
using Iterator = typename base_t::iterator;
std::pair<typename base_t::iterator, bool>insert(const std::pair<const K, V> &p) {
return this->base_t::insert(p);
}
};
/*
* Person class.
*/
struct Person {
friend bool operator<(const Person &p1, const Person &p2) {
return p1.name < p2.name;
}
friend bool operator==(const Person &p1, const Person &p2) {
return p1.name == p2.name;
}
Person(const char *n) : name(n) {}
void print() const {
printf("Name: %s\n", name.c_str());
}
const std::string name;
Person &operator=(const Person &) = delete;
};
void
print(const std::pair<const Person, int> &p) {
p.first.print();
printf(" %d\n", p.second);
}
/*
* MyClass class.
*/
struct MyClass {
friend bool operator<(const MyClass &o1, const MyClass &o2) {
return o1.num < o2.num;
}
friend bool operator==(const MyClass &o1, const MyClass &o2) {
return o1.num == o2.num;
}
MyClass(double n) : num(n) {}
double num;
};
void
print(const std::pair<const int, std::string> &p) {
printf("%d, %s; ", p.first, p.second.c_str());
}
/*
* Stress class.
*/
struct Stress {
friend bool operator<(const Stress& o1, const Stress& o2) {
return o1.val < o2.val;
}
friend bool operator==(const Stress& o1, const Stress& o2) {
return o1.val == o2.val;
}
Stress(int _v) : val(_v){}
int val;
};
// Helper function for stress testing. This orders iterators by what they point to.
template <template <typename, typename> class MAP_T>
inline bool
less(const typename MAP_T<const Stress, double>::Iterator &lhs, const typename MAP_T<const Stress, double>::Iterator &rhs) {
return (*lhs).first.val < (*rhs).first.val;
}
/*
* Additional test functions for BST.
*/
template <template <typename, typename> class MAP_T>
void traverse(const MAP_T<const Person, int> &, int level);
template <template <typename, typename> class MAP_T>
void traverse2(int level);
template <template <typename, typename> class MAP_T>
void check(const MAP_T<const Stress, double> &, const std::map<const Stress, double> &);
/*
* The actual test code. It's a template so that it can be run with the std::map and the
* assignment Map.
*/
template <template <typename, typename> class MAP_T>
void
run_test(int iterations) {
/*
* Test with Person.
*/
{
Person p1("Jane");
Person p2("John");
Person p3("Mary");
Person p4("Dave");
MAP_T<const Person, int> map;
// Insert people into the map.
auto p1_it = map.insert(std::make_pair(p1, 1));
map.insert(std::make_pair(p2, 2));
map.insert(std::make_pair(p3, 3));
map.insert(std::make_pair(p4, 4));
// Check iterator equality.
{
// Returns an iterator pointing to the first element.
auto it1 = map.begin();
// Returns an iterator pointing to one PAST the last element. This
// iterator is obviously conceptual only. It cannot be
// dereferenced.
auto it2 = map.end();
it1++; // Second node now.
it1++; // Third node now.
it2--; // Fourth node now.
it2--; // Third node now.
assert(it1 == it2);
it2--; // Second node now.
it2--; // First node now.
assert(map.begin() == it2);
}
// Check insert return value.
{
printf("---- Test insert() return.\n");
// Insert returns an interator. If it's already in, it returns an
// iterator to the already inserted element.
auto it = map.insert(std::make_pair(p1, 1));
assert(it.first == p1_it.first);
// Now insert one that is new.
it = map.insert(std::make_pair(Person("Larry"), 5));
print(*(it.first));
map.erase(it.first);
}
// Print the whole thing now, to verify ordering.
printf("---- Before erasures.\n");
// Iterate through the whole map, and call print() on each Person.
for (auto &e : map) {
print(e);
}
// Test multiple traversals of the same map.
printf("---- Multiple traversals\n");
traverse(map, 4);
// Test multiple BST at the same time.
printf("---- Multiple BST\n");
traverse2<MAP_T>(4);
/*
* Test some erasures.
*/
// Erase first element.
map.erase(map.begin());
auto it = map.end();
--it; // it now points to last element.
it--; // it now points to penultimate element.
map.erase(it);
printf("---- After erasures.\n");
// Iterate through the whole map, and call print() on each Person.
for (auto &e : map) {
print(e);
}
// Test iterator validity.
{
// Iterators must be valid even when other things are inserted or
// erased.
printf("---- Test iterator non-invalidation\n");
// Get iterator to the first.
auto b = map.begin();
// Insert element which will be at the end.
auto it = map.insert(std::make_pair(Person("Zeke"), 10));
// Iterator to the first should still be valid.
print(*b);
// Delete first, saving the actual object.
auto tmp(*b); // Save, so we can reinsert.
map.erase(map.begin()); // Erase it.
// Check iterator for inserted. Iterator to end should still be valid.
print(*it.first); // This should still be valid.
// Reinsert first element.
map.insert(tmp);
// Erase inserted last element.
map.erase(it.first);
}
}
/*
* Test Map with MyClass.
*/
{
MAP_T<const MyClass, std::string> map;
// Empty container, should print nothing.
for (auto it = map.begin(); it != map.end(); ++it) {
abort();
}
MyClass m1(0), m2(3), m3(1), m4(2);
auto m1_it = map.insert(std::make_pair(m1, "mmm1"));
map.insert(std::make_pair(m2, "mmm2"));
map.insert(std::make_pair(m3, "mmm3"));
map.insert(std::make_pair(m4, "mmm4"));
// Should print 0.0 1.0 2.0 3.0
for (auto &e : map) {
printf("%3.1f ", e.first.num);
}
printf("\n");
// Check return value of insert.
{
// Already in, so must return equal to m1_it.
auto it = map.insert(std::make_pair(m1, "mmm1"));
assert(it.first == m1_it.first);
}
// Erase the first element.
map.erase(map.begin());
// Should print "1.0 2.0 3.0".
for (auto &e : map) {
printf("%3.1f ", e.first.num);
}
printf("\n");
// Erase the new first element.
map.erase(map.begin());
// Should print "2.0 3.0".
for (auto &e : map) {
printf("%3.1f ", e.first.num);
}
printf("\n");
map.erase(--map.end());
// Should print "2.0".
for (auto &e : map) {
printf("%3.1f ", e.first.num);
}
printf("\n");
// Erase the last element.
map.erase(map.begin());
// Should print nothing.
for (auto &e : map) {
printf("%3.1f ", e.first.num);
}
printf("\n");
}
/*
* Test Map with plain int.
*/
{
MAP_T<const int, std::string> map;
// Empty container, should print nothing.
for (auto &e : map) {
printf("%d ", e.first);
}
auto p1(std::make_pair(4, "444"));
auto p2(std::make_pair(3, "333"));
auto p3(std::make_pair(0, "000"));
auto p4(std::make_pair(2, "222"));
auto p5(std::make_pair(1, "111"));
map.insert(p1);
map.insert(p2);
map.insert(p3);
map.insert(p4);
map.insert(p5);
// Should print "0 1 2 3 4".
for (auto it = map.begin(); it != map.end(); ++it) {
print(*it);
}
printf("\n");
// Insert dupes.
map.insert(p4);
map.insert(p1);
map.insert(p3);
map.insert(p2);
map.insert(p5);
// Should print "0 1 2 3 4".
for (auto it = map.begin(); it != map.end(); ++it) {
print(*it);
}
printf("\n");
// Erase the first element.
map.erase(map.begin());
// Erase the new first element.
map.erase(map.begin());
// Erase the element in the end.
map.erase(--map.end());
// Should print "2 3".
for (auto &e : map) {
print(e);
}
printf("\n");
// Erase all elements.
map.erase(map.begin());
map.erase(map.begin());
// Should print nothing.
for (auto &e : map) {
print(e);
}
printf("\n");
}
/*
* Stress test Map.
*/
if (iterations > 0) {
MAP_T<const Stress, double> map;
using it_t = typename MAP_T<const Stress, double>::Iterator;
using mirror_t = std::map<const Stress, double>;
mirror_t mirror;
using iters_t = std::set<it_t, bool(*)(const it_t &lhs, const it_t &rhs)>;
iters_t iters(&less<MAP_T>);
std::cout << "---- Starting stress test:" << std::endl;
const int N = iterations;
srand(9757);
int n_inserted = 0, n_erased = 0, n_iters_changed = 0, n_empty = 0, n_dupes = 0;
double avg_size = 0;
for (int i = 0; i < N; ++i) {
double op = drand48();
// The probability of removal should be slightly higher than the
// probability of insertion so that the map is often empty.
if (op < .44) {
// Insert an element. Repeat until no duplicate.
do {
// Limit the range of values of Stress so that we get some dupes.
auto v(std::make_pair(Stress(rand()%50000), drand48()));
auto find_it = map.find(v.first);
auto it = map.insert(v);
auto mir_res = mirror.insert(v);
if (mir_res.second) {
// If insert into mirror succeeded, insert into the map
// should also have succeeded. It should not have
// found it before insert.
assert(find_it == map.end());
// Store the iterator.
iters.insert(it.first);
break;
}
// If insert into mirror did not succeed, insert into map
// should also not have succeeded, in which case, we
// generate another value to store. Also, find should have
// found it, and insert should have returned same iterator.
assert(find_it == it.first);
n_dupes++;
} while (true);
++n_inserted;
} else if (op < .90) {
// Erase an element.
if (iters.size() != 0) {
// Pick a random index.
int index = rand()%iters.size();
typename iters_t::iterator iit = iters.begin();
while(index--) {
++iit;
}
auto it = *iit;
// The iterator should not be end()
assert(it != map.end());
Stress s((*it).first);
mirror.erase(s);
iters.erase(iit);
map.erase(it);
++n_erased;
}
} else {
// Does either postfix or prefix inc/dec operation.
auto either_or = [&](it_t &it, it_t &(it_t::*f1)(), it_t (it_t::*f2)(int)) {
if (rand()%2 == 0) {
(it.*f1)();
} else {
(it.*f2)(0);
}
};
// Increment or decrement an iterator.
// Size of containers should be same
assert(iters.size() == mirror.size());
// If the container is empty, skip
if (iters.size() != 0) {
// Pick a random index
int index = rand()%iters.size();
typename iters_t::iterator iters_it = iters.begin();
while (index--) {
++iters_it;
}
auto it = *iters_it;
// The iterator should not be end().
assert(it != map.end());
// If it is the begin(), then only increment,
// otherwise, pick either forward or backward.
if (it == map.begin()) {
either_or(it, &it_t::operator++, &it_t::operator++);
++iters_it;
} else {
if (rand()%2 == 0) {
either_or(it, &it_t::operator++, &it_t::operator++);
++iters_it;
} else {
either_or(it, &it_t::operator--, &it_t::operator--);
--iters_it;
}
}
// If we didn't hit the end, replace the resulting iterator
// in the iterator list.
// Note that the set is sorted.
if (it != map.end()) {
assert(it == *iters_it);
iters.erase(iters_it);
iters.insert(it);
}
}
++n_iters_changed;
}
avg_size += double(iters.size())/N;
if (iters.size() == 0) {
++n_empty;
}
check(map, mirror);
}
std::cout << "inserted: " << n_inserted << " times" << std::endl;
std::cout << "erased: " << n_erased << " times" << std::endl;
std::cout << "iterators changed: " << n_iters_changed << " times" << std::endl;
std::cout << "empty count: " << n_empty << std::endl;
std::cout << "avg size: " << avg_size << std::endl;
std::cout << "n dupes: " << n_dupes << std::endl;
}
}
/*
* Main.
*/
int
main(int argc, char *argv[]) {
bool correct_output = false;
int iterations = 0;
{
int c;
while ((c = getopt(argc, argv, "pi:")) != EOF) {
switch (c) {
case 'p':
correct_output = true;
break;
case 'i':
iterations = atoi(optarg);
break;
case '?':
fprintf(stderr, "Unrecog.\n");
exit(1);
}
}
}
srand48(1234);
if (correct_output) {
run_test<test_map>(iterations);
} else {
run_test<cs440::Map>(iterations);
}
}
template <template <typename, typename> class MAP_T>
void
check(const MAP_T<const Stress, double> &map, const std::map<const Stress, double> &mirror) {
// Check if the reference container and stress container is identical
auto it = map.begin();
auto mit = mirror.begin();
for( ; it != map.end() && mit != mirror.end(); ++it, ++mit) {
if ((*it).first == (*mit).first) {
if ((*it).second == (*mit).second) {
continue;
}
}
fprintf(stderr, "Reference tree and test tree differ.\n");
abort();
}
if (it != map.end() || mit != mirror.end()) {
fprintf(stderr, "Reference tree and test tree differ.\n");
abort();
}
}
// Test single list being traversed by multiple iterators simultaneously.
template <template <typename, typename> class MAP_T>
void
traverse(const MAP_T<const Person, int> &m, int level) {
for (auto it = m.begin(); it != m.end(); ++it) {
print(*it);
if (level != 0) {
traverse(m, level - 1);
}
}
}
// Test multiple lists and multiple iterators.
template <template <typename, typename> class MAP_T>
void
traverse2(int level) {
MAP_T<const Person, int> map;
for (int i = 0; i < 4; i++) {
char name[30];
sprintf(name, "Jane%d", int(10000*drand48()));
printf("Generated name: %s\n", name);
map.insert(std::make_pair(Person(name), 10*level + i));
}
for (auto &e : map) {
print(e);
if (level != 0) {
traverse2<MAP_T>(level - 1);
}
}
}

620
test-scaling.cpp Normal file
View file

@ -0,0 +1,620 @@
#include "Map.hpp"
#include <chrono>
#include <random>
#include <iostream>
#include <typeinfo>
#include <cxxabi.h>
#include <assert.h>
#include <map>
#include <initializer_list>
#include <set>
//Enables iteration test on a map larger than the memory available to the remote cluster
//WARNING: This will be VERY slow.
#define DO_BIG_ITERATION_TEST 0
namespace cs440 {
template <typename K, typename V>
class StdMapWrapper {
private:
using base_map = std::map<K, V>;
public:
typedef typename base_map::iterator Iterator;
typedef typename base_map::const_iterator ConstIterator;
typedef typename base_map::reverse_iterator ReverseIterator;
typedef typename base_map::const_reverse_iterator ConstReverseIterator;
typedef typename base_map::value_type value_type;
typedef typename base_map::mapped_type mapped_type;
typedef typename base_map::key_type key_type;
StdMapWrapper() {}
StdMapWrapper(std::initializer_list<std::pair<K,V>> il) {
for(auto x : il) {
m_map.insert(x);
}
}
StdMapWrapper(StdMapWrapper &&other)
: m_map(std::move(other.m_map))
{}
StdMapWrapper(const StdMapWrapper &other)
: m_map(other.m_map)
{}
StdMapWrapper &operator=(const StdMapWrapper &other) {
if(this != &other) {
StdMapWrapper tmp(other);
std::swap(m_map, tmp.m_map);
}
return *this;
}
StdMapWrapper &operator=(const StdMapWrapper &&other) {
StdMapWrapper tmp(other);
std::swap(tmp.m_map, m_map);
return *this;
}
///////// Iterators
Iterator begin() {
return m_map.begin();
}
ConstIterator begin() const {
return m_map.begin();
}
ConstIterator cbegin() const {
return m_map.begin();
}
ReverseIterator rbegin() {
return m_map.rbegin();
}
/*
ConstReverseIterator rbegin() const {
return m_map.rbegin();
}
ConstReverseIterator crbegin() const {
return m_map.crbegin();
}
*/
Iterator end() {
return m_map.end();
}
ConstIterator end() const {
return m_map.end();
}
ConstIterator cend() const {
return m_map.cend();
}
ReverseIterator rend() {
return m_map.rend();
}
/*
ConstReverseIterator rend() const {
return m_map.rend();
}
ConstReverseIterator crend() const {
return m_map.crend();
}
*/
///////// Capacity
size_t size() const {
return m_map.size();
}
size_t max_size() const {
return m_map.max_size();
}
bool empty() const {
return m_map.empty();
}
///////// Modifiers
Iterator insert(const value_type &value) {
return m_map.insert(value).first;
}
Iterator insert(value_type &&value) {
return m_map.insert(std::move(value)).first;
}
void erase(const K &k) {
m_map.erase(k);
}
void erase(Iterator it) {
m_map.erase(it);
}
///////// Lookup
V &at(const K &k) {
return m_map.at(k);
}
const V &at(const K &k) const {
return m_map.at(k);
}
Iterator find(const K &k) {
return m_map.find(k);
}
ConstIterator find(const K &k) const {
return m_map.find(k);
}
V &operator[](const K &k) {
return m_map[k];
}
private:
base_map m_map;
template<typename A, typename B>
friend
bool operator==(const StdMapWrapper<A,B>&, const StdMapWrapper<A,B>&);
template<typename A, typename B>
friend bool operator!=(const StdMapWrapper<A,B>&, const StdMapWrapper<A,B>&);
template<typename A, typename B>
friend bool operator<=(const StdMapWrapper<A,B>&, const StdMapWrapper<A,B>&);
template<typename A, typename B>
friend bool operator<(const StdMapWrapper<A,B>&, const StdMapWrapper<A,B>&);
template<typename A, typename B>
friend bool operator>=(const StdMapWrapper<A,B>&, const StdMapWrapper<A,B>&);
template<typename A, typename B>
friend bool operator>(const StdMapWrapper<A,B>&, const StdMapWrapper<A,B>&);
};
template<typename K, typename T>
bool operator==(const StdMapWrapper<K,T> &a, const StdMapWrapper<K,T> &b) {
return a.m_map == b.m_map;
}
template<typename K, typename T>
bool operator!=(const StdMapWrapper<K,T> &a, const StdMapWrapper<K,T> &b) {
return a.m_map != b.m_map;
}
template<typename K, typename T>
bool operator<=(const StdMapWrapper<K,T> &a, const StdMapWrapper<K,T> &b) {
return a.m_map <= b.m_map;
}
template<typename K, typename T>
bool operator<(const StdMapWrapper<K,T> &a, const StdMapWrapper<K,T> &b) {
return a.m_map < b.m_map;
}
template<typename K, typename T>
bool operator>=(const StdMapWrapper<K,T> &a, const StdMapWrapper<K,T> &b) {
return a.m_map >= b.m_map;
}
template<typename K, typename T>
bool operator>(const StdMapWrapper<K,T> &a, const StdMapWrapper<K,T> &b) {
return a.m_map > b.m_map;
}
}
using Milli = std::chrono::duration<double, std::ratio<1,1000>>;
using TimePoint = std::chrono::time_point<std::chrono::system_clock>;
void dispTestName(const char *testName, const char *typeName) {
std::cout << std::endl << std::endl << "************************************" << std::endl;
std::cout << "\t" << testName << " for " << typeName << "\t" << std::endl;
std::cout << "************************************" << std::endl << std::endl;
}
template <typename T>
T ascendingInsert(int count, bool print = true) {
using namespace std::chrono;
TimePoint start, end;
start = system_clock::now();
T map;
for(int i = 0; i < count; i++) {
map.insert(std::pair<int, int>(i,i));
}
end = system_clock::now();
Milli elapsed = end - start;
if(print)
std::cout << "Inserting " << count << " elements in aescending order took " << elapsed.count() << " milliseconds" << std::endl;
return map;
}
template <typename T>
T descendingInsert(int count, bool print = true) {
using namespace std::chrono;
TimePoint start, end;
start = system_clock::now();
T map;
for(int i = count; i > 0; i--) {
map.insert(std::pair<int, int>(i,i));
}
end = system_clock::now();
Milli elapsed = end - start;
if(print)
std::cout << "Inserting " << count << " elements in descending order took " << elapsed.count() << " milliseconds" << std::endl;
return map;
}
template <typename T>
void deleteTest() {
using namespace std::chrono;
TimePoint start, end;
T m1 = ascendingInsert<T>(10000, false);
T m2 = ascendingInsert<T>(100000, false);
T m3 = ascendingInsert<T>(1000000, false);
T m4 = ascendingInsert<T>(10000000, false);
std::set<int> toDelete;
for(int i = 0; i < 10000; i++) {
toDelete.insert(i);
}
start = system_clock::now();
for(const int e : toDelete)
m1.erase(e);
end = system_clock::now();
Milli elapsed1 = end - start;
std::cout << "deleting 10000 elements from a map of size 10000 took " << elapsed1.count() << " milliseconds" << std::endl;
{
toDelete.clear();
std::default_random_engine generator;
std::uniform_int_distribution<int> distribution(0,99999);
while(toDelete.size() < 10000) {
toDelete.insert(distribution(generator));
}
}
start = system_clock::now();
for(const int e : toDelete)
m2.erase(e);
end = system_clock::now();
Milli elapsed2 = end - start;
std::cout << "deleting 10000 elements from a map of size 100000 took " << elapsed2.count() << " milliseconds" << std::endl;
{
toDelete.clear();
std::default_random_engine generator;
std::uniform_int_distribution<int> distribution(0,999999);
while(toDelete.size() < 10000) {
toDelete.insert(distribution(generator));
}
}
start = system_clock::now();
for(const int e : toDelete)
m3.erase(e);
end = system_clock::now();
Milli elapsed3 = end - start;
std::cout << "deleting 10000 elements from a map of size 1000000 took " << elapsed3.count() << " milliseconds" << std::endl;
{
toDelete.clear();
std::default_random_engine generator;
std::uniform_int_distribution<int> distribution(0,9999999);
while(toDelete.size() < 10000) {
toDelete.insert(distribution(generator));
}
}
start = system_clock::now();
for(const int e : toDelete)
m4.erase(e);
end = system_clock::now();
Milli elapsed4 = end - start;
std::cout << "deleting 10000 elements from a map of size 10000000 took " << elapsed4.count() << " milliseconds" << std::endl;
}
template <typename T>
void findTest() {
using namespace std::chrono;
TimePoint start, end;
T m1 = ascendingInsert<T>(10000, false);
T m2 = ascendingInsert<T>(100000, false);
T m3 = ascendingInsert<T>(1000000, false);
T m4 = ascendingInsert<T>(10000000, false);
T m11;
T m22;
T m33;
T m44;
std::vector<int> toFind;
for(int i = 0; i < 10000; i++) {
toFind.push_back(i);
}
start = system_clock::now();
for(const int e : toFind) {
auto it = m1.find(e);
m11.insert(*it);
}
end = system_clock::now();
Milli elapsed1 = end - start;
std::cout << "Finding 10000 elements from a map of size " << m1.size() << " took " << elapsed1.count() << " milliseconds" << std::endl;
{
toFind.clear();
std::default_random_engine generator;
std::uniform_int_distribution<int> distribution(0,99999);
while(toFind.size() < 10000) {
toFind.push_back(distribution(generator));
}
}
start = system_clock::now();
for(const int e : toFind) {
auto it = m2.find(e);
m22.insert(*it);
}
end = system_clock::now();
Milli elapsed2 = end - start;
std::cout << "Finding 10000 elements from a map of size " << m2.size() << " took " << elapsed2.count() << " milliseconds" << std::endl;
{
toFind.clear();
std::default_random_engine generator;
std::uniform_int_distribution<int> distribution(0,999999);
while(toFind.size() < 10000) {
toFind.push_back(distribution(generator));
}
}
start = system_clock::now();
for(const int e : toFind) {
auto it = m3.find(e);
m33.insert(*it);
}
end = system_clock::now();
Milli elapsed3 = end - start;
std::cout << "Finding 10000 elements from a map of size " << m3.size() << " took " << elapsed3.count() << " milliseconds" << std::endl;
{
toFind.clear();
std::default_random_engine generator;
std::uniform_int_distribution<int> distribution(0,9999999);
while(toFind.size() < 10000) {
toFind.push_back(distribution(generator));
}
}
start = system_clock::now();
for(const int e : toFind) {
auto it = m4.find(e);
m44.insert(*it);
}
end = system_clock::now();
Milli elapsed4 = end - start;
std::cout << "Finding 10000 elements from a map of size " << m4.size() << " took " << elapsed4.count() << " milliseconds" << std::endl;
}
template <typename T>
void iterationTest(int count) {
using namespace std::chrono;
T m = ascendingInsert<T>(count,false);
TimePoint start, end;
for(int j = 0; j < 3; j++) {
start = system_clock::now();
for(auto it = m.begin(); it != m.end(); it++) {
if(j==2)
(*it).second += j;
}
end = system_clock::now();
}
Milli elapsed = end - start;
std::cout << "Iterating across " << count << " elements in a map of size " << count << " took " << elapsed.count() << " milliseconds time per iteration was " << elapsed.count()/double(count)*1e6 << " nanoseconds" << std::endl;
}
template <typename T>
void copyTest(int count) {
using namespace std::chrono;
T m = ascendingInsert<T>(count,false);
TimePoint start, end;
start = system_clock::now();
T m2(m);
end = system_clock::now();
Milli elapsed = end - start;
std::cout << "Copy construction of a map of size " << m2.size() << " took " << elapsed.count() << " milliseconds" << std::endl;
}
/*
#include <assert.h>
using namespace std;
ostream &
operator<<(ostream &os, const type_info &ti) {
int ec;
const char *demangled_name = abi::__cxa_demangle(ti.name(), 0, 0, &ec);
assert(ec == 0);
os << demangled_name;
free((void *) demangled_name);
return os;
}
template <typename T>
void foo(T &&o) {
//o = 2;
cout << typeid(const int &) << endl;
}
int main() {
const int i = 1;
foo(i);
}
*/
class comma_numpunct : public std::numpunct<char> {
protected:
virtual char do_thousands_sep() const { return ','; }
virtual std::string do_grouping() const { return "\03"; }
};
int main() {
//separate all printed numbers with commas
std::locale comma_locale(std::locale(), new comma_numpunct());
std::cout.imbue(comma_locale);
auto demangle = [](const std::type_info &ti) {
int ec;
return abi::__cxa_demangle(ti.name(), 0, 0, &ec);
assert(ec == 0);
};
const char *w = demangle(typeid(cs440::StdMapWrapper<int,int>));
const char *m = demangle(typeid(cs440::Map<int,int>));
{
dispTestName("Ascending insert", m);
ascendingInsert<cs440::Map<int,int>>(1000);
ascendingInsert<cs440::Map<int,int>>(10000);
ascendingInsert<cs440::Map<int,int>>(100000);
ascendingInsert<cs440::Map<int,int>>(1000000);
ascendingInsert<cs440::Map<int,int>>(10000000);
dispTestName("Ascending insert", w);
ascendingInsert<cs440::StdMapWrapper<int,int>>(1000);
ascendingInsert<cs440::StdMapWrapper<int,int>>(10000);
ascendingInsert<cs440::StdMapWrapper<int,int>>(100000);
ascendingInsert<cs440::StdMapWrapper<int,int>>(1000000);
ascendingInsert<cs440::StdMapWrapper<int,int>>(10000000);
}
{
dispTestName("Descending insert", m);
descendingInsert<cs440::Map<int,int>>(1000);
descendingInsert<cs440::Map<int,int>>(10000);
descendingInsert<cs440::Map<int,int>>(100000);
descendingInsert<cs440::Map<int,int>>(1000000);
descendingInsert<cs440::Map<int,int>>(10000000);
dispTestName("Descending insert", w);
descendingInsert<cs440::StdMapWrapper<int,int>>(1000);
descendingInsert<cs440::StdMapWrapper<int,int>>(10000);
descendingInsert<cs440::StdMapWrapper<int,int>>(100000);
descendingInsert<cs440::StdMapWrapper<int,int>>(1000000);
descendingInsert<cs440::StdMapWrapper<int,int>>(10000000);
}
{
dispTestName("Delete test", m);
deleteTest<cs440::Map<int,int>>();
dispTestName("Delete test", w);
deleteTest<cs440::StdMapWrapper<int,int>>();
}
{
dispTestName("Find test", m);
findTest<cs440::Map<int,int>>();
dispTestName("Find test", w);
findTest<cs440::StdMapWrapper<int,int>>();
}
/*
Remember that some of these maps get quite large - iteration times may be affected by things other than the scaling of your algorithm.
How do the many levels of the memory heirarchy in a computer relate?
How do they perform relative to one another?
How might this have affected other performance tests?
*/
{
dispTestName("Iteration test", m);
iterationTest<cs440::Map<int,int>>(10000);
iterationTest<cs440::Map<int,int>>(20000);
iterationTest<cs440::Map<int,int>>(40000);
iterationTest<cs440::Map<int,int>>(80000);
iterationTest<cs440::Map<int,int>>(160000);
iterationTest<cs440::Map<int,int>>(320000);
iterationTest<cs440::Map<int,int>>(640000);
iterationTest<cs440::Map<int,int>>(1280000);
iterationTest<cs440::Map<int,int>>(2560000);
iterationTest<cs440::Map<int,int>>(5120000);
#if DO_BIG_ITERATION_TEST
//Optional test. This is more ram than the remote machines have and will likely take a long time to run.
iterationTest<cs440::Map<int,int>>(600000000);
#endif
dispTestName("Iteration test", w);
iterationTest<cs440::StdMapWrapper<int,int>>(10000);
iterationTest<cs440::StdMapWrapper<int,int>>(20000);
iterationTest<cs440::StdMapWrapper<int,int>>(40000);
iterationTest<cs440::StdMapWrapper<int,int>>(80000);
iterationTest<cs440::StdMapWrapper<int,int>>(160000);
iterationTest<cs440::StdMapWrapper<int,int>>(320000);
iterationTest<cs440::StdMapWrapper<int,int>>(640000);
iterationTest<cs440::StdMapWrapper<int,int>>(1280000);
iterationTest<cs440::StdMapWrapper<int,int>>(5120000);
#if DO_BIG_ITERATION_TEST
//Optional test. This is more ram than the remote machines have and will likely take a long time to run.
iterationTest<cs440::Map<int,int>>(600000000);
#endif
}
{
//Test copy constructor scaling
dispTestName("Copy test", m);
copyTest<cs440::Map<int,int>>(10000);
copyTest<cs440::Map<int,int>>(100000);
copyTest<cs440::Map<int,int>>(1000000);
copyTest<cs440::Map<int,int>>(10000000);
dispTestName("Copy test", w);
copyTest<cs440::StdMapWrapper<int,int>>(10000);
copyTest<cs440::StdMapWrapper<int,int>>(100000);
copyTest<cs440::StdMapWrapper<int,int>>(1000000);
copyTest<cs440::StdMapWrapper<int,int>>(10000000);
}
//Add your own indexibility scaling test here
// Cast, due to const-ness.
free((void *) w);
free((void *) m);
}

140
test.cpp Normal file
View file

@ -0,0 +1,140 @@
#include "Map.hpp"
#include <iostream>
#include <string>
#include <stdexcept>
#include <utility>
#include <random>
#include <chrono>
#include <iterator>
#include <cassert>
void stress(int stress_size) {
auto seed = std::chrono::system_clock::now().time_since_epoch().count();
std::default_random_engine gen(seed);
std::uniform_int_distribution<unsigned int> dist(0, 10000);
cs440::Map<int, int> m;
for(int i = 0; i < stress_size; ++i) {
auto g = dist(gen);
m.insert({g, g});
}
int num_erases = gen() % m.size();
for(int i = 0; i < num_erases; ++i) {
//select a random element
int choice = gen() % m.size();
auto iter = std::begin(m);
for (int j = 0; j < choice; ++j) {
++iter;
}
m.erase(iter);
}
}
// unrealistic examples
void access_by_key() {
cs440::Map<int, long> m;
m.insert({10, 10});
m.insert({3, 3});
m.insert({20, 20});
m.insert({15, 15});
m.at(10);
bool thrown = false;
try {
m.at(10000);
} catch (std::out_of_range) {
thrown = true;
}
assert(thrown); // the .at should have thrown an exception
const auto& m_ref = m;
m_ref.at(10); // const .at
auto iter = m.find(3);
assert((*iter).second == 3);
auto iter2 = m.find(100000); // not in map, should give iterator to end()
assert(iter2 == std::end(m));
m[30] = 30; // should default construct
m.erase(10);
assert(m.find(10) == std::end(m)); // 10 shouldn't be in the map anymore
}
void count_words() {
cs440::Map<std::string, int> words_count;
// just a big list of words
auto words = {"this", "is", "a", "string", "with", "words", "some",
"words", "in", "the", "string", "repeat", "the", "more", "they",
"repeat", "the", "more", "they", "should", "count", "they", "more",
"they", "count", "the", "more", "they", "will", "have", "their",
"count", "increased"};
for (auto& word : words) {
// this works because int can be default constructed, and the
// default of int (by doing int{} ) is 0.
words_count[word] += 1; // add 1 to the count
}
// print the frequency of each word
std::cout << "word frequency:\n";
for (auto& count : words_count) { // uses .begin() and .end()
std::cout << count.first << ": " << count.second << '\n';
}
std::cout << "word frequency reversed order:\n";
for (auto riter = words_count.rbegin();
riter != words_count.rend();
++riter) {
std::cout << (*riter).first << ": " << (*riter).second << '\n';
}
}
// creates a mapping from the values in the range [low, high) to their cubes
cs440::Map<int, int> cubes(int low, int high) {
cs440::Map<int, int> cb;
for (int i = low; i < high; ++i) {
cb.insert({i, i*i*i});
}
return cb;
}
int main () {
count_words();
auto cube = cubes(-5, 10); // move construct returned value
std::cout << cube.at(-2) << '\n'; // -8
std::cout << cube.at(5) << '\n'; // 125
const int n = 30;
try {
std::cout << cube.at(n) << '\n'; // 30 is not in the Map
} catch (std::out_of_range) {
std::cout << n << " not in cubes range\n";
}
// constructors and assignment examples:
// initializer_list example
cs440::Map<int, double> int_double_map {{1, 1.0}, {3, 5.67}, {13, 6.9}};
// must support copy construction
cs440::Map<int, double> copy_example{int_double_map};
cs440::Map<int, double> assign_example;
// must support copy assignment
assign_example = copy_example;
access_by_key();
stress(10000);
return 0;
}