From e8ba3807b843c5e196e2496a55b40be3665e202a Mon Sep 17 00:00:00 2001 From: itsmosalah Date: Tue, 27 May 2025 17:12:47 -0500 Subject: [PATCH 01/11] Simplify iterator tests, add iterator tests, make them pass - Substitute repetitive push_backs with initializer list to make tests simpler and easier to read - Create tests for edge cases when using ++ and -- on empty list - Make the tests pass by modifying the list iterator code --- include/doubly_linked_list.hpp | 10 ++ tests/doubly_linked_list/iterator_tests.cpp | 129 ++++++++++---------- 2 files changed, 73 insertions(+), 66 deletions(-) diff --git a/include/doubly_linked_list.hpp b/include/doubly_linked_list.hpp index 97f340a..0c71091 100644 --- a/include/doubly_linked_list.hpp +++ b/include/doubly_linked_list.hpp @@ -228,6 +228,9 @@ class DoublyLinkedList { } Iterator& operator++() { + if (list_ptr->empty()) + throw std::out_of_range("List is empty"); + if (current_node_ptr) { current_node_ptr = current_node_ptr->next.get(); } @@ -241,6 +244,9 @@ class DoublyLinkedList { } Iterator& operator--() { + if (list_ptr->empty()) + throw std::out_of_range("List is empty"); + if (current_node_ptr == nullptr) { current_node_ptr = list_ptr->tail; } @@ -249,6 +255,10 @@ class DoublyLinkedList { } return *this; } + + Iterator operator--(int) { + throw std::out_of_range("List is empty"); + } }; Iterator begin() { diff --git a/tests/doubly_linked_list/iterator_tests.cpp b/tests/doubly_linked_list/iterator_tests.cpp index 1606831..0a1e3db 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,68 @@ 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]") { - DoublyLinkedList list; - list.push_back(1); +TEST_CASE("list contains {1}, begin iterator dereferences to 1", "[begin]") { + DoublyLinkedList list = {1}; REQUIRE(*list.begin() == 1); } -TEST_CASE("push_back 2, begin iterator dereferences to 2", "[begin]") { - DoublyLinkedList list; - list.push_back(2); +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 begin() 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 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("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 +86,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 +112,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 +130,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 +140,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 +150,27 @@ 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 decerement (--) on end iterator returns 3", "[back_iterator]") { + DoublyLinkedList list = {1, 2, 3}; auto it = list.end(); --it; REQUIRE(*it == 3); -} \ No newline at end of file +} + + + From ea74de18d5c02f26495900ccd50dbb8b14db7daa Mon Sep 17 00:00:00 2001 From: itsmosalah Date: Tue, 27 May 2025 21:12:14 -0500 Subject: [PATCH 02/11] Add tests for list iterator and make them pass - Dereferencing end iterator - Dereferencing begin iterator on empty list - Post and pre decrement operator functionality --- include/doubly_linked_list.hpp | 10 ++++++- tests/doubly_linked_list/iterator_tests.cpp | 30 ++++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/include/doubly_linked_list.hpp b/include/doubly_linked_list.hpp index 0c71091..f2d717a 100644 --- a/include/doubly_linked_list.hpp +++ b/include/doubly_linked_list.hpp @@ -224,6 +224,12 @@ class DoublyLinkedList { } T& operator*() const { + if (list_ptr->empty()) + throw std::out_of_range("List is empty"); + + if (*this == list_ptr->end()) + throw std::out_of_range("end() iterator unexpectedly dereferenced"); + return current_node_ptr->value; } @@ -257,7 +263,9 @@ class DoublyLinkedList { } Iterator operator--(int) { - throw std::out_of_range("List is empty"); + Iterator initial_state_copy = *this; + --(*this); + return initial_state_copy; } }; diff --git a/tests/doubly_linked_list/iterator_tests.cpp b/tests/doubly_linked_list/iterator_tests.cpp index 0a1e3db..72bfd6e 100644 --- a/tests/doubly_linked_list/iterator_tests.cpp +++ b/tests/doubly_linked_list/iterator_tests.cpp @@ -23,6 +23,20 @@ TEST_CASE("list contains {1}, begin iterator != end iterator returns false", "[i REQUIRE(list.begin() != list.end()); } +TEST_CASE("dereferencing end iterator throws an exception", "[iterator]") { + DoublyLinkedList list; + auto it = list.end(); + + REQUIRE_THROWS_AS(*it, std::out_of_range); +} + +TEST_CASE("dereferencing begin iterator in an empty list throws an exception", "[iterator]") { + DoublyLinkedList list; + 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}; @@ -164,7 +178,7 @@ TEST_CASE("list contains {1, 2}, dereferencing back iterator returns 2", "[back_ REQUIRE(*it == 2); } -TEST_CASE("list contains {1, 2, 3}, using decerement (--) on end iterator returns 3", "[back_iterator]") { +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; @@ -172,5 +186,19 @@ TEST_CASE("list contains {1, 2, 3}, using decerement (--) on end iterator return REQUIRE(*it == 3); } +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); +} From 01244fcb86c574e07144cb8b37448338c51078e1 Mon Sep 17 00:00:00 2001 From: itsmosalah Date: Tue, 27 May 2025 21:36:06 -0500 Subject: [PATCH 03/11] Add tests for iterator, simplify existing tests - Create tests for dereferencing and copying iterators - Create tests for edge cases when incrementing or decrementing out of range - Simplify tests by using initializer lists and constructors using iterators --- include/doubly_linked_list.hpp | 3 + include/ordered_map.hpp | 11 ++ tests/doubly_linked_list/iterator_tests.cpp | 8 + tests/ordered_map/iterator_tests.cpp | 173 +++++++++++++++----- 4 files changed, 151 insertions(+), 44 deletions(-) diff --git a/include/doubly_linked_list.hpp b/include/doubly_linked_list.hpp index f2d717a..dbb91f1 100644 --- a/include/doubly_linked_list.hpp +++ b/include/doubly_linked_list.hpp @@ -237,6 +237,9 @@ class DoublyLinkedList { if (list_ptr->empty()) throw std::out_of_range("List is empty"); + if (*this == list_ptr->end()) + throw std::out_of_range("end() iterator unexpectedly dereferenced"); + if (current_node_ptr) { current_node_ptr = current_node_ptr->next.get(); } diff --git a/include/ordered_map.hpp b/include/ordered_map.hpp index cb6927f..a94c70c 100644 --- a/include/ordered_map.hpp +++ b/include/ordered_map.hpp @@ -94,6 +94,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; } diff --git a/tests/doubly_linked_list/iterator_tests.cpp b/tests/doubly_linked_list/iterator_tests.cpp index 72bfd6e..a58bd39 100644 --- a/tests/doubly_linked_list/iterator_tests.cpp +++ b/tests/doubly_linked_list/iterator_tests.cpp @@ -71,6 +71,14 @@ TEST_CASE("using post-decrement operator on end() in an empty list throws an exc 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(); 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); +} From 718ef1c1e3f773be5fa92f24a131ea0ceb4df446 Mon Sep 17 00:00:00 2001 From: itsmosalah Date: Tue, 27 May 2025 21:44:25 -0500 Subject: [PATCH 04/11] Refactor list class - Move private fields to the end - Move private internal methods below public ones - Make empty() rely on head ptr - Make clear() rely on empty() - Use empty() to express intent when checked --- include/doubly_linked_list.hpp | 170 ++++++++++++++++----------------- 1 file changed, 83 insertions(+), 87 deletions(-) diff --git a/include/doubly_linked_list.hpp b/include/doubly_linked_list.hpp index dbb91f1..8a59268 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) {} @@ -195,13 +112,11 @@ class DoublyLinkedList { } void clear() { - while(head) { - pop_back(); - } + while(!empty()) pop_back(); } bool empty() const { - return _size == 0; + return !head; } class Iterator { @@ -313,5 +228,86 @@ class DoublyLinkedList { 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); + } + } + + std::unique_ptr> head; + Node* tail; + int _size; }; From d2e7feca735a81b1a4e3bf732e39beadf00e46e5 Mon Sep 17 00:00:00 2001 From: itsmosalah Date: Tue, 27 May 2025 22:03:41 -0500 Subject: [PATCH 05/11] Refactor: reduce code duplication by extracting code for raising exceptions in empty list --- include/doubly_linked_list.hpp | 53 ++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/include/doubly_linked_list.hpp b/include/doubly_linked_list.hpp index 8a59268..fcf4de6 100644 --- a/include/doubly_linked_list.hpp +++ b/include/doubly_linked_list.hpp @@ -69,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); @@ -97,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); @@ -120,13 +116,6 @@ class DoublyLinkedList { } 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) {} @@ -139,21 +128,19 @@ class DoublyLinkedList { } T& operator*() const { - if (list_ptr->empty()) - throw std::out_of_range("List is empty"); + raise_exception_if_empty(); if (*this == list_ptr->end()) - throw std::out_of_range("end() iterator unexpectedly dereferenced"); + throw std::out_of_range("end iterator unexpectedly dereferenced"); return current_node_ptr->value; } Iterator& operator++() { - if (list_ptr->empty()) - throw std::out_of_range("List is empty"); + raise_exception_if_empty(); if (*this == list_ptr->end()) - throw std::out_of_range("end() iterator unexpectedly dereferenced"); + throw std::out_of_range("called ++ on end iterator"); if (current_node_ptr) { current_node_ptr = current_node_ptr->next.get(); @@ -168,8 +155,8 @@ class DoublyLinkedList { } Iterator& operator--() { - if (list_ptr->empty()) - throw std::out_of_range("List is empty"); + raise_exception_if_empty(); + if (current_node_ptr == nullptr) { current_node_ptr = list_ptr->tail; @@ -185,6 +172,17 @@ class DoublyLinkedList { --(*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() { @@ -201,8 +199,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++; @@ -214,8 +212,8 @@ 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); @@ -307,6 +305,11 @@ class DoublyLinkedList { } } + void raise_exception_if_empty() const { + if (empty()) + throw std::out_of_range("Container is empty"); + } + std::unique_ptr> head; Node* tail; int _size; From 81c7792b12c5e45b870463c753d896e4ddf47446 Mon Sep 17 00:00:00 2001 From: itsmosalah Date: Tue, 27 May 2025 22:13:29 -0500 Subject: [PATCH 06/11] Add tests for list iterator edge case & make them pass - Create tests for using pre-decrement (--iterator) on begin iterator in both empty & non-empty lists - Make the tests pass by adding a check to raise exception if it's called on begin iterator --- include/doubly_linked_list.hpp | 2 ++ tests/doubly_linked_list/iterator_tests.cpp | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/include/doubly_linked_list.hpp b/include/doubly_linked_list.hpp index fcf4de6..280f873 100644 --- a/include/doubly_linked_list.hpp +++ b/include/doubly_linked_list.hpp @@ -157,6 +157,8 @@ 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; diff --git a/tests/doubly_linked_list/iterator_tests.cpp b/tests/doubly_linked_list/iterator_tests.cpp index a58bd39..4cd7a8e 100644 --- a/tests/doubly_linked_list/iterator_tests.cpp +++ b/tests/doubly_linked_list/iterator_tests.cpp @@ -91,6 +91,20 @@ TEST_CASE("using post-increment operator on begin() in an empty list throws an e 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; + 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(); From a3b907f8e6061546c4e5b68345b180cb285dcd4c Mon Sep 17 00:00:00 2001 From: itsmosalah Date: Tue, 27 May 2025 22:21:20 -0500 Subject: [PATCH 07/11] Create tests for map.clear() function and make them pass --- CMakeLists.txt | 1 + include/ordered_map.hpp | 27 +++++++++++++++++++-------- tests/ordered_map/clear_tests.cpp | 16 ++++++++++++++++ 3 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 tests/ordered_map/clear_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bb058d4..96b88fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,7 @@ add_executable( tests/ordered_map/iterator_tests.cpp tests/ordered_map/constructor_tests.cpp tests/ordered_map/find_tests.cpp + tests/ordered_map/clear_tests.cpp tests/ordered_map/move_to_front_tests.cpp ) target_link_libraries(tests PRIVATE ordered_map_lib Catch2::Catch2WithMain) diff --git a/include/ordered_map.hpp b/include/ordered_map.hpp index a94c70c..0cadc34 100644 --- a/include/ordered_map.hpp +++ b/include/ordered_map.hpp @@ -4,17 +4,11 @@ #include -template -class OrderedMap { -private: - - DoublyLinkedList> _list; - using ListIterator = typename DoublyLinkedList>::Iterator; - std::unordered_map _map; +template +class OrderedMap { - public: OrderedMap() {} @@ -55,6 +49,15 @@ class OrderedMap { return _list.size(); } + bool empty() const { + return _list.empty(); + } + + void clear() { + _list.clear(); + _map.clear(); + } + ValueType& at(const KeyType& key) { if (_map.find(key) == _map.end()) { throw std::out_of_range("Key not found in ordered map"); @@ -69,6 +72,8 @@ class OrderedMap { return (*_map[key]).second; } + + using ListIterator = typename DoublyLinkedList>::Iterator; class Iterator { ListIterator _it; @@ -145,4 +150,10 @@ class OrderedMap { } _list.move_to_begin(_map[key]); } + +private: + + DoublyLinkedList> _list; + std::unordered_map _map; + }; \ No newline at end of file diff --git a/tests/ordered_map/clear_tests.cpp b/tests/ordered_map/clear_tests.cpp new file mode 100644 index 0000000..12df6a2 --- /dev/null +++ b/tests/ordered_map/clear_tests.cpp @@ -0,0 +1,16 @@ +#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()); +} From 080ce875876a184129a1b4c9feed23aa07586234 Mon Sep 17 00:00:00 2001 From: itsmosalah Date: Tue, 27 May 2025 22:47:44 -0500 Subject: [PATCH 08/11] Create tests for erase, and implement erase() method - Create tests for erase functionality in map - Create DestructionFlag to flag calling destructor on values when entries are erased - Add a destruction flag test for clear method as well --- CMakeLists.txt | 1 + include/ordered_map.hpp | 14 +++++++- tests/ordered_map/clear_tests.cpp | 9 +++++ tests/ordered_map/erase_tests.cpp | 60 +++++++++++++++++++++++++++++++ tests/tests_utils.hpp | 9 ++++- 5 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 tests/ordered_map/erase_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 96b88fe..4a357cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ add_executable( tests/ordered_map/find_tests.cpp tests/ordered_map/clear_tests.cpp tests/ordered_map/move_to_front_tests.cpp + tests/ordered_map/erase_tests.cpp ) target_link_libraries(tests PRIVATE ordered_map_lib Catch2::Catch2WithMain) diff --git a/include/ordered_map.hpp b/include/ordered_map.hpp index 0cadc34..cd22594 100644 --- a/include/ordered_map.hpp +++ b/include/ordered_map.hpp @@ -45,7 +45,7 @@ class OrderedMap { } } - unsigned int size() const { + size_t size() const { return _list.size(); } @@ -58,6 +58,18 @@ class OrderedMap { _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"); diff --git a/tests/ordered_map/clear_tests.cpp b/tests/ordered_map/clear_tests.cpp index 12df6a2..e8889df 100644 --- a/tests/ordered_map/clear_tests.cpp +++ b/tests/ordered_map/clear_tests.cpp @@ -14,3 +14,12 @@ TEST_CASE("clear on a non-empty map clears the map and empty returns true", "[cl 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/erase_tests.cpp b/tests/ordered_map/erase_tests.cpp new file mode 100644 index 0000000..b6fdd71 --- /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); +} \ No newline at end of file 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; } +}; From afcef1e34aa60cf0da3c82487039867db7983123 Mon Sep 17 00:00:00 2001 From: itsmosalah Date: Tue, 27 May 2025 22:59:10 -0500 Subject: [PATCH 09/11] Add map destructor tests and implement destructor --- CMakeLists.txt | 1 + include/ordered_map.hpp | 4 +++ tests/ordered_map/destructor_tests.cpp | 35 ++++++++++++++++++++++++++ tests/ordered_map/erase_tests.cpp | 2 +- 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 tests/ordered_map/destructor_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a357cc..783b016 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,7 @@ add_executable( 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/move_to_front_tests.cpp diff --git a/include/ordered_map.hpp b/include/ordered_map.hpp index cd22594..748bcb9 100644 --- a/include/ordered_map.hpp +++ b/include/ordered_map.hpp @@ -25,6 +25,10 @@ class OrderedMap { } } + ~OrderedMap() { + clear(); + } + void insert(const KeyType& key, const ValueType& value) { if (_map.find(key) == _map.end()) { _list.emplace_back(key, value); 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 index b6fdd71..04e5355 100644 --- a/tests/ordered_map/erase_tests.cpp +++ b/tests/ordered_map/erase_tests.cpp @@ -57,4 +57,4 @@ TEST_CASE("erase() a key leads to the destruction of the value", "[erase]") { o_map.erase(1); REQUIRE(destroyed); -} \ No newline at end of file +} From 4b2ea8d220eb884028718d3c1703310e7fb3be66 Mon Sep 17 00:00:00 2001 From: itsmosalah Date: Tue, 27 May 2025 23:20:23 -0500 Subject: [PATCH 10/11] Create tests for move_to_end method in list and implement the method to pass the tests --- CMakeLists.txt | 1 + include/doubly_linked_list.hpp | 8 ++++ .../doubly_linked_list/move_to_end_tests.cpp | 38 +++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 tests/doubly_linked_list/move_to_end_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 783b016..26f94a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ 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 diff --git a/include/doubly_linked_list.hpp b/include/doubly_linked_list.hpp index 280f873..b46c53e 100644 --- a/include/doubly_linked_list.hpp +++ b/include/doubly_linked_list.hpp @@ -222,6 +222,14 @@ class DoublyLinkedList { 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)...); 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"); +} From b6abaaee60f75b49f07a11109ef365bf75a870b4 Mon Sep 17 00:00:00 2001 From: itsmosalah Date: Tue, 27 May 2025 23:27:14 -0500 Subject: [PATCH 11/11] Create tests for move_to_back method in map and implement to pass the tests --- CMakeLists.txt | 3 +- include/ordered_map.hpp | 10 ++++-- tests/ordered_map/move_to_back_tests.cpp | 40 ++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 tests/ordered_map/move_to_back_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 26f94a7..2c66f03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,8 +44,9 @@ add_executable( tests/ordered_map/destructor_tests.cpp tests/ordered_map/find_tests.cpp tests/ordered_map/clear_tests.cpp - tests/ordered_map/move_to_front_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/ordered_map.hpp b/include/ordered_map.hpp index 748bcb9..dd8c996 100644 --- a/include/ordered_map.hpp +++ b/include/ordered_map.hpp @@ -4,11 +4,8 @@ #include - - template class OrderedMap { - public: OrderedMap() {} @@ -167,6 +164,13 @@ 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; 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); +} +