diff --git a/include/list.h b/include/list.h index 6f6f129..233297a 100644 --- a/include/list.h +++ b/include/list.h @@ -59,6 +59,15 @@ typedef struct List int len; } List; +/** + * @brief @ref ListItr direction + */ +enum ListItrDirection +{ + LIST_ITR_FORWARD, + LIST_ITR_REVERSE, +}; + /** * @brief An iterator into a list */ @@ -80,6 +89,11 @@ typedef struct * The node of the most recently returned data from @ref list_itr_next() . */ ListNode* current_node; + + /** + * @brief The direction of the iterator + */ + enum ListItrDirection direction; } ListItr; /** @@ -128,24 +142,75 @@ void list_push_front(List* list, void* data); void list_push_back(List* list, void* data); /** - * Get a List's node at it's nth index + * Insert data into a @ref List a specific index + * + * If the index specified is larger than the length of the list + * it will @ref list_push_back() the data instead; + * + * Performs the following operation: + * + * ┌─────┐ + * │ node│ + * └─────┘ + * ┌─────┐ ┌─────┐ ┌─────┐ + * │idx-1│◄─►│ idx │◄─►│idx+1│ + * └─────┘ └─────┘ └─────┘ + * + * 1. Set new `node` `prev` to the node at idx - 1 + * 2. Set new `node` `next` to the node at idx + * 3. Set node at idx - 1 `next` to new `node` + * 4. Set node at idx `prev` to the new `node` + * + * Result: + * + * ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ + * │idx-1│◄─►│ node│◄─►│ idx │◄─►│idx+1│ + * └─────┘ └─────┘ └─────┘ └─────┘ + * + * Finally, the list is now updated with new `node` now at the labeled idx: + * + * ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ + * │idx-1│◄─►│ idx │◄─►│idx+1│◄─►│idx+2│ + * └─────┘ └─────┘ └─────┘ └─────┘ * * @param list pointer to a @ref List - * @param n index of the desired @ref ListNode in the list - * - * @return a pointer to the data at the nth @ref ListNode, or NULL if out-of-bounds + * @param data pointer to data to put into the @ref List + * @param idx desired index to insert */ -void* list_get_at_idx(List *list, int n); +void list_insert(List* list, void* data, unsigned int idx); /** - * Remove a List's node at it's nth index + * Swap the data pointers at the specified indices of a @ref List + * + * If either indices are larger than the length of the list, return false. * * @param list pointer to a @ref List - * @param n index of the desired @ref ListNode in the list + * @param idx_a desired index to swap with idx_b + * @param idx_b desired index to swap with idx_a + * + * @return true if successful, false otherwise + */ +bool list_swap(List* list, unsigned int idx_a, unsigned int idx_b); + +/** + * Get a List's node at the specified index + * + * @param list pointer to a @ref List + * @param idx index of the desired @ref ListNode in the list + * + * @return a pointer to the data at the index of the list, or NULL if out-of-bounds + */ +void* list_get_at_idx(List *list, unsigned int idx); + +/** + * Remove a List's node at the specified index + * + * @param list pointer to a @ref List + * @param idx index of the desired @ref ListNode in the list * * @return `true` if successfully removed, `false` if out-of-bounds */ -bool list_remove_at_idx(List *list, int n); +bool list_remove_at_idx(List *list, unsigned int idx); /** * Get the number of elements in a @ref List @@ -165,6 +230,15 @@ int list_get_len(const List* list); */ ListItr list_itr_create(List* list); +/** + * Declare a reverse @ref ListItr + * + * @param list pointer to a @ref List + * + * @return A new reverse @ref ListItr + */ +ListItr rev_list_itr_create(List* list); + /** * Get the next data entry in a @ref ListItr * diff --git a/source/list.c b/source/list.c index e8c55e8..ffb764f 100644 --- a/source/list.c +++ b/source/list.c @@ -1,5 +1,4 @@ #include -#include #include "list.h" #include "pool.h" @@ -100,6 +99,81 @@ void list_push_back(List *list, void* data) list->len++; } +void list_insert(List* list, void* data, unsigned int idx) +{ + if(idx >= list->len) + { + list_push_back(list, data); + return; + } + + if(idx == 0) + { + list_push_front(list, data); + return; + } + + // After the above two checks the index is guaranteed to be inbetween the + // `head` and `tail` of the `list`. This means the actual list doesn't need + // to modify it's head and tail, only it's length. Simplifying the code below: + + unsigned int curr_idx = 0; + ListItr itr = list_itr_create(list); + ListNode* ln; + + while((ln = _list_itr_node_next(&itr))) + { + if(idx == curr_idx++) + { + ListNode *node = POOL_GET(ListNode); + node->prev = ln->prev; + node->next = ln; + ln->prev->next = node; + ln->prev = node; + node->data = data; + list->len++; + return; + } + } +} + +bool list_swap(List* list, unsigned int idx_a, unsigned int idx_b) +{ + if(idx_a >= list->len || idx_b >= list->len) return false; + if(idx_a == idx_b) return true; // swapping with yourself isn't technically an error + + unsigned int curr_idx = 0; + unsigned int max_idx = idx_a > idx_b ? idx_a : idx_b; + ListNode* node_a = NULL; + ListNode* node_b = NULL; + + ListItr itr = list_itr_create(list); + ListNode* ln; + + do + { + ln = _list_itr_node_next(&itr); + if(idx_a == curr_idx) + { + node_a = ln; + continue; + } + if(idx_b == curr_idx) + { + node_b = ln; + continue; + } + } + while(max_idx != curr_idx++); + + // Just swap the data pointers + void* tmp = node_a->data; + node_a->data = node_b->data; + node_b->data = tmp; + + return true; +} + static void _list_remove_node(List *list, ListNode *node) { if(node->prev && !node->next) // end of list @@ -133,9 +207,9 @@ int list_get_len(const List* list) return list->len; } -void* list_get_at_idx(List* list, int n) +void* list_get_at_idx(List* list, unsigned int idx) { - if(n >= list_get_len(list) || n < 0) return NULL; + if(idx >= list_get_len(list)) return NULL; int curr_idx = 0; ListItr itr = list_itr_create(list); @@ -143,15 +217,15 @@ void* list_get_at_idx(List* list, int n) while((data = list_itr_next(&itr))) { - if (n == curr_idx++) return data; + if (idx == curr_idx++) return data; } return NULL; } -bool list_remove_at_idx(List* list, int n) +bool list_remove_at_idx(List* list, unsigned int idx) { - if(n >= list_get_len(list) || n < 0) return false; + if(idx >= list_get_len(list)) return false; int len = 0; ListItr itr = list_itr_create(list); @@ -159,7 +233,7 @@ bool list_remove_at_idx(List* list, int n) while((ln = _list_itr_node_next(&itr))) { - if(n == len++) + if(idx == len++) { _list_remove_node(list, ln); return true; @@ -175,6 +249,20 @@ ListItr list_itr_create(List* list) .list = list, .next_node = !list_is_empty(list) ? list->head : NULL, .current_node = NULL, + .direction = LIST_ITR_FORWARD, + }; + + return itr; +} + +ListItr rev_list_itr_create(List* list) +{ + ListItr itr = + { + .list = list, + .next_node = !list_is_empty(list) ? list->tail : NULL, + .current_node = NULL, + .direction = LIST_ITR_REVERSE, }; return itr; @@ -193,10 +281,11 @@ static ListNode* _list_itr_node_next(ListItr* itr) itr->current_node = itr->next_node; ListNode* ln = itr->next_node; + ListNode* next_itr_node = (itr->direction == LIST_ITR_FORWARD) ? ln->next : ln->prev; - if(ln->next) + if(next_itr_node) { - itr->next_node = ln->next; + itr->next_node = next_itr_node; return ln; } diff --git a/tests/list/list_test.c b/tests/list/list_test.c index 647df13..eafc13e 100644 --- a/tests/list/list_test.c +++ b/tests/list/list_test.c @@ -286,6 +286,382 @@ void push_back_three_remove_push_front_three_entries(void) assert(list_is_empty(&my_cool_list)); } +// Test inserting at head, middle, and tail of list +// - list_create +// - list_push_back +// - list_insert +// - list_is_empty +// - list_get_len +// - list_itr_create +// - rev_list_itr_create +// - list_itr_next +// - list_clear +// - list_get_at_idx +void test_list_insertion(void) +{ + List my_cool_list = list_create(); + + // verify no data + assert(my_cool_list.head == NULL); + assert(my_cool_list.tail == NULL); + assert(list_get_len(&my_cool_list) == 0); + assert(list_is_empty(&my_cool_list)); + + const int initial_list_size = 5; + int test_data[initial_list_size]; + + for(int i = 0; i < initial_list_size; i++) + { + test_data[i] = i; + list_push_back(&my_cool_list, &test_data[i]); + } + + assert(list_get_len(&my_cool_list) == initial_list_size); + + int head_val = 0xDEADBEEF; + int middle_val = 0x1337; + int tail_val = 0x10101010; + int last_tail_val = 0x12345678; + + // insert at head of list + list_insert(&my_cool_list, &head_val, 0); + + ListItr itr = list_itr_create(&my_cool_list); + void* data; + int test_data_itr = 0; + + int index = 0; + + while((data = list_itr_next(&itr))) + { + int data_itr = *(int*)data; + int data_from_get_at_idx = *(int*)list_get_at_idx(&my_cool_list, index); + + if(index == 0) + { + assert(data_itr == head_val); + assert(data_from_get_at_idx == head_val); + } + else + { + assert(data_itr == test_data[test_data_itr]); + assert(data_from_get_at_idx == test_data[test_data_itr]); + test_data_itr++; + } + index++; + } + + assert(list_get_len(&my_cool_list) == initial_list_size + 1); + + // insert at tail of list, use large value of 100 to force it to append it + // to the end of the list + list_insert(&my_cool_list, &tail_val, 100); + + itr = list_itr_create(&my_cool_list); + index = 0; + test_data_itr = 0; + + while((data = list_itr_next(&itr))) + { + int data_itr = *(int*)data; + int data_from_get_at_idx = *(int*)list_get_at_idx(&my_cool_list, index); + + if(index == 0) + { + assert(data_itr == head_val); + assert(data_from_get_at_idx == head_val); + } + else if(index == initial_list_size + 1) // for head and tail insertion + { + assert(data_itr == tail_val); + assert(data_from_get_at_idx == tail_val); + } + else + { + assert(data_itr == test_data[test_data_itr]); + assert(data_from_get_at_idx == test_data[test_data_itr]); + test_data_itr++; + } + index++; + } + + assert(list_get_len(&my_cool_list) == initial_list_size + 2); + + // insert at "middle" of list + int insert_loc = 2; + test_data_itr = 0; + list_insert(&my_cool_list, &middle_val, insert_loc); + + itr = list_itr_create(&my_cool_list); + index = 0; + + while((data = list_itr_next(&itr))) + { + int data_itr = *(int*)data; + int data_from_get_at_idx = *(int*)list_get_at_idx(&my_cool_list, index); + + if(index == 0) + { + assert(data_itr == head_val); + assert(data_from_get_at_idx == head_val); + } + else if(index == insert_loc) + { + assert(data_itr == middle_val); + assert(data_from_get_at_idx == middle_val); + } + else if(index == initial_list_size + 2) // for head+middle+tail + { + assert(data_itr == tail_val); + assert(data_from_get_at_idx == tail_val); + } + else + { + assert(data_itr == test_data[test_data_itr]); + assert(data_from_get_at_idx == test_data[test_data_itr]); + test_data_itr++; + } + index++; + } + + printf("\n"); + + ListItr rev_itr = rev_list_itr_create(&my_cool_list); + index = initial_list_size + 2; + test_data_itr = initial_list_size - 1; + while((data = list_itr_next(&rev_itr))) + { + int data_itr = *(int*)data; + int data_from_get_at_idx = *(int*)list_get_at_idx(&my_cool_list, index); + + if(index == 0) + { + assert(data_itr == head_val); + assert(data_from_get_at_idx == head_val); + } + else if(index == insert_loc) + { + assert(data_itr == middle_val); + assert(data_from_get_at_idx == middle_val); + } + else if(index == initial_list_size + 2) // for head+middle+tail + { + assert(data_itr == tail_val); + assert(data_from_get_at_idx == tail_val); + } + else + { + assert(data_itr == test_data[test_data_itr]); + assert(data_from_get_at_idx == test_data[test_data_itr]); + test_data_itr--; + } + index--; + } + + assert(list_get_len(&my_cool_list) == initial_list_size + 3); + + // insert at the real end of the list, to make sure + // it works in conjunction with the previous "out of range" insertion for the tail + int tail_idx = list_get_len(&my_cool_list) - 1; + test_data_itr = 0; + list_insert(&my_cool_list, &last_tail_val, tail_idx); + + itr = list_itr_create(&my_cool_list); + index = 0; + + while((data = list_itr_next(&itr))) + { + int data_itr = *(int*)data; + int data_from_get_at_idx = *(int*)list_get_at_idx(&my_cool_list, index); + + if(index == 0) + { + assert(data_itr == head_val); + assert(data_from_get_at_idx == head_val); + } + else if(index == insert_loc) + { + assert(data_itr == middle_val); + assert(data_from_get_at_idx == middle_val); + } + else if(index == initial_list_size + 2) // for head+middle+tail + { + assert(data_itr == last_tail_val); + assert(data_from_get_at_idx == last_tail_val); + } + else if(index == initial_list_size + 3) // for head+middle+tail+last_tail + { + assert(data_itr == tail_val); + assert(data_from_get_at_idx == tail_val); + } + else + { + assert(data_itr == test_data[test_data_itr]); + assert(data_from_get_at_idx == test_data[test_data_itr]); + test_data_itr++; + } + index++; + } + + assert(list_get_len(&my_cool_list) == initial_list_size + 4); + + // clear the list + list_clear(&my_cool_list); + + // verify no data + assert(my_cool_list.head == NULL); + assert(my_cool_list.tail == NULL); + assert(list_is_empty(&my_cool_list)); +} + +// Test inserting at head, middle, and tail of list +// - list_create +// - list_push_back +// - list_swap +// - list_is_empty +// - list_get_len +// - list_itr_create +// - list_itr_next +// - list_clear +// - list_get_at_idx +void test_list_swap(void) +{ + List my_cool_list = list_create(); + + // verify no data + assert(my_cool_list.head == NULL); + assert(my_cool_list.tail == NULL); + assert(list_get_len(&my_cool_list) == 0); + assert(list_is_empty(&my_cool_list)); + + const int initial_list_size = 5; + int test_data[initial_list_size]; + + for(int i = 0; i < initial_list_size; i++) + { + // 0 -> 1 -> 2 -> 3 -> 4 + test_data[i] = i; + list_push_back(&my_cool_list, &test_data[i]); + } + + assert(list_get_len(&my_cool_list) == initial_list_size); + + + ListItr itr = list_itr_create(&my_cool_list); + void* data; + + int index = 0; + + while((data = list_itr_next(&itr))) + { + // 0 -> 1 -> 2 -> 3 -> 4 + assert(*(int*)list_get_at_idx(&my_cool_list, index) == test_data[index]); + assert(*(int*)data == test_data[index]); + index++; + } + + // swap nothing, out of range + assert(!list_swap(&my_cool_list, 100, 100)); + + itr = list_itr_create(&my_cool_list); + index = 0; + while((data = list_itr_next(&itr))) + { + // 0 -> 1 -> 2 -> 3 -> 4 + assert(*(int*)list_get_at_idx(&my_cool_list, index) == test_data[index]); + assert(*(int*)data == test_data[index]); + index++; + } + + // swap nothing, in range + assert(list_swap(&my_cool_list, 0, 0)); + + itr = list_itr_create(&my_cool_list); + index = 0; + while((data = list_itr_next(&itr))) + { + // 0 -> 1 -> 2 -> 3 -> 4 + assert(*(int*)list_get_at_idx(&my_cool_list, index) == test_data[index]); + assert(*(int*)data == test_data[index]); + index++; + } + + // swap head and "middle" + int mid_idx = initial_list_size / 2; + assert(list_swap(&my_cool_list, 0, mid_idx)); + + itr = list_itr_create(&my_cool_list); + index = 0; + + while((data = list_itr_next(&itr))) + { + int data_itr = *(int*)data; + int data_from_get_at_idx = *(int*)list_get_at_idx(&my_cool_list, index); + + // 2 -> 1 -> 0 -> 3 -> 4 + if(index == 0) + { + assert(data_itr == test_data[mid_idx]); + assert(data_from_get_at_idx == test_data[mid_idx]); + } + else if(index == mid_idx) + { + assert(data_itr == test_data[0]); + assert(data_from_get_at_idx == test_data[0]); + } + else + { + assert(data_itr == test_data[index]); + assert(data_from_get_at_idx == test_data[index]); + } + index++; + } + + // swap tail and "middle" + assert(list_swap(&my_cool_list, initial_list_size - 1, mid_idx)); + + itr = list_itr_create(&my_cool_list); + index = 0; + + while((data = list_itr_next(&itr))) + { + int data_itr = *(int*)data; + int data_from_get_at_idx = *(int*)list_get_at_idx(&my_cool_list, index); + + // 2 -> 1 -> 4 -> 3 -> 0 + if(index == 0) + { + assert(data_itr == test_data[mid_idx]); + assert(data_from_get_at_idx == test_data[mid_idx]); + } + else if(index == mid_idx) + { + assert(data_itr == test_data[initial_list_size - 1]); + assert(data_from_get_at_idx == test_data[initial_list_size - 1]); + } + else if(index == initial_list_size - 1) + { + assert(data_itr == test_data[0]); + assert(data_from_get_at_idx == test_data[0]); + } + else + { + assert(data_itr == test_data[index]); + assert(data_from_get_at_idx == test_data[index]); + } + index++; + } + + // clear the list + list_clear(&my_cool_list); + + // verify no data + assert(my_cool_list.head == NULL); + assert(my_cool_list.tail == NULL); + assert(list_is_empty(&my_cool_list)); +} + + int main(void) { printf("Testing List Create and Clear.\n"); @@ -296,9 +672,16 @@ int main(void) printf("Testing List Push Front.\n"); push_front_one_entry(); + printf("Testing List Complete Exercise.\n"); push_back_three_remove_push_front_three_entries(); + printf("Testing List Insertion.\n"); + test_list_insertion(); + + printf("Testing List Swap.\n"); + test_list_swap(); + printf("-------------------------------------------------------------------------------\n"); printf("List Tests Passed :)\n"); printf("-------------------------------------------------------------------------------\n");