19-advanced-doubly-linked-l.../doubly_linked_list_with_iterator.c
2025-03-26 19:10:24 +01:00

621 lines
No EOL
17 KiB
C

/*----------------------------------------------------------
* HTBLA-Leonding / Class: 2IHIF
* ---------------------------------------------------------
* Exercise Number: S02
* Title: Doubly Linked List implementation
* Author: Marc Tismonar
* ----------------------------------------------------------
* Description:
* Implementation of a doubly linked list.
* ----------------------------------------------------------
*/
/*
Implementation notes:
1) The 'ListData' struct of this linked list SHALL have
- a pointer to the head node,
- a pointer to the tail node
- and the size of the list
as members!
2) List list, node, and iterator allocation:
Use functions `mem_alloc(…)` and `mem_free(…)`
declared in `allocator.h`. DO NOT use `malloc(…)` and `free(…)` directly
as unit tests will fail.
3) Use 'limits.h' to get maximum and minimum values for numeric types, if needed.
4) Implement 'list_iterator.h' in this file as well.
5) Avoid code duplication wherever (reasonably) possible.
This is valid for implementation of similar functions as well
as for reoccurring code patterns, such as list iteration.
Nevertheless, aim for efficiency, e.g. `remove_all` shall traverse
the list only once and not use `remove` as defined, because
the later would start at the beginning of the list for each iteration.
*/
#include "doubly_linked_list_with_iterator.h"
#include "limits.h"
#include "allocator.h"
#include "stdio.h"
/** The type of list nodes */
typedef struct IntListNodeData* IntListNode;
/** The implementation of list node data */
struct IntListNodeData {
int data;
IntListNode next;
IntListNode prev;
};
/** The implementation of list data */
struct IntListData {
IntListNode head;
IntListNode tail;
unsigned int size;
};
/** The implementation of list iterator data */
struct IntListIteratorData {
IntListNode cur;
};
/* ===================================================================== */
/* private list functions */
/* abstract away and generalize also memory allocation for list nodes */
static IntListNode list_obtain_node(int data) {
IntListNode node = (IntListNode)alloc_mem(sizeof(struct IntListNodeData));
node->data = data;
node->next = 0;
node->prev = 0;
return node;
}
static void list_release_node(IntListNode node) {
free_mem(node);
node = 0;
}
/* optional: implement a function for printing the content of the list - may be useful for debugging */
void list_dump(IntList list) {
char list_dumper_title[13] = "List dumper:";
if(!list_is_valid(list)) {
printf("%s Got invalid list\n", list_dumper_title);
return;
}
if (list_is_empty(list)) {
printf("%s Got an empty list\n", list_dumper_title);
return;
}
printf("%s Got a list with size %d\n", list_dumper_title ,list->size);
IntListIterator iterator = list_it_obtain(list);
if (iterator == 0) {
printf("%s An error occured while trying to obtain the list iterator\n", list_dumper_title);
return;
}
for (int i = 0; i < list->size; i++) {
printf("%s Node %d with value %d\n", list_dumper_title, i, list_it_get(iterator));
if(!list_it_next(iterator)) { // early break, due to being already at the end of the list
break;
}
}
list_it_release(iterator);
}
/* ===================================================================== */
/**
* Obtains ('creates') and provides a 'new' list instance.
* Any list obtained via this function MUST be released using
* function `release_list()`.
*
* Note: This function does not make any assumptions
* about how list components, esp. nodes, are allocated.
*
* @return The list instance or 0, if no list could by instantiated.
*/
IntList list_obtain() {
IntList intList = (IntList)alloc_mem(sizeof(struct IntListData));
if (intList != 0) {
intList->head = 0;
intList->tail = 0;
intList->size = 0;
}
return intList;
}
/**
* Releases a list that was obtained earlier via function `obtain_list`.
* Released lists MUST NOT be used anymore.
*
* Note: The implementation of this function does not make any assumptions
* about the allocation method of list elements, but MUST match the implementation
* of function `obtain_list` as its inverse function.
*
* @param p_list The pointer to the list to release. The value of the pointer
* is set to 0, if the list was successfully released, otherwise it is left untouched.
*/
void list_release(IntList* p_list) { // no need to use the tail & size as its going to free everything anyways, TODO: check here later
if (p_list == 0 || *p_list == 0) {
return;
}
IntList list = *p_list;
IntListNode current = list->head;
while (current != 0) {
IntListNode next = current->next;
list_release_node(current);
current = next;
}
free_mem(list);
*p_list = 0;
}
/**
* Determines whether or not the given list is valid.
*
* @param list The list to evaluate.
* @return `True` if the list is valid, false otherwise.
*/
bool list_is_valid(IntList list) {
return list != 0;
}
/**
* Determines whether or not the list contains at least one item.
*
* @param list The list to evaluate.
* @return `False` if the list contains one or more items, `true` otherwise.
*/
bool list_is_empty(IntList list) {
if (!list_is_valid(list)) {
return true;
}
return list->head == 0 && list->tail == 0 && list->size == 0;
}
/**
* Provides the number of values stored in the list.
*
* @param list The list to evaluate.
* @return The number of values the list contains.
*/
int list_get_size(IntList list) {
if(!list_is_valid(list)) {
return 0;
}
return list->size;
}
/**
* Determines whether or not the list given list contains the queried value
* at least once.
*
* @param list The list to query.
* @param value The value.
* @return `True` if the list contains at least one instance of the value,
* `false ` otherwise.
*/
bool list_contains(IntList list, int value) { // TODO: rewrite
if (!list_is_valid(list)) {
return false;
}
IntListNode current = list->head;
while (current != 0) {
if (current->data == value) {
return true;
}
current = current->next;
}
return false;
}
/**
* Provides the value stored in the list at the given position.
*
* @param list The list from which the value shall be retrieved.
* @param index The zero-based position index of the value to retrieve.
* @return The value stored at the given position or 0, if the position
* is not available.
*/
int list_get_at(IntList list, unsigned int index) { // TODO: rewrite?
if (!list_is_valid(list)) {
return 0;
}
unsigned int i = 0;
IntListNode current = list->head;
while (current != 0) {
if (i == index) {
return current->data;
}
current = current->next;
i++;
}
return 0;
}
/**
* Inserts the given value at the end of the given list.
*
* @param list The list to which the value shall be appended.
* @param value The value to append to the list.
*/
void list_insert(IntList list, int value) {
if (!list_is_valid(list)) {
return;
}
IntListNode newNode = list_obtain_node(value);
if (newNode != 0) {
if (list_is_empty(list)) {
list->head = newNode;
}
} else {
list->tail->next = newNode;
}
list->tail = newNode;
list->size++;
}
/**
* Inserts the given value at the indexed position in a way the
* the inserted value is on that position. The index is
* - similar to arrays - zero-based. If the the list is shorter
* than the indexed position, the value is inserted at the end
* of the list.
*
* @param list The list into which the value shall be appended.
* @param index The position index of the value to insert.
* @param value The value to insert.
*/
void list_insert_at(IntList list, unsigned int index, int value) { // TODO: rewrite
if (!list_is_valid(list)) {
return;
}
IntListNode newNode = list_obtain_node(value);
if (list_is_empty(list)) {
list->head = newNode;
return;
}
if (index == 0) {
newNode->next = list->head;
list->head = newNode;
return;
}
unsigned int i = 0;
IntListNode current = list->head;
IntListNode previous = 0;
while (current != 0 && i < index) {
previous = current;
current = current->next;
i++;
}
previous->next = newNode;
newNode->next = current;
}
/**
* Appends the `list_to_append` at the end of the given `list`.
* The appended list is empty afterwards, because all nodes of that list
* have been transferred to `list`.
*
* @param list The list that receives the other list.
* @param list_to_append The list that is appended to `list`.
*/
void list_append(IntList list, IntList list_to_append) {
if (!list_is_valid(list) || !list_is_valid(list_to_append) || list_is_empty(list_to_append)) { // TODO: rewrite
return;
}
if (list_is_empty(list)) {
list->head = list_to_append->head;
} else {
IntListNode current = list->head;
while (current->next != 0) {
current = current->next;
}
current->next = list_to_append->head;
}
list_to_append->head = 0;
}
/**
* Removes the first occurrance of `value` from the given list.
* If the list does not contain that value, the list shall not
* be modified.
*
* @param list The list from which the given value shall be removed.
* @param value The value to remove from the list.
*/
void list_remove(IntList list, int value) { // TODO: rewrite
if (!list_is_valid(list) || list_is_empty(list)) {
return;
}
if (list->head->data == value) {
IntListNode old_head = list->head;
list->head = list->head->next;
list_release_node(old_head);
return;
}
IntListNode current = list->head;
while (current->next != 0) {
if (current->next->data == value) {
IntListNode to_remove = current->next;
current->next = to_remove->next;
list_release_node(to_remove);
return;
}
current = current->next;
}
}
/**
* Removes all occurrances of `value` from the list.
* If the list does not contain that value, the list shall not
* be modified.
*
* @param list The list from which all occurrances of `value` shall be removed.
* @param value The `value` to remove throughout the list.
*/
void list_remove_all(IntList list, int value) { // TODO: rewrite
if (!list_is_valid(list) || list_is_empty(list)) {
return;
}
while (list->head != 0 && list->head->data == value) {
IntListNode old_head = list->head;
list->head = list->head->next;
list_release_node(old_head);
}
if (list_is_empty(list)) {
return;
}
IntListNode current = list->head;
while (current->next != 0) {
if (current->next->data == value) {
IntListNode to_remove = current->next;
current->next = to_remove->next;
list_release_node(to_remove);
} else {
current = current->next;
}
}
}
/**
* Removes the value at the indexed position from the given list
* and provides that value. If the list does not have a value
* at that position, the list remains unmodified.
*
* @param list The list from which the value at the given index shall be returned.
* @param index The zero-based index of the value to return.
* @return The removed value or 0 in case of errors.
*/
int list_remove_at(IntList list, unsigned int index) { // TODO: rewrite
if (!list_is_valid(list) || list_is_empty(list)) {
return 0;
}
if (index == 0) {
int value = list->head->data;
IntListNode old_head = list->head;
list->head = list->head->next;
list_release_node(old_head);
return value;
}
unsigned int i = 0;
IntListNode current = list->head;
IntListNode previous = 0;
while (current != 0 && i < index) {
previous = current;
current = current->next;
i++;
}
if (current == 0) {
return 0;
}
int value = current->data;
previous->next = current->next;
list_release_node(current);
return value;
}
/**
* Clears the given list by removing all values from the list.
*
* @param list The list to clear.
*/
void list_clear(IntList list) { // TODO: rewrite?
if (!list_is_valid(list) || list_is_empty(list)) {
return;
}
IntListNode current = list->head;
while (current != 0) {
IntListNode next = current->next;
list_release_node(current);
current = next;
}
list->head = 0;
}
/* ===================================================================== */
/**
* Obtains ('creates') and provides a 'new' list iterator instance for the given list.
* The provided iterator initially points to the head node of the list.
*
* Any iterator obtained via this function MUST be released using
* function `release_iterator()`.
*
* Note: This function does not make any assumptions
* about how list components, esp. nodes, are allocated.
*
* @param list The list for which the iterator is obtained.
* @return The list iterator instance or 0, if no list iterator could by instantiated.
*/
IntListIterator list_it_obtain(IntList list) {
if (!list_is_valid (list)) {
return 0;
}
IntListIterator iterator = (IntListIterator)alloc_mem(sizeof(struct IntListIteratorData));
iterator->cur = list->head;
return iterator;
}
/**
* Releases a list iterator that was obtained earlier via function `list_it_obtain`.
* Released list iterators MUST NOT be used anymore.
*
* Note: The implementation of this function does not make any assumptions
* about the allocation method of list iterator elements, but MUST match the implementation
* of function `list_it_obtain` as its inverse function.
*
* @param p_it The pointer to the list iterator to release. The value of the pointer
* is set to 0, if the list iterator was successfully released, otherwise it is left untouched.
*/
void list_it_release(IntListIterator* p_it) {
free_mem(p_it);
p_it = 0;
}
/**
* Determines whether or not the given list iterator is valid.
*
* @param it The list iterator to evaluate.
* @return `True` if the list iterator is valid, false otherwise.
*/
bool list_it_is_valid(IntListIterator it) {
return it != 0;
}
/**
* Proceeds the list iterator to the next list element, if possible.
*
* @param it The list iterator to evaluate.
* @return `True` if the list iterator could proceed to the next list node, `false` otherwise.
*/
bool list_it_next(IntListIterator it) {
if (it->cur->next == 0) {
return false;
}
it->cur = it->cur->next;
return true;
}
/**
* Proceeds the list iterator to the previous list element, if possible.
*
* @param it The list iterator to evaluate.
* @return `True` if the list iterator could proceed to the previous list node, `false` otherwise.
*/
bool list_it_previous(IntListIterator it) {
if (it->cur->prev == 0) {
return false;
}
it->cur = it->cur->prev;
return true;
}
/**
* Provides the value of the node the list iterator currently points to.
*
* @param it The list iterator to evaluate.
* @return The value of the current list node under the iterator.
*/
int list_it_get(IntListIterator it) {
if (!list_it_is_valid(it)) {
return 0;
}
return it->cur->data;
}
/**
* Applies the given value to the node the list iterator currently points to.
*
* @param it The list iterator to evaluate.
* @param value The value to set to the current list node under the iterator.
*/
void list_it_set(IntListIterator it, int value) {
if (!list_it_is_valid(it)) {
return;
}
it->cur->data = value;
}
/**
* Inserts the given value after the node under the iterator and proceeds the iterator to the
* inserted node.
*
* @param it The list iterator to evaluate.
* @param value The value to insert.
*/
void list_it_insert(IntListIterator it, int value) {
if (!list_it_is_valid(it)) {
return;
}
IntListNode newNode = list_obtain_node(value);
if (newNode != 0) {
it->cur->next = newNode;
}
}
/**
* Removes the node under the iterator and proceeds the iterator to the 'next' node.
*
* @param it The list iterator to evaluate.
*/
void list_it_remove(IntListIterator it) {
if (!list_it_is_valid(it)) {
return;
}
IntListNode nextNode = it->cur->next;
list_release_node(it->cur);
it->cur = nextNode;
}