diff --git a/CMakeLists.txt b/CMakeLists.txt index bb058d4..2c66f03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,14 +34,19 @@ add_executable( tests/doubly_linked_list/clear_tests.cpp tests/doubly_linked_list/erase_tests.cpp tests/doubly_linked_list/move_to_begin_tests.cpp + tests/doubly_linked_list/move_to_end_tests.cpp tests/doubly_linked_list/emplace_back_tests.cpp tests/ordered_map/size_tests.cpp tests/ordered_map/insertion_tests.cpp tests/ordered_map/lookup_tests.cpp tests/ordered_map/iterator_tests.cpp tests/ordered_map/constructor_tests.cpp + tests/ordered_map/destructor_tests.cpp tests/ordered_map/find_tests.cpp + tests/ordered_map/clear_tests.cpp + tests/ordered_map/erase_tests.cpp tests/ordered_map/move_to_front_tests.cpp + tests/ordered_map/move_to_back_tests.cpp ) target_link_libraries(tests PRIVATE ordered_map_lib Catch2::Catch2WithMain) diff --git a/include/doubly_linked_list.hpp b/include/doubly_linked_list.hpp index 97f340a..b46c53e 100644 --- a/include/doubly_linked_list.hpp +++ b/include/doubly_linked_list.hpp @@ -21,89 +21,6 @@ struct Node { template class DoublyLinkedList { -private: - std::unique_ptr> head; - Node* tail; - int _size; - - void link_new_back_node(std::unique_ptr> new_node) { - if (!head) { - head = std::move(new_node); - tail = head.get(); - } else { - tail->next = std::move(new_node); - tail->next->prev = tail; - tail = tail->next.get(); - } - ++_size; - } - - template - void push_back_internal(U&& value) { - auto new_node = std::make_unique>(tail, nullptr, std::forward(value)); - link_new_back_node(std::move(new_node)); - } - - template - void push_front_internal(U&& value) { - auto new_node = std::make_unique>(nullptr, std::move(head), std::forward(value)); - if (new_node->next) { - new_node->next->prev = new_node.get(); - } - else { - tail = new_node.get(); - } - head = std::move(new_node); - _size++; - } - - T erase_middle_node(Node* node) { - T value = std::move(node->value); - node->next->prev = node->prev; - node->prev->next = std::move(node->next); - _size--; - return value; - } - - std::unique_ptr> extract_node_and_link_prev_with_next(Node* node) { - std::unique_ptr> extracted; - - if (node->next) { - node->next->prev = node->prev; - } else { - tail = node->prev; - } - - if (node->prev) { - extracted = std::move(node->prev->next); - node->prev->next = std::move(node->next); - } else { - extracted = std::move(head); - head = std::move(node->next); - } - - return extracted; - } - - void emplace_node_before(std::unique_ptr> node, Node* position) { - if (position == head.get()) { - node->next = std::move(head); - if (node->next) node->next->prev = node.get(); - head = std::move(node); - } - else if (position == nullptr) { - node->prev = tail; - tail = node.get(); - node->prev->next = std::move(node); - } - else { - node->prev = position->prev; - node->next = std::move(position->prev->next); - position->prev = node.get(); - node->prev->next = std::move(node); - } - } - public: DoublyLinkedList() : head(nullptr), tail(nullptr), _size(0) {} @@ -152,9 +69,7 @@ class DoublyLinkedList { } T pop_back() { - if (!head) { - throw std::out_of_range("List is empty"); - } + raise_exception_if_empty(); T popped_value = std::move(tail->value); @@ -180,9 +95,7 @@ class DoublyLinkedList { } T pop_front() { - if (!head) { - throw std::out_of_range("List is empty"); - } + raise_exception_if_empty(); T popped_value = std::move(head->value); head = std::move(head->next); @@ -195,23 +108,14 @@ class DoublyLinkedList { } void clear() { - while(head) { - pop_back(); - } + while(!empty()) pop_back(); } bool empty() const { - return _size == 0; + return !head; } class Iterator { - Node* current_node_ptr; - DoublyLinkedList* list_ptr; - - Iterator(Node* a_current, DoublyLinkedList* a_list_ptr) : current_node_ptr(a_current), list_ptr(a_list_ptr) {} - - friend class DoublyLinkedList; - public: Iterator() : current_node_ptr(nullptr), list_ptr(nullptr) {} @@ -224,10 +128,20 @@ class DoublyLinkedList { } T& operator*() const { + raise_exception_if_empty(); + + if (*this == list_ptr->end()) + throw std::out_of_range("end iterator unexpectedly dereferenced"); + return current_node_ptr->value; } Iterator& operator++() { + raise_exception_if_empty(); + + if (*this == list_ptr->end()) + throw std::out_of_range("called ++ on end iterator"); + if (current_node_ptr) { current_node_ptr = current_node_ptr->next.get(); } @@ -241,6 +155,11 @@ class DoublyLinkedList { } Iterator& operator--() { + raise_exception_if_empty(); + + if (*this == list_ptr->begin()) + throw std::out_of_range("called -- on begin iterator"); + if (current_node_ptr == nullptr) { current_node_ptr = list_ptr->tail; } @@ -249,6 +168,23 @@ class DoublyLinkedList { } return *this; } + + Iterator operator--(int) { + Iterator initial_state_copy = *this; + --(*this); + return initial_state_copy; + } + + private: + void raise_exception_if_empty() const { + if (list_ptr->empty()) + throw std::out_of_range("Container is empty"); + } + + Node* current_node_ptr; + DoublyLinkedList* list_ptr; + Iterator(Node* a_current, DoublyLinkedList* a_list_ptr) : current_node_ptr(a_current), list_ptr(a_list_ptr) {} + friend class DoublyLinkedList; }; Iterator begin() { @@ -265,8 +201,8 @@ class DoublyLinkedList { Iterator erase(Iterator it) { - if (empty()) throw std::out_of_range("List is empty"); - if (it == end()) throw std::out_of_range("Invalid iterator"); + raise_exception_if_empty(); + if (it == end()) throw std::out_of_range("erase called on end iterator"); Iterator target_it = it++; @@ -278,19 +214,113 @@ class DoublyLinkedList { } void move_to_begin(Iterator it) { - if (empty()) throw std::out_of_range("List is empty"); - if (it == end()) throw std::out_of_range("Invalid iterator"); + raise_exception_if_empty(); + if (it == end()) throw std::out_of_range("move_to_begin called on end iterator"); if (it == begin()) return; auto extracted = extract_node_and_link_prev_with_next(it.current_node_ptr); emplace_node_before(std::move(extracted), head.get()); } + void move_to_end(Iterator it) { + raise_exception_if_empty(); + if (it == end()) throw std::out_of_range("move_to_end called on end iterator"); + + auto extracted = extract_node_and_link_prev_with_next(it.current_node_ptr); + emplace_node_before(std::move(extracted), nullptr); + } + template void emplace_back(Args&&... args) { auto new_node = std::make_unique>(tail, nullptr, std::forward(args)...); link_new_back_node(std::move(new_node)); } +private: + void link_new_back_node(std::unique_ptr> new_node) { + if (empty()) { + head = std::move(new_node); + tail = head.get(); + } else { + tail->next = std::move(new_node); + tail->next->prev = tail; + tail = tail->next.get(); + } + ++_size; + } + + template + void push_back_internal(U&& value) { + auto new_node = std::make_unique>(tail, nullptr, std::forward(value)); + link_new_back_node(std::move(new_node)); + } + template + void push_front_internal(U&& value) { + auto new_node = std::make_unique>(nullptr, std::move(head), std::forward(value)); + if (new_node->next) { + new_node->next->prev = new_node.get(); + } + else { + tail = new_node.get(); + } + head = std::move(new_node); + _size++; + } + + T erase_middle_node(Node* node) { + T value = std::move(node->value); + node->next->prev = node->prev; + node->prev->next = std::move(node->next); + _size--; + return value; + } + + std::unique_ptr> extract_node_and_link_prev_with_next(Node* node) { + std::unique_ptr> extracted; + + if (node->next) { + node->next->prev = node->prev; + } else { + tail = node->prev; + } + + if (node->prev) { + extracted = std::move(node->prev->next); + node->prev->next = std::move(node->next); + } else { + extracted = std::move(head); + head = std::move(node->next); + } + + return extracted; + } + + void emplace_node_before(std::unique_ptr> node, Node* position) { + if (position == head.get()) { + node->next = std::move(head); + if (node->next) node->next->prev = node.get(); + head = std::move(node); + } + else if (position == nullptr) { + node->prev = tail; + tail = node.get(); + node->prev->next = std::move(node); + } + else { + node->prev = position->prev; + node->next = std::move(position->prev->next); + position->prev = node.get(); + node->prev->next = std::move(node); + } + } + + void raise_exception_if_empty() const { + if (empty()) + throw std::out_of_range("Container is empty"); + } + + std::unique_ptr> head; + Node* tail; + int _size; }; diff --git a/include/ordered_map.hpp b/include/ordered_map.hpp index cb6927f..dd8c996 100644 --- a/include/ordered_map.hpp +++ b/include/ordered_map.hpp @@ -5,16 +5,7 @@ template -class OrderedMap { -private: - - DoublyLinkedList> _list; - - using ListIterator = typename DoublyLinkedList>::Iterator; - - std::unordered_map _map; - - +class OrderedMap { public: OrderedMap() {} @@ -31,6 +22,10 @@ class OrderedMap { } } + ~OrderedMap() { + clear(); + } + void insert(const KeyType& key, const ValueType& value) { if (_map.find(key) == _map.end()) { _list.emplace_back(key, value); @@ -51,10 +46,31 @@ class OrderedMap { } } - unsigned int size() const { + size_t size() const { return _list.size(); } + bool empty() const { + return _list.empty(); + } + + void clear() { + _list.clear(); + _map.clear(); + } + + size_t erase(const KeyType& key) { + if (empty()) return 0; + + auto it = _map.find(key); + if (it != _map.end()) { + _list.erase(it->second); + _map.erase(it); + return 1; + } + return 0; + } + ValueType& at(const KeyType& key) { if (_map.find(key) == _map.end()) { throw std::out_of_range("Key not found in ordered map"); @@ -69,6 +85,8 @@ class OrderedMap { return (*_map[key]).second; } + + using ListIterator = typename DoublyLinkedList>::Iterator; class Iterator { ListIterator _it; @@ -94,6 +112,17 @@ class OrderedMap { return initial_state_copy; } + Iterator& operator--() { + --_it; + return *this; + } + + Iterator operator--(int) { + Iterator initial_state_copy = *this; + _it--; + return initial_state_copy; + } + bool operator==(const Iterator& other) const { return _it == other._it; } @@ -134,4 +163,17 @@ class OrderedMap { } _list.move_to_begin(_map[key]); } + + void move_to_back(const KeyType& key) { + if (_map.find(key) == _map.end()) { + throw std::out_of_range("Key not found in ordered map"); + } + _list.move_to_end(_map[key]); + } + +private: + + DoublyLinkedList> _list; + std::unordered_map _map; + }; \ No newline at end of file diff --git a/tests/doubly_linked_list/iterator_tests.cpp b/tests/doubly_linked_list/iterator_tests.cpp index 1606831..4cd7a8e 100644 --- a/tests/doubly_linked_list/iterator_tests.cpp +++ b/tests/doubly_linked_list/iterator_tests.cpp @@ -7,9 +7,8 @@ TEST_CASE("begin iterator equals end iterator in empty list", "[iterator]") { REQUIRE(list.begin() == list.end()); } -TEST_CASE("push_back 1, begin iterator does not equal end iterator", "[iterator]") { - DoublyLinkedList list; - list.push_back(1); +TEST_CASE("list contains {1}, begin iterator does not equal end iterator", "[iterator]") { + DoublyLinkedList list = {1}; REQUIRE(!(list.begin() == list.end())); } @@ -18,51 +17,104 @@ TEST_CASE("begin iterator != end iterator returns false in an empty list", "[ite REQUIRE(!(list.begin() != list.end())); } -TEST_CASE("push_back 1, begin iterator != end iterator returns false", "[iterator]") { - DoublyLinkedList list; - list.push_back(1); +TEST_CASE("list contains {1}, begin iterator != end iterator returns false", "[iterator]") { + DoublyLinkedList list = {1}; REQUIRE(list.begin() != list.end()); } -TEST_CASE("push_back 1, begin iterator dereferences to 1", "[begin]") { +TEST_CASE("dereferencing end iterator throws an exception", "[iterator]") { DoublyLinkedList list; - list.push_back(1); + auto it = list.end(); - REQUIRE(*list.begin() == 1); + REQUIRE_THROWS_AS(*it, std::out_of_range); } -TEST_CASE("push_back 2, begin iterator dereferences to 2", "[begin]") { +TEST_CASE("dereferencing begin iterator in an empty list throws an exception", "[iterator]") { DoublyLinkedList list; - list.push_back(2); + auto it = list.begin(); + + REQUIRE_THROWS_AS(*it, std::out_of_range); +} + +TEST_CASE("list contains {1}, begin iterator dereferences to 1", "[begin]") { + DoublyLinkedList list = {1}; + + REQUIRE(*list.begin() == 1); +} + +TEST_CASE("list contains {2}, begin iterator dereferences to 2", "[begin]") { + DoublyLinkedList list = {2}; REQUIRE(*list.begin() == 2); } -TEST_CASE("push_back hello string, modify string by dereferencing begin, front returns modified string", "[begin]") { - DoublyLinkedList list; - list.push_back("hello"); +TEST_CASE("list contains {hello}, modify string by dereferencing begin, front returns modified string", "[begin]") { + DoublyLinkedList list = {"hello"}; auto &str = *list.begin(); str[0] = 'H'; REQUIRE(*list.begin() == "Hello"); } -TEST_CASE("push_back 1, 2, using pre-increment (++it) on begin iterator to reach 2", "[iterator]") { +TEST_CASE("using pre-decrement operator on end() in an empty list throws an exception", "[iterator]") { + DoublyLinkedList list; + auto it = list.end(); + + REQUIRE_THROWS_AS(--it, std::out_of_range); +} + +TEST_CASE("using post-decrement operator on end() in an empty list throws an exception", "[iterator]") { + DoublyLinkedList list; + auto it = list.end(); + + REQUIRE_THROWS_AS(it--, std::out_of_range); +} + +TEST_CASE("using pre-increment operator on end() in a non-empty list throws an exception", "[iterator]") { + DoublyLinkedList list = {1}; + auto it = list.end(); + + REQUIRE_THROWS_AS(++it, std::out_of_range); +} + + +TEST_CASE("using pre-increment operator on begin() in an empty list throws an exception", "[iterator]") { + DoublyLinkedList list; + auto it = list.begin(); + REQUIRE_THROWS_AS(++it, std::out_of_range); +} + +TEST_CASE("using post-increment operator on begin() in an empty list throws an exception", "[iterator]") { + DoublyLinkedList list; + auto it = list.begin(); + REQUIRE_THROWS_AS(it++, std::out_of_range); +} + +TEST_CASE("using pre-decrement operator on begin iterator in an empty list throws an exception", "[iterator]") { DoublyLinkedList list; - list.push_back(1); - list.push_back(2); + auto it = list.begin(); + + REQUIRE_THROWS_AS(--it, std::out_of_range); +} + +TEST_CASE("Using pre-decrement on begin iterator in a non-empty list throws an exception", "[iterator]") { + DoublyLinkedList list = {1}; + auto it = list.begin(); + + REQUIRE_THROWS_AS(--it, std::out_of_range); +} + +TEST_CASE("list contains {1, 2}, using pre-increment (++it) on begin iterator to reach 2", "[iterator]") { + DoublyLinkedList list = {1, 2}; auto it = list.begin(); ++it; REQUIRE(*it == 2); } -TEST_CASE("push_back 1, 2, 3, using pre-increment (++it) twice on begin iterator to reach 2", "[iterator]") { - DoublyLinkedList list; - list.push_back(1); - list.push_back(2); - list.push_back(3); +TEST_CASE("list contains {1, 2, 3}, using pre-increment (++it) twice on begin iterator to reach 3", "[iterator]") { + DoublyLinkedList list = {1, 2, 3}; auto it = list.begin(); ++it; ++it; @@ -70,32 +122,24 @@ TEST_CASE("push_back 1, 2, 3, using pre-increment (++it) twice on begin iterator REQUIRE(*it == 3); } -TEST_CASE("push_back 1, 2, copy the iterator after using pre-increment (++it) on begin iterator to reach 2", "[iterator]") { - DoublyLinkedList list; - list.push_back(1); - list.push_back(2); +TEST_CASE("list contains {1, 2}, copy the iterator after using pre-increment (++it) on begin iterator to reach 2", "[iterator]") { + DoublyLinkedList list = {1, 2}; auto it = list.begin(); auto copied_it = ++it; REQUIRE(*copied_it == 2); } - -TEST_CASE("push_back 1, 2, using post-increment (it++) on begin iterator to reach 2", "[iterator]") { - DoublyLinkedList list; - list.push_back(1); - list.push_back(2); +TEST_CASE("list contains {1, 2}, using post-increment (it++) on begin iterator to reach 2", "[iterator]") { + DoublyLinkedList list = {1, 2}; auto it = list.begin(); it++; REQUIRE(*it == 2); } -TEST_CASE("push_back 1, 2, 3, using post-increment (it++) on begin iterator twice to reach 3", "[iterator]") { - DoublyLinkedList list; - list.push_back(1); - list.push_back(2); - list.push_back(3); +TEST_CASE("list contains {1, 2, 3}, using post-increment (it++) on begin iterator twice to reach 3", "[iterator]") { + DoublyLinkedList list = {1, 2, 3}; auto it = list.begin(); it++; it++; @@ -104,21 +148,16 @@ TEST_CASE("push_back 1, 2, 3, using post-increment (it++) on begin iterator twic REQUIRE(*it == 3); } -TEST_CASE("push_back 1, 2, copy the iterator after using post-increment (it++) on begin iterator", "[iterator]") { - DoublyLinkedList list; - list.push_back(1); - list.push_back(2); +TEST_CASE("list contains {1, 2}, copy the iterator after using post-increment (it++) on begin iterator", "[iterator]") { + DoublyLinkedList list = {1, 2}; auto it = list.begin(); auto copied_it = it++; REQUIRE(*copied_it == 1); } -TEST_CASE("push back 1, 2, 3, iterate using for-loop with post-increment (it++) on begin iterator", "[iterator]") { - DoublyLinkedList list; - list.push_back(1); - list.push_back(2); - list.push_back(3); +TEST_CASE("list contains {1, 2, 3}, iterate using for-loop with post-increment (it++) on begin iterator", "[iterator]") { + DoublyLinkedList list = {1, 2, 3}; int counter = 1; for (auto it = list.begin(); it != list.end(); it++) { @@ -127,11 +166,8 @@ TEST_CASE("push back 1, 2, 3, iterate using for-loop with post-increment (it++) } } -TEST_CASE("push back 1, 2, 3, iterate using for-loop with pre-increment (++it) on begin iterator", "[iterator]") { - DoublyLinkedList list; - list.push_back(1); - list.push_back(2); - list.push_back(3); +TEST_CASE("list contains {1, 2, 3}, iterate using for-loop with pre-increment (++it) on begin iterator", "[iterator]") { + DoublyLinkedList list = {1, 2, 3}; int counter = 1; for (auto it = list.begin(); it != list.end(); ++it) { @@ -140,11 +176,8 @@ TEST_CASE("push back 1, 2, 3, iterate using for-loop with pre-increment (++it) o } } -TEST_CASE("push back 1, 2, 3, iterate using for-loop with for-each syntax", "[iterator]") { - DoublyLinkedList list; - list.push_back(1); - list.push_back(2); - list.push_back(3); +TEST_CASE("list contains {1, 2, 3}, iterate using for-loop with for-each syntax", "[iterator]") { + DoublyLinkedList list = {1, 2, 3}; int counter = 1; for (auto it : list) { @@ -153,27 +186,41 @@ TEST_CASE("push back 1, 2, 3, iterate using for-loop with for-each syntax", "[it } } -TEST_CASE("push_back 1, dereferencing back iterator returns 1", "[back_iterator]") { - DoublyLinkedList list; - list.push_back(1); +TEST_CASE("list contains {1}, dereferencing back iterator returns 1", "[back_iterator]") { + DoublyLinkedList list = {1}; auto it = list.back_iterator(); REQUIRE(*it == 1); } -TEST_CASE("push_back 1, 2, dereferencing back iterator returns 2", "[back_iterator]") { - DoublyLinkedList list; - list.push_back(1); list.push_back(2); +TEST_CASE("list contains {1, 2}, dereferencing back iterator returns 2", "[back_iterator]") { + DoublyLinkedList list = {1, 2}; auto it = list.back_iterator(); REQUIRE(*it == 2); } -TEST_CASE("push_back 1, 2, 3, using decerement (--) on end iterator returns 3", "[back_iterator]") { - DoublyLinkedList list; - list.push_back(1); list.push_back(2); list.push_back(3); +TEST_CASE("list contains {1, 2, 3}, using pre-decrement (--it) on end iterator points to 3", "[iterator]") { + DoublyLinkedList list = {1, 2, 3}; auto it = list.end(); --it; REQUIRE(*it == 3); -} \ No newline at end of file +} + +TEST_CASE("list contains {1, 2, 3}, using post-decrement (it--) on end iterator points to 3", "[iterator]") { + DoublyLinkedList list = {1, 2, 3}; + auto it = list.end(); + it--; + + REQUIRE(*it == 3); +} + +TEST_CASE("list contains {1, 2, 3}, using pre-decrement (--it) on end iterator points to 2", "[iterator]") { + DoublyLinkedList list = {1, 2, 3}; + auto it = list.end(); + --it; + --it; + + REQUIRE(*it == 2); +} diff --git a/tests/doubly_linked_list/move_to_end_tests.cpp b/tests/doubly_linked_list/move_to_end_tests.cpp new file mode 100644 index 0000000..0580975 --- /dev/null +++ b/tests/doubly_linked_list/move_to_end_tests.cpp @@ -0,0 +1,38 @@ +#include +#include "doubly_linked_list.hpp" +#include "../tests_utils.hpp" + +TEST_CASE("move_to_end() on an empty list throws an exception", "[move_to_end]") { + DoublyLinkedList list; + REQUIRE_THROWS_AS(list.move_to_end(list.begin()), std::out_of_range); +} + +TEST_CASE("move_to_end() given end iterator throws an exception", "[move_to_end]") { + DoublyLinkedList list = {1, 2, 3}; + REQUIRE_THROWS_AS(list.move_to_end(list.end()), std::out_of_range); +} + +TEST_CASE("list containing {1, 2}, move_to_end on begin iterator moves 1 to back", "[move_to_end]") { + DoublyLinkedList list = {1, 2}; + list.move_to_end(list.begin()); + REQUIRE(list.front() == 2); + REQUIRE(list.back() == 1); +} + +TEST_CASE("list containing {1, 2, 3}, move_to_end on iterator to 2 makes front 1 and back 2", "[move_to_end]") { + DoublyLinkedList list = {1, 2, 3}; + auto it = list.begin(); + ++it; + list.move_to_end(it); + + REQUIRE(list.front() == 1); + REQUIRE(list.back() == 2); +} + +TEST_CASE("list containing 2 MoveCopyFlag objects, move_to_end on begin iterator moves object to back untouched", "[move_to_end]") { + DoublyLinkedList list = {MoveCopyFlag(), MoveCopyFlag()}; + list.move_to_end(list.begin()); + + REQUIRE(list.front().state == "default, copied"); + REQUIRE(list.back().state == "default, copied"); +} diff --git a/tests/ordered_map/clear_tests.cpp b/tests/ordered_map/clear_tests.cpp new file mode 100644 index 0000000..e8889df --- /dev/null +++ b/tests/ordered_map/clear_tests.cpp @@ -0,0 +1,25 @@ +#include +#include "ordered_map.hpp" +#include "../tests_utils.hpp" + +TEST_CASE("clear on an empty map does nothing and empty returns true", "[clear]") { + OrderedMap o_map; + o_map.clear(); + REQUIRE(o_map.empty()); +} + +TEST_CASE("clear on a non-empty map clears the map and empty returns true", "[clear]") { + OrderedMap o_map; + o_map.insert(1, 1); + o_map.clear(); + REQUIRE(o_map.empty()); +} + +TEST_CASE("clear() on a non-empty map leads to the destruction of the values", "[clear]") { + bool destroyed = false; + OrderedMap o_map = { + {1, DestructionFlag(&destroyed)} + }; + o_map.clear(); + REQUIRE(destroyed); +} diff --git a/tests/ordered_map/destructor_tests.cpp b/tests/ordered_map/destructor_tests.cpp new file mode 100644 index 0000000..3be474f --- /dev/null +++ b/tests/ordered_map/destructor_tests.cpp @@ -0,0 +1,35 @@ +#include +#include "ordered_map.hpp" +#include "../tests_utils.hpp" +#include + +TEST_CASE("destructor of an empty map does nothing", "[destructor]") { + { + OrderedMap o_map; + } +} + +TEST_CASE("destructor of a non-empty map destroys the values", "[destructor]") { + bool destroyed1 = false, destroyed2 = false, destroyed3 = false; + { + OrderedMap o_map = { + {1, DestructionFlag(&destroyed1)}, + {2, DestructionFlag(&destroyed2)}, + {3, DestructionFlag(&destroyed3)} + }; + } + REQUIRE((destroyed1 and destroyed2 and destroyed3)); +} + +TEST_CASE("destructor of a map with 100,000 values destroys the values", "[destructor]") { + bool destroyed[100000] = {}; + + { + OrderedMap o_map = {}; + for (int i = 0; i < 100000; i++) + o_map.insert(i, DestructionFlag(&destroyed[i])); + } + + bool all_destroyed = std::all_of(destroyed, destroyed + 100000, [](bool flag) { return flag; }); + REQUIRE(all_destroyed); +} diff --git a/tests/ordered_map/erase_tests.cpp b/tests/ordered_map/erase_tests.cpp new file mode 100644 index 0000000..04e5355 --- /dev/null +++ b/tests/ordered_map/erase_tests.cpp @@ -0,0 +1,60 @@ +#include +#include "ordered_map.hpp" +#include "../tests_utils.hpp" + +TEST_CASE("erase() on an empty map returns 0", "[erase]") { + OrderedMap o_map; + REQUIRE(o_map.erase(1) == 0); +} + +TEST_CASE("erase() on a non-empty map makes the size decrease by 1", "[erase]") { + OrderedMap o_map = { + {1, 1}, + {2, 2}, + {3, 3} + }; + o_map.erase(1); + + REQUIRE(o_map.size() == 2); +} + +TEST_CASE("erase() an existing key returns 1", "[erase]") { + OrderedMap o_map = { + {1, 1}, + {2, 2}, + {3, 3} + }; + + REQUIRE(o_map.erase(1) == 1); +} + +TEST_CASE("erase() a non-existing key returns 0", "[erase]") { + OrderedMap o_map = { + {1, 1}, + {2, 2}, + {3, 3} + }; + + REQUIRE(o_map.erase(4) == 0); +} + +TEST_CASE("erase() an existing key makes find() return end iterator", "[erase]") { + OrderedMap o_map = { + {1, 1}, + {2, 2}, + {3, 3} + }; + o_map.erase(1); + + REQUIRE(o_map.find(1) == o_map.end()); +} + +TEST_CASE("erase() a key leads to the destruction of the value", "[erase]") { + bool destroyed = false; + OrderedMap o_map = { + {1, DestructionFlag(&destroyed)} + }; + o_map.erase(1); + + REQUIRE(destroyed); +} diff --git a/tests/ordered_map/iterator_tests.cpp b/tests/ordered_map/iterator_tests.cpp index 5886ba4..d5b2ace 100644 --- a/tests/ordered_map/iterator_tests.cpp +++ b/tests/ordered_map/iterator_tests.cpp @@ -2,84 +2,88 @@ #include "../include/ordered_map.hpp" #include -TEST_CASE("insert <1, 2>, begin iterator points to <1, 2>", "[ordered_map_iterator]") { - OrderedMap o_map; - o_map.insert(1, 2); - REQUIRE(*o_map.begin() == std::make_pair(1, 2)); +TEST_CASE("dereferencing end() in a non-empty map throws an exception", "[ordered_map_iterator]") { + OrderedMap o_map = {{1, 2}}; + + REQUIRE_THROWS_AS(*o_map.end(), std::out_of_range); } -TEST_CASE("insert <1, 2>, <3, 4>, begin iterator points to <1, 2>", "[ordered_map_iterator]") { +TEST_CASE("dereferencing begin() in an empty map throws an exception", "[ordered_map_iterator]") { OrderedMap o_map; - o_map.insert(1, 2); + + REQUIRE_THROWS_AS(*o_map.begin(), std::out_of_range); +} + + + +TEST_CASE("map containing 1 entry {1, 2}, dereferencing begin() returns {1, 2}", "[ordered_map_iterator]") { + OrderedMap o_map = {{1, 2}}; REQUIRE(*o_map.begin() == std::make_pair(1, 2)); } -TEST_CASE("insert <1, 2>, <3, 4>, using pre-increment (++it) on begin iterator makes it point to <3, 4>", "[ordered_map_iterator]") { - OrderedMap o_map; - o_map.insert(1, 2); - o_map.insert(3, 4); +TEST_CASE("map containing 2 entries {1, 2}, {3, 4}, dereferencing begin() returns {1, 2}", "[ordered_map_iterator]") { + OrderedMap o_map = {{1, 2}, {3, 4}}; + + REQUIRE(*o_map.begin() == std::make_pair(1, 2)); +} +TEST_CASE("map containing 2 entries {1, 2}, {3, 4}, using pre-increment (++it) on begin iterator makes it point to {3, 4}", "[ordered_map_iterator]") { + OrderedMap o_map = {{1, 2}, {3, 4}}; auto it = o_map.begin(); ++it; REQUIRE(*it == std::make_pair(3, 4)); } -TEST_CASE("insert <1, 2>, <3, 4>, using post-increment (it++) on begin iterator makes it point to <1, 2>", "[ordered_map_iterator]") { - OrderedMap o_map; - o_map.insert(1, 2); - o_map.insert(3, 4); - +TEST_CASE("map containing 2 entries {1, 2}, {3, 4}, using post-increment (it++) on begin iterator makes it point to {3, 4}", "[ordered_map_iterator]") { + OrderedMap o_map = {{1, 2}, {3, 4}}; auto it = o_map.begin(); it++; REQUIRE(*it == std::make_pair(3, 4)); } -TEST_CASE("iterator (begin == end) is true when map is empty", "[ordered_map_iterator]") { +TEST_CASE("map is empty, begin == end returns true", "[ordered_map_iterator]") { OrderedMap o_map; REQUIRE(o_map.begin() == o_map.end()); } -TEST_CASE("iterator (begin == end) is false when map is not empty", "[ordered_map_iterator]") { - OrderedMap o_map; - o_map.insert(1, 2); +TEST_CASE("map containing 1 entry {1, 2}, begin == end returns false", "[ordered_map_iterator]") { + OrderedMap o_map = {{1, 2}}; REQUIRE(!(o_map.begin() == o_map.end())); } -TEST_CASE("iterator (begin != end) is false when map is empty", "[ordered_map_iterator]") { +TEST_CASE("map is empty, begin != end returns false", "[ordered_map_iterator]") { OrderedMap o_map; REQUIRE(!(o_map.begin() != o_map.end())); } -TEST_CASE("iterator (begin != end) is true when map is not empty", "[ordered_map_iterator]") { - OrderedMap o_map; - o_map.insert(1, 2); +TEST_CASE("map containing 1 entry {1, 2}, begin != end returns true", "[ordered_map_iterator]") { + OrderedMap o_map = {{1, 2}}; REQUIRE(o_map.begin() != o_map.end()); } -TEST_CASE("using for-loop with begin and end iterators to iterate over map yields correct results in order", "[ordered_map_iterator]") { +TEST_CASE("map containing 3 entries {1, 2}, {2, 3}, {3, 4}, using for-loop with begin and end iterators to iterate over map yields correct results in order", "[ordered_map_iterator]") { std::vector> expected_results = { {1, 2}, {2, 3}, {3, 4} }; - OrderedMap o_map; - for (const auto& pair : expected_results) { - o_map.insert(pair.first, pair.second); - } + OrderedMap o_map(expected_results.begin(), expected_results.end()); + int i = 0; for (auto it = o_map.begin(); it != o_map.end(); it++) { - REQUIRE(*it == expected_results[i]); - i++; + REQUIRE(*it == expected_results[i++]); } + + REQUIRE(i == expected_results.size()); } TEST_CASE("using for-each loop yields correct results in order", "[ordered_map_iterator]") { @@ -88,15 +92,10 @@ TEST_CASE("using for-each loop yields correct results in order", "[ordered_map_i {2, 3}, {3, 4} }; - OrderedMap o_map; - for (const auto& pair : expected_results) { - o_map.insert(pair.first, pair.second); - } - + OrderedMap o_map(expected_results.begin(), expected_results.end()); int i = 0; for (auto& pair : o_map) { - REQUIRE(pair == expected_results[i]); - i++; + REQUIRE(pair == expected_results[i++]); } } @@ -106,18 +105,104 @@ TEST_CASE("back_iterator() throws out_of_range when map is empty", "[ordered_map REQUIRE_THROWS_AS(o_map.back_iterator(), std::out_of_range); } -TEST_CASE("insert <1, 2>, back_iterator points to <1, 2>", "[ordered_map_iterator]") { - OrderedMap o_map; - o_map.insert(1, 2); +TEST_CASE("map containing 1 entry {1, 2}, back_iterator points to {1, 2}", "[ordered_map_iterator]") { + OrderedMap o_map = {{1, 2}}; REQUIRE(*o_map.back_iterator() == std::make_pair(1, 2)); } -TEST_CASE("insert <1, 2>, <3, 4>, back_iterator points to <3, 4>", "[ordered_map_iterator]") { - OrderedMap o_map; - o_map.insert(1, 2); - o_map.insert(3, 4); +TEST_CASE("map containing 2 entries {1, 2}, {3, 4}, back_iterator points to {3, 4}", "[ordered_map_iterator]") { + OrderedMap o_map = {{1, 2}, {3, 4}}; REQUIRE(*o_map.back_iterator() == std::make_pair(3, 4)); } +TEST_CASE("using post-increment operator on end iterator in a non-empty map throws an exception", "[ordered_map_iterator]") { + OrderedMap o_map = {{1, 2}}; + auto it = o_map.end(); + REQUIRE_THROWS_AS(it++, std::out_of_range); +} + +TEST_CASE("using pre-increment operator on begin iterator in an empty map throws an exception", "[ordered_map_iterator]") { + OrderedMap o_map; + auto it = o_map.begin(); + REQUIRE_THROWS_AS(++it, std::out_of_range); +} + +TEST_CASE("using pre-decrement operator on end iterator in an empty map throws an exception", "[ordered_map_iterator]") { + OrderedMap o_map; + auto it = o_map.end(); + REQUIRE_THROWS_AS(--it, std::out_of_range); +} + +TEST_CASE("using post-decrement operator on end() in an empty map throws an exception", "[ordered_map_iterator]") { + OrderedMap o_map; + auto it = o_map.end(); + REQUIRE_THROWS_AS(it--, std::out_of_range); +} + +TEST_CASE("map containing 1 entry {1, 2}, using pre-decrement operator on end() points to {1, 2}", "[ordered_map_iterator]") { + OrderedMap o_map = {{1, 2}}; + auto it = o_map.end(); + --it; + + REQUIRE(*it == std::make_pair(1, 2)); +} + +TEST_CASE("map containing 2 entries {1, 2}, {3, 4}, using pre-decrement operator on end() twice points to {1, 2}", "[ordered_map_iterator]") { + OrderedMap o_map = {{1, 2}, {3, 4}}; + auto it = o_map.end(); + --it; + --it; + + REQUIRE(*it == std::make_pair(1, 2)); +} + +TEST_CASE("map containing 1 entry {1, 2}, using post-decrement operator on end() points to {1, 2}", "[ordered_map_iterator]") { + OrderedMap o_map = {{1, 2}}; + auto it = o_map.end(); + it--; + + REQUIRE(*it == std::make_pair(1, 2)); +} + +TEST_CASE("map containing 2 entries {1, 2}, {3, 4}, using post-decrement operator on end() twice points to {1, 2}", "[ordered_map_iterator]") { + OrderedMap o_map = {{1, 2}, {3, 4}}; + auto it = o_map.end(); + it--; + it--; + + REQUIRE(*it == std::make_pair(1, 2)); +} + +TEST_CASE("map containing 2 entries {1, 2}, {3, 4}, post-decrement operator on end() returns end() iterator", "[ordered_map_iterator]") { + OrderedMap o_map = {{1, 2}, {3, 4}}; + auto it = o_map.end(); + auto copied_it = it--; + + REQUIRE(copied_it == o_map.end()); +} + +TEST_CASE("map containing 2 entries {1, 2}, {3, 4}, post-decrement operator on end() returns different object", "[ordered_map_iterator]") { + OrderedMap o_map = {{1, 2}, {3, 4}}; + auto it = o_map.end(); + auto copied_it = it--; + + REQUIRE(copied_it != it); +} + +TEST_CASE("map containing 2 entries {1, 2}, {3, 4}, pre-decrement operator on end() returns iterator pointing to {3, 4}", "[ordered_map_iterator]") { + OrderedMap o_map = {{1, 2}, {3, 4}}; + auto it = o_map.end(); + auto copied_it = --it; + + REQUIRE(*copied_it == std::make_pair(3, 4)); +} + +TEST_CASE("map containing 2 entries {1, 2}, {3, 4}, pre-decrement operator on end() returns same initial object", "[ordered_map_iterator]") { + OrderedMap o_map = {{1, 2}, {3, 4}}; + auto it = o_map.end(); + auto copied_it = --it; + + REQUIRE(copied_it == it); +} diff --git a/tests/ordered_map/move_to_back_tests.cpp b/tests/ordered_map/move_to_back_tests.cpp new file mode 100644 index 0000000..9f8616d --- /dev/null +++ b/tests/ordered_map/move_to_back_tests.cpp @@ -0,0 +1,40 @@ +#include +#include "ordered_map.hpp" +#include "../tests_utils.hpp" + +TEST_CASE("move_to_back() on an empty map throws an exception", "[move_to_back]") { + OrderedMap o_map; + REQUIRE_THROWS_AS(o_map.move_to_back(1), std::out_of_range); +} + +TEST_CASE("move_to_back() given key not in map throws an exception", "[move_to_back]") { + OrderedMap o_map = {{1, 1}, {2, 2}}; + REQUIRE_THROWS_AS(o_map.move_to_back(3), std::out_of_range); +} + +TEST_CASE("map containing two entries {1, 1}, {2, 2}, move_to_back(1) makes {1, 1} at the back and {2, 2} at the front", "[move_to_back]") { + OrderedMap o_map = {{1, 1}, {2, 2}}; + o_map.move_to_back(1); + + auto it = o_map.begin(); + REQUIRE(it->first == 2); + ++it; + REQUIRE(it->first == 1); +} + +TEST_CASE("map containing three entries {1, 1}, {2, 2}, {3, 3}, move_to_back(2) makes {2, 2} at the back and {1, 1} and {3, 3} at the front", "[move_to_back]") { + OrderedMap o_map = { + {1, 1}, + {2, 2}, + {3, 3} + }; + o_map.move_to_back(2); + + auto it = o_map.begin(); + REQUIRE(it->first == 1); + ++it; + REQUIRE(it->first == 3); + ++it; + REQUIRE(it->first == 2); +} + diff --git a/tests/tests_utils.hpp b/tests/tests_utils.hpp index 7146f6e..e02d647 100644 --- a/tests/tests_utils.hpp +++ b/tests/tests_utils.hpp @@ -17,4 +17,11 @@ class MoveCopyFlag { state = other.state + ", moved"; return *this; } -}; \ No newline at end of file +}; + +class DestructionFlag { + public: + bool* destroyed; + DestructionFlag(bool* a_destroyed) : destroyed(a_destroyed) {} + ~DestructionFlag() { *destroyed = true; } +};