Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,18 @@ add_executable(
tests/doubly_linked_list/pop_front_tests.cpp
tests/doubly_linked_list/push_back_tests.cpp
tests/doubly_linked_list/push_front_tests.cpp
tests/doubly_linked_list/constructor_tests.cpp
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/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/find_tests.cpp
tests/ordered_map/move_to_front_tests.cpp
)
target_link_libraries(tests PRIVATE ordered_map Catch2::Catch2WithMain)

Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,9 @@ ordered_map/
./build.sh
```

## Future Improvements
## Future Features

- [ ] Add `move_to_front` and `move_to_back` operations in ordered_map to re-order entries (without affecting or copying the entry value)

- [ ] Add const iterators
- [ ] Add reverse iterators
Expand All @@ -206,8 +208,7 @@ ordered_map/
- [ ] **DoublyLinkedList:** Add `erase` method to erase elements given their iterator
- [ ] **DoublyLinkedList:** Add pre and post decrement operators
- [ ] **DoublyLinkedList:** Test front(), back(), insertion and deletion functions for copying behavior
- [ ] **OrderedMap:** Add support for initializing map with
- [ ] **OrderedMap:** Add support for initializing map with
- [ ] **OrderedMap:** Add support for custom hash functions
- [ ] **OrderedMap:** Add `erase` method to erase elements given their iterator or key
- [ ] Test for memory leaks

182 changes: 154 additions & 28 deletions include/doubly_linked_list.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ struct Node {
std::unique_ptr<Node> next;
Node* prev;

Node(const T& a_value, Node* a_prev, std::unique_ptr<Node> a_next)
: value(a_value), prev(a_prev), next(std::move(a_next)) {}
template<typename U>
Node(U&& a_value, Node* a_prev, std::unique_ptr<Node> a_next)
: value(std::forward<U>(a_value)), prev(a_prev), next(std::move(a_next)) {}

template <typename... Args>
Node(Node* p, std::unique_ptr<Node> n, Args&&... args)
: value(std::forward<Args>(args)...), prev(p), next(std::move(n)) {}

};

template <typename T>
Expand All @@ -20,8 +26,98 @@ class DoublyLinkedList {
Node<T>* tail;
int _size;

void link_new_back_node(std::unique_ptr<Node<T>> 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<typename U>
void push_back_internal(U&& value) {
auto new_node = std::make_unique<Node<T>>(std::forward<U>(value), tail, nullptr);
link_new_back_node(std::move(new_node));
}

template<typename U>
void push_front_internal(U&& value) {
auto new_node = std::make_unique<Node<T>>(std::forward<U>(value), nullptr, std::move(head));
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<T>* 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<Node<T>> extract_node_and_link_prev_with_next(Node<T>* node) {
std::unique_ptr<Node<T>> 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<T>> node, Node<T>* 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) {}

DoublyLinkedList(std::initializer_list<T> init_list) : DoublyLinkedList() {
for (const auto& value : init_list) push_back(value);
}

template <typename ParamIterator>
DoublyLinkedList(ParamIterator begin, ParamIterator end) : DoublyLinkedList() {
for (auto it = begin; it != end; it++) {
push_back(*it);
}
}

~DoublyLinkedList() {
clear();
}
Expand All @@ -45,23 +141,22 @@ class DoublyLinkedList {
return tail->value;
}



void push_back(T&& value) {
push_back_internal(std::move(value));
}

void push_back(const T& value) {
if (!head) {
head = std::make_unique<Node<T>>(value, nullptr, nullptr);
tail = head.get();
} else {
tail->next = std::make_unique<Node<T>>(value, tail, nullptr);
tail = tail->next.get();
}
_size++;
push_back_internal(value);
}

T pop_back() {
if (!head) {
throw std::out_of_range("List is empty");
}

T popped_value = tail->value;
T popped_value = std::move(tail->value);

if (tail == head.get()) {
head.reset();
Expand All @@ -76,24 +171,20 @@ class DoublyLinkedList {
return popped_value;
}

void push_front(T&& value) {
push_front_internal(std::move(value));
}

void push_front(const T& value) {
if (!head) {
head = std::make_unique<Node<T>>(value, nullptr, nullptr);
tail = head.get();
} else {
auto new_node = std::make_unique<Node<T>>(value, nullptr, std::move(head));
new_node->next->prev = new_node.get();
head = std::move(new_node);
}
_size++;
push_front_internal(value);
}

T pop_front() {
if (!head) {
throw std::out_of_range("List is empty");
}

T popped_value = head->value;
T popped_value = std::move(head->value);
head = std::move(head->next);

if (!head)
Expand All @@ -104,7 +195,7 @@ class DoublyLinkedList {
}

void clear() {
while(size() > 0) {
while(head) {
pop_back();
}
}
Expand All @@ -115,13 +206,14 @@ class DoublyLinkedList {

class Iterator {
Node<T>* current_node_ptr;
DoublyLinkedList<T>* list_ptr;

Iterator(Node<T>* a_current) : current_node_ptr(a_current) {}
Iterator(Node<T>* a_current, DoublyLinkedList<T>* a_list_ptr) : current_node_ptr(a_current), list_ptr(a_list_ptr) {}

friend class DoublyLinkedList<T>;

public:
Iterator() : current_node_ptr(nullptr) {}
Iterator() : current_node_ptr(nullptr), list_ptr(nullptr) {}

bool operator==(const Iterator& other) const {
return current_node_ptr == other.current_node_ptr;
Expand Down Expand Up @@ -149,22 +241,56 @@ class DoublyLinkedList {
}

Iterator& operator--() {
if (current_node_ptr) {
if (current_node_ptr == nullptr) {
current_node_ptr = list_ptr->tail;
}
else if (current_node_ptr) {
current_node_ptr = current_node_ptr->prev;
}
return *this;
}
};

Iterator begin() {
return Iterator(head.get());
return Iterator(head.get(), this);
}

Iterator end() {
return Iterator(nullptr);
return Iterator(nullptr, this);
}

Iterator back_iterator() {
return Iterator(tail);
return Iterator(tail, this);
}


Iterator erase(Iterator it) {
if (empty()) throw std::out_of_range("List is empty");
if (it == end()) throw std::out_of_range("Invalid iterator");

Iterator target_it = it++;

target_it == begin() ? pop_front() :
target_it == back_iterator() ? pop_back() :
erase_middle_node(target_it.current_node_ptr);

return it;
}

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");
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());
}

template <typename... Args>
void emplace_back(Args&&... args) {
auto new_node = std::make_unique<Node<T>>(tail, nullptr, std::forward<Args>(args)...);
link_new_back_node(std::move(new_node));
}


};
25 changes: 22 additions & 3 deletions include/ordered_map.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,21 @@ class OrderedMap {

void insert(const KeyType& key, const ValueType& value) {
if (_map.find(key) == _map.end()) {
_list.push_back(std::make_pair(key, value));
_list.emplace_back(key, value);
_map[key] = _list.back_iterator();
}
else {
*_map.find(key)->second = std::make_pair(key, value);
(*_map[key]).second = value;
}
}

void insert(const KeyType& key, ValueType&& value) {
if (_map.find(key) == _map.end()) {
_list.emplace_back(key, std::move(value));
_map[key] = _list.back_iterator();
}
else {
(*_map[key]).second = std::move(value);
}
}

Expand Down Expand Up @@ -113,6 +123,15 @@ class OrderedMap {
}
return Iterator(_list.back_iterator());
}


Iterator find(const KeyType& key) {
return _map.find(key) == _map.end() ? end() : Iterator(_map[key]);
}

void move_to_front(const KeyType& key) {
if (_map.find(key) == _map.end()) {
throw std::out_of_range("Key not found in ordered map");
}
_list.move_to_begin(_map[key]);
}
};
Loading