507 lines
12 KiB
C++
507 lines
12 KiB
C++
#ifndef LINKEDLIST_H
|
|
#define LINKEDLIST_H
|
|
|
|
#include <concepts>
|
|
#include <cstdlib>
|
|
#include <expected>
|
|
#include <format>
|
|
#include <new>
|
|
#include <print>
|
|
|
|
/**
|
|
* @brief Concept that ensures a type supports equality comparison via operator==.
|
|
*
|
|
* @tparam T Type to be checked.
|
|
*/
|
|
template <typename T>
|
|
concept HasEqual = requires (T a, T b) {
|
|
{ a == b } -> std::convertible_to<bool>;
|
|
};
|
|
|
|
/**
|
|
* @brief Concept defining the requirements for types supported by LinkedList.
|
|
*
|
|
* A valid type must:
|
|
* - Be formattable (usable with std::print / std::format)
|
|
* - Support equality comparison
|
|
*
|
|
* @tparam T Type to be checked.
|
|
*/
|
|
template <typename T>
|
|
concept LinkedListSupported =
|
|
std::formattable<T, char> &&
|
|
std::equality_comparable<T> &&
|
|
HasEqual<T>;
|
|
|
|
|
|
/**
|
|
* @brief Error codes for LinkedList operations.
|
|
*/
|
|
enum class LinkedListErr {
|
|
LINKEDLIST_OK, /**< Operation completed successfully */
|
|
LINKEDLIST_BAD_ALLOC, /**< Memory allocation failed */
|
|
LINKEDLIST_OUT_OF_BOUNDS, /**< Index is out of valid range */
|
|
LINKEDLIST_INVALID_SIZE, /**< Invalid size provided */
|
|
LINKEDLIST_NOT_FOUND, /**< Element not found */
|
|
LINKEDLIST_EMPTY, /**< The list is empty */
|
|
};
|
|
|
|
|
|
/**
|
|
* @brief Converts a LinkedListErr value to its string representation.
|
|
*
|
|
* Provides a human-readable, null-terminated string corresponding to
|
|
* the given LinkedListErr enumeration value. This function is primarily
|
|
* intended for logging, debugging, and formatting purposes (e.g., with
|
|
* std::format / std::print).
|
|
*
|
|
* @param err The LinkedListErr value to convert.
|
|
*
|
|
* @return const char* A pointer to a statically allocated string literal
|
|
* representing the error. The returned pointer remains valid for the
|
|
* lifetime of the program and must not be freed.
|
|
*
|
|
* @note This function does not throw exceptions.
|
|
* @note If an unknown or invalid enumeration value is provided, the
|
|
* string "UNKNOWN_ERROR" is returned.
|
|
*
|
|
* @see std::formatter<LinkedListErr>
|
|
*/
|
|
[[nodiscard]] inline const char* to_string(LinkedListErr err) {
|
|
switch (err) {
|
|
case LinkedListErr::LINKEDLIST_OK:
|
|
return "OK";
|
|
case LinkedListErr::LINKEDLIST_BAD_ALLOC:
|
|
return "The program couldn't allocate memory for the list.";
|
|
case LinkedListErr::LINKEDLIST_OUT_OF_BOUNDS:
|
|
return "The index is out of bounds.";
|
|
case LinkedListErr::LINKEDLIST_INVALID_SIZE:
|
|
return "Is an invalid size fo the list.";
|
|
case LinkedListErr::LINKEDLIST_NOT_FOUND:
|
|
return "The value was not found.";
|
|
case LinkedListErr::LINKEDLIST_EMPTY:
|
|
return "The list is empty";
|
|
default:
|
|
return "UNKNOWN_ERROR";
|
|
}
|
|
}
|
|
|
|
template <>
|
|
struct std::formatter<LinkedListErr> : std::formatter<const char*> {
|
|
|
|
auto format(LinkedListErr err, format_context& ctx) const {
|
|
return std::formatter<const char*>::format(to_string(err), ctx);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Node of a singly linked list.
|
|
*
|
|
* @tparam T Type of data stored in the node.
|
|
*/
|
|
template<LinkedListSupported T>
|
|
class Node {
|
|
public:
|
|
T data; /**< Data stored in the node */
|
|
Node<T>* next; /**< Pointer to the next node */
|
|
};
|
|
|
|
/**
|
|
* @brief Implementation of a singly linked list.
|
|
*
|
|
* A dynamic data structure that stores elements of type T
|
|
* using nodes connected via pointers.
|
|
*
|
|
* @tparam T Type of elements stored in the list.
|
|
*/
|
|
template<LinkedListSupported T>
|
|
class LinkedList {
|
|
private:
|
|
size_t size; /**< Number of elements in the list */
|
|
Node<T>* head; /**< Pointer to the first node */
|
|
Node<T>* tail; /**< Pointer to the last node */
|
|
|
|
public:
|
|
|
|
/**
|
|
* @brief Constructs an empty linked list.
|
|
*/
|
|
LinkedList();
|
|
|
|
/**
|
|
* @brief Destroys the linked list.
|
|
*
|
|
* Releases all dynamically allocated nodes.
|
|
*/
|
|
~LinkedList();
|
|
|
|
/**
|
|
* @brief Retrieves the element at a given index.
|
|
*
|
|
* @param index Position of the element.
|
|
* @return std::expected<T, LinkedListErr> The element or an error code.
|
|
*/
|
|
std::expected<T, LinkedListErr> get(size_t index);
|
|
|
|
/**
|
|
* @brief Appends an element to the end of the list.
|
|
*
|
|
* @param value Value to insert.
|
|
* @return LinkedListErr Status code of the operation.
|
|
*/
|
|
LinkedListErr append(const T& value);
|
|
|
|
/**
|
|
* @brief Inserts an element at the beginning of the list.
|
|
*
|
|
* @param value Value to insert.
|
|
* @return LinkedListErr Status code of the operation.
|
|
*/
|
|
LinkedListErr prepend(const T& value);
|
|
|
|
/**
|
|
* @brief Removes and returns the last element of the list.
|
|
*
|
|
* @return std::expected<T, LinkedListErr> Removed element or an error.
|
|
*/
|
|
std::expected<T, LinkedListErr> pop();
|
|
|
|
/**
|
|
* @brief Removes the element at a specific index.
|
|
*
|
|
* @param index Index of the element to remove.
|
|
* @return LinkedListErr Status code.
|
|
*/
|
|
LinkedListErr remove(size_t index);
|
|
|
|
/**
|
|
* @brief Updates the value of an element at a given index.
|
|
*
|
|
* @param index Index of the element.
|
|
* @param value New value.
|
|
* @return LinkedListErr Status code.
|
|
*/
|
|
LinkedListErr set(size_t index, const T& value);
|
|
|
|
/**
|
|
* @brief Inserts a value at a specified index.
|
|
*
|
|
* @param index Target position.
|
|
* @param value Value to insert.
|
|
* @return LinkedListErr Status code.
|
|
*/
|
|
LinkedListErr insert(size_t index, const T& value);
|
|
|
|
/**
|
|
* @brief Searches for a value in the list.
|
|
*
|
|
* @param value Value to search for.
|
|
* @return std::expected<size_t, LinkedListErr> Index of the element or error.
|
|
*/
|
|
std::expected<size_t, LinkedListErr> find(const T& value);
|
|
|
|
/**
|
|
* @brief Prints all elements in the list.
|
|
*
|
|
* @return LinkedListErr Status code.
|
|
*/
|
|
LinkedListErr print();
|
|
|
|
/**
|
|
* @brief Checks whether the list is empty.
|
|
*
|
|
* @return true if the list contains no elements, false otherwise.
|
|
*/
|
|
bool is_empty();
|
|
|
|
/**
|
|
* @brief Returns the number of elements in the list.
|
|
*
|
|
* @return size_t Number of stored elements.
|
|
*/
|
|
size_t len();
|
|
|
|
/**
|
|
* @brief Clears the list by deleting all its elements.
|
|
*
|
|
* Frees all nodes and resets the list to an empty state.
|
|
*
|
|
* @note After this call, the list size is 0 and both head and tail are nullptr.
|
|
*/
|
|
void clear();
|
|
};
|
|
|
|
template <LinkedListSupported T>
|
|
LinkedList<T>::LinkedList() {
|
|
size = 0;
|
|
head = nullptr;
|
|
tail = nullptr;
|
|
}
|
|
|
|
template <LinkedListSupported T>
|
|
LinkedList<T>::~LinkedList() {
|
|
Node<T> *current = this->head;
|
|
|
|
while (current != nullptr) {
|
|
Node<T> *temp = current;
|
|
current = current->next;
|
|
delete temp;
|
|
}
|
|
}
|
|
|
|
template <LinkedListSupported T>
|
|
std::expected<T, LinkedListErr> LinkedList<T>::get(size_t index) {
|
|
if (index >= this->size) {
|
|
return std::unexpected(LinkedListErr::LINKEDLIST_OUT_OF_BOUNDS);
|
|
}
|
|
|
|
if (index == this->size - 1) {
|
|
return tail->data;
|
|
}
|
|
|
|
Node<T> *current = this->head;
|
|
for (size_t i = 0; i < index ; i++) {
|
|
current = current->next;
|
|
}
|
|
|
|
return current->data;
|
|
}
|
|
|
|
template <LinkedListSupported T>
|
|
LinkedListErr LinkedList<T>::set(size_t index, const T& val) {
|
|
if (index >= this->size) {
|
|
return LinkedListErr::LINKEDLIST_OUT_OF_BOUNDS;
|
|
}
|
|
|
|
if (index == this->size - 1) {
|
|
this->tail->data = val;
|
|
}
|
|
|
|
Node<T> *current = this->head;
|
|
for (size_t i = 0; i < index; i++) {
|
|
current = current->next;
|
|
}
|
|
|
|
current->data = val;
|
|
return LinkedListErr::LINKEDLIST_OK;
|
|
}
|
|
|
|
template <LinkedListSupported T>
|
|
LinkedListErr LinkedList<T>::insert(size_t index, const T& value) {
|
|
if (this->is_empty() && index == 0) {
|
|
return this->append(value);
|
|
}
|
|
|
|
if (index > this->size) {
|
|
return LinkedListErr::LINKEDLIST_OUT_OF_BOUNDS;
|
|
}
|
|
|
|
if (index == 0) {
|
|
return this->prepend(value);
|
|
}
|
|
|
|
if (index == this->size) {
|
|
return this->append(value);
|
|
}
|
|
|
|
Node<T>* current = this->head;
|
|
|
|
for (size_t i = 1; i < index; i++) {
|
|
current = current->next;
|
|
}
|
|
|
|
Node<T>* new_node = nullptr;
|
|
|
|
try {
|
|
new_node = new Node<T>;
|
|
} catch (const std::bad_alloc&) {
|
|
return LinkedListErr::LINKEDLIST_BAD_ALLOC;
|
|
}
|
|
|
|
new_node->data = value;
|
|
new_node->next = current->next;
|
|
current->next = new_node;
|
|
|
|
this->size++;
|
|
|
|
return LinkedListErr::LINKEDLIST_OK;
|
|
}
|
|
|
|
template <LinkedListSupported T>
|
|
LinkedListErr LinkedList<T>::append(const T& value) {
|
|
Node<T> *new_node = nullptr;
|
|
|
|
try {
|
|
new_node = new Node<T>;
|
|
} catch(std::bad_alloc&) {
|
|
return LinkedListErr::LINKEDLIST_BAD_ALLOC;
|
|
}
|
|
|
|
new_node->data = value;
|
|
new_node->next = nullptr;
|
|
|
|
if (this->head == nullptr && this->tail == nullptr) {
|
|
this->head = new_node;
|
|
this->tail = this->head;
|
|
} else {
|
|
this->tail->next = new_node;
|
|
this->tail = this->tail->next;
|
|
}
|
|
|
|
this->size += 1;
|
|
|
|
return LinkedListErr::LINKEDLIST_OK;
|
|
}
|
|
|
|
template <LinkedListSupported T>
|
|
LinkedListErr LinkedList<T>::prepend(const T& value) {
|
|
Node<T> *new_node = nullptr;
|
|
|
|
try {
|
|
new_node = new Node<T>;
|
|
} catch(std::bad_alloc&) {
|
|
return LinkedListErr::LINKEDLIST_BAD_ALLOC;
|
|
}
|
|
|
|
new_node->data = value;
|
|
new_node->next = nullptr;
|
|
|
|
if (this->head == nullptr && this->tail == nullptr) {
|
|
this->head = new_node;
|
|
this->tail = this->head;
|
|
} else {
|
|
new_node->next = this->head;
|
|
this->head = new_node;
|
|
}
|
|
|
|
this->size += 1;
|
|
|
|
return LinkedListErr::LINKEDLIST_OK;
|
|
}
|
|
|
|
template <LinkedListSupported T>
|
|
std::expected<T, LinkedListErr> LinkedList<T>::pop() {
|
|
if (this->size == 0) {
|
|
return std::unexpected(LinkedListErr::LINKEDLIST_EMPTY);
|
|
}
|
|
|
|
if (this->head->next == nullptr) {
|
|
T return_val = this->head->data;
|
|
delete this->head;
|
|
this->head = nullptr;
|
|
this->size--;
|
|
return return_val;
|
|
}
|
|
|
|
Node<T>* current = this->head;
|
|
|
|
while (current->next->next != nullptr) {
|
|
current = current->next;
|
|
}
|
|
|
|
T return_val = current->next->data;
|
|
delete current->next;
|
|
current->next = nullptr;
|
|
|
|
this->size--;
|
|
|
|
return return_val;
|
|
}
|
|
|
|
template <LinkedListSupported T>
|
|
LinkedListErr LinkedList<T>::remove(size_t index) {
|
|
if (index >= this->size) {
|
|
return LinkedListErr::LINKEDLIST_OUT_OF_BOUNDS;
|
|
}
|
|
|
|
if (index == 0) {
|
|
Node<T>* tmp = this->head;
|
|
this->head = this->head->next;
|
|
delete tmp;
|
|
this->size--;
|
|
return LinkedListErr::LINKEDLIST_OK;
|
|
}
|
|
|
|
Node<T>* current = this->head;
|
|
|
|
for (size_t i = 1; i < index; ++i) {
|
|
current = current->next;
|
|
}
|
|
|
|
Node<T>* tmp = current->next;
|
|
current->next = tmp->next;
|
|
|
|
if(tmp == this->tail) {
|
|
this->tail = current;
|
|
}
|
|
|
|
delete tmp;
|
|
this->size--;
|
|
|
|
return LinkedListErr::LINKEDLIST_OK;
|
|
}
|
|
|
|
|
|
template <LinkedListSupported T>
|
|
std::expected<size_t, LinkedListErr> LinkedList<T>::find(const T& value) {
|
|
Node<T> *current = this->head;
|
|
size_t count = 0;
|
|
while (current != nullptr) {
|
|
if (current->data == value) {
|
|
return count;
|
|
}
|
|
|
|
count++;
|
|
current = current->next;
|
|
}
|
|
|
|
return std::unexpected(LinkedListErr::LINKEDLIST_NOT_FOUND);
|
|
}
|
|
|
|
template <LinkedListSupported T>
|
|
LinkedListErr LinkedList<T>::print() {
|
|
if (this->size == 0) {
|
|
return LinkedListErr::LINKEDLIST_OK;
|
|
}
|
|
|
|
std::println("[LL]");
|
|
std::println(" |\n v");
|
|
Node<T> *current = this->head;
|
|
while (current != nullptr) {
|
|
std::println("Node({})", current->data);
|
|
std::println(" |");
|
|
std::println(" v");
|
|
current = current->next;
|
|
}
|
|
|
|
std::println("NULL");
|
|
return LinkedListErr::LINKEDLIST_OK;
|
|
}
|
|
|
|
template <LinkedListSupported T>
|
|
size_t LinkedList<T>::len() {
|
|
return this->size;
|
|
}
|
|
|
|
template <LinkedListSupported T>
|
|
bool LinkedList<T>::is_empty() {
|
|
return this->len() < 1;
|
|
}
|
|
|
|
template <LinkedListSupported T>
|
|
void LinkedList<T>::clear() {
|
|
Node<T>* current = head;
|
|
|
|
while (current != nullptr) {
|
|
Node<T>* next = current->next;
|
|
delete current;
|
|
current = next;
|
|
}
|
|
|
|
head = nullptr;
|
|
tail = nullptr;
|
|
size = 0;
|
|
}
|
|
|
|
#endif // !
|