diff --git a/Makefile b/Makefile index 597ff1c..fa08891 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,8 @@ examples: libraries $(CC) $(STD) $(DISTDIR)/bitarray-lib.o $(EXAMPLEDIR)/bitarray_example.c $(CCFLAGS) $(COMPFLAGS) -o ./$(DISTDIR)/ex_bitarray $(CC) $(STD) $(DISTDIR)/fileutils-lib.o $(EXAMPLEDIR)/fileutils_example.c $(CCFLAGS) $(COMPFLAGS) -o ./$(DISTDIR)/ex_fileutils $(CC) $(STD) $(DISTDIR)/stringlib.o $(EXAMPLEDIR)/stringlib_example.c $(CCFLAGS) $(COMPFLAGS) -o ./$(DISTDIR)/ex_stringlib + $(CC) $(STD) $(DISTDIR)/llist-lib.o $(EXAMPLEDIR)/linkedlist_example.c $(CCFLAGS) $(COMPFLAGS) -o ./$(DISTDIR)/ex_linkedlist + $(CC) $(STD) $(DISTDIR)/dllist-lib.o $(EXAMPLEDIR)/doublylinkedlist_example.c $(CCFLAGS) $(COMPFLAGS) -o ./$(DISTDIR)/ex_doublylinkedlist clean: rm -rf ./$(DISTDIR)/* diff --git a/examples/doublylinkedlist_example.c b/examples/doublylinkedlist_example.c new file mode 100644 index 0000000..dff8144 --- /dev/null +++ b/examples/doublylinkedlist_example.c @@ -0,0 +1,84 @@ +/******************************************************************************* +* Demonstrate some of the uses of the doubly linked list datastructure by +* adding people into a queue! We will make a person struct, to show it being +* used with a non-standard datatype. +* +* NOTE: One can make a queue (FIFO) using the data structure by making a +* macro for pop and push to ensure that one always pushes to the tail (-1) +* and pops from the front (0). A stack (LIFO) is best implemented using a +* doubly linked list pushes to the front and pops from the tail. +* +* Use -v to include more debugging information +*******************************************************************************/ + + +#include +#include +#include +#include +#include +#include +#include "../src/dllist.h" + + +typedef struct person { + int age; + char gender; + int height; /* feet */ + int id; /* order of getting here; for testing */ +} _person; + + +#define NUM_ELEMENTS 1000000 + +int main(int argc, char const *argv[]) { + bool verbose = false; + + if (argc == 2 && strcmp(argv[1], "-v") == 0) { + verbose = true; + } + int i; + time_t t; + srand((unsigned) time(&t)); + + dllist_t l = dll_init(); + + for (i = 0; i < NUM_ELEMENTS; i++) { + _person* p = malloc(sizeof(_person)); + p->age = rand() % 110 + 1; + p->gender = rand() % 2 == 0 ? 'm' : 'f'; + p->height = rand() % 7 + 1; + p->id = i; + if (dll_insert(l, p, -1) == DLL_FAILURE) { /* we want to append to the end each time */ + printf("Failed to allocate memory!\n"); + } + + assert(dll_num_elements(l) == (unsigned int)(i + 1)); + } + + int cnt = 0; + dll_node* n; + dll_reverse_traverse(l, n) { /* traverse for testing in reverse (from tail) */ + _person* q = (_person*) n->data; + assert(q->id == NUM_ELEMENTS - ++cnt); + } + cnt = 0; + dll_traverse(l, n) { /* traverse for testing from the head node */ + _person* q = (_person*) n->data; + assert(q->id == cnt++); + } + + for (i = 1; i <= NUM_ELEMENTS; i++) { + _person* w = (_person*) dll_remove(l, 0); + assert((unsigned int)(NUM_ELEMENTS - i) == dll_num_elements(l)); + if (verbose) { + printf("person: %d\tage: %d \tgender: %c\theight (feet): %d\n", w->id, w->age, w->gender, w->height); + } + free(w); /* it was pop'd but not yet free'd */ + } + + dll_free(l); + + printf("Completed successfully!\n"); + return 0; +} diff --git a/examples/linkedlist_example.c b/examples/linkedlist_example.c new file mode 100644 index 0000000..8ce54c6 --- /dev/null +++ b/examples/linkedlist_example.c @@ -0,0 +1,80 @@ +/******************************************************************************* +* Demonstrate some of the uses of the linked list datastructure by +* adding points as a stack! We will make a point struct, to show it being +* used with a non-standard datatype. +* +* NOTE: One can make a stake using the data structure by making a macro for +* pop and push to ensure that one always pushes to the front (0) and pops +* from the front. A stack (LIFO) is best implemented using a singly linked +* list that always does all work from the head node. +* +* Use -v to include more debugging information +*******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "../src/llist.h" + +typedef struct point { + int x; + int y; + int z; +} _point; + + +#define NUM_ELEMENTS 9000 + + +int main(int argc, char const *argv[]) { + bool verbose = false; + + if (argc == 2 && strcmp(argv[1], "-v") == 0) { + verbose = true; + } + + time_t t; + srand((unsigned) time(&t)); + + llist_t l = ll_init(); + + /* build out a list of points and add them to the stack*/ + int i; + for (i = 0; i < NUM_ELEMENTS; i++) { + _point* p = malloc(sizeof(_point)); + p->x = i; + p->y = rand() % 50; + p->z = i + 1; + ll_insert(l, p, 0); /* for a stack, everything must be up front */ + } + + /* traverse the list and make sure that each point's x is less than the previous */ + int cnt = 0; + int prev = NUM_ELEMENTS + 1; + ll_node* n; + ll_traverse(l, n) { /* this is a macro to simplify n = n->next type loops */ + _point* q = (_point*) n->data; + assert(prev > q->x); + prev = q->x; + } + + /* for a stack, we need to "pop" or remove the first element each time */ + for (i = 1; i <= NUM_ELEMENTS; i++) { + _point* w = (_point*) ll_remove(l, 0); + assert(NUM_ELEMENTS - i == w->x); + assert((unsigned int)(NUM_ELEMENTS - i) == ll_num_elements(l)); + if (verbose) { + printf("point: %d\tx: %d\ty: %d\tz: %d\n", cnt++, w->x, w->y, w->z); + } + free(w); /* it was pop'd but not yet free'd */ + } + + assert(0 == ll_num_elements(l)); + + ll_free_alt(l, true); + printf("Completed successfully!\n"); + return 0; +} diff --git a/src/dllist.c b/src/dllist.c index ce136f9..42c8189 100644 --- a/src/dllist.c +++ b/src/dllist.c @@ -1,9 +1,9 @@ #include #include +#include #include "dllist.h" - typedef struct __doubly_linked_list { dll_node* head; dll_node* tail; @@ -16,7 +16,6 @@ dllist_t dll_init() { l->head = NULL; l->tail = NULL; l->elms = 0; - return l; } @@ -50,31 +49,11 @@ dll_node* dll_last_node(dllist_t l) { } int dll_append(dllist_t l, void* data) { - dll_node* n = calloc(1, sizeof(dll_node)); - if (n == NULL) - return DLL_FAILURE; - - n->data = data; - n->next = NULL; - n->prev = NULL; - - if (l->elms == 0) { - l->tail = n; - l->head = n; - ++(l->elms); - return DLL_SUCCESS; - } - - l->tail->next = n; - n->prev = l->tail; - l->tail = n; - ++(l->elms); - - return DLL_SUCCESS; + return dll_insert(l, data, -1); } int dll_insert(dllist_t l, void * data, int idx) { - if (idx < 0 && idx <= (-1 * (int)l->elms)) + if (idx != -1 && idx < 0 && idx <= (-1 * (int)l->elms)) return DLL_FAILURE; if (idx < 0) @@ -89,38 +68,39 @@ int dll_insert(dllist_t l, void * data, int idx) { n->next = NULL; n->prev = NULL; - if (idx == 0) { - n->next = (l->head); + if (l->elms == 0) { /* first node to be added edge case */ + l->head = n; + l->tail = n; + } else if (idx == 0) { /* first node */ + n->next = l->head; n->next->prev = n; l->head = n; - ++(l->elms); - return DLL_SUCCESS; - } else if (idx >= (int)l->elms) { + } else if (idx >= (int)l->elms) { /* last node */ l->tail->next = n; n->prev = l->tail; /* we want the tail to point to the end */ l->tail = n; - ++(l->elms); - return DLL_SUCCESS; - } - - int i; - dll_node* t; - if (idx <= (int)l->elms / 2) { - t = dll_first_node(l); - for (i = 1; i < idx; i++) - t = t->next; - } else { - /* start from the tail and go backwards! */ - int stop = l->elms - idx; - t = dll_last_node(l); - for (i = 0; i < stop; i++) - t = t->prev; + } else { /* mid node; determine which direction would be fastest to get to it */ + int i; + dll_node* t; + if (idx <= (int)l->elms / 2) { + t = dll_first_node(l); + for (i = 1; i < idx; i++) + t = t->next; + } else { + /* start from the tail and go backwards! */ + int stop = l->elms - idx; + t = dll_last_node(l); + for (i = 0; i < stop; i++) + t = t->prev; + } + + n->next = t->next; + t->next = n; + n->next->prev = n; + n->prev = t; } - n->next = t->next; - t->next = n; - n->next->prev = n; - n->prev = t; + ++(l->elms); return DLL_SUCCESS; } @@ -137,52 +117,39 @@ void* dll_remove(dllist_t l, int idx) { if (idx < 0) idx = l->elms + idx; - void* ret; - dll_node* n; - if (l->elms == 1) { /* this is the oddest edge case */ - n = l->head; - ret = n->data; - l->head = NULL; - l->tail = NULL; - --l->elms; - free(n); - return ret; - } else if (idx == 0) { /* handle edge cases */ - n = l->head; - ret = n->data; + void* data; + dll_node* ret; + if (l->elms == 1) { /* remove the final node */ + ret = l->head; + l->head = l->tail = NULL; + } else if (idx == 0) { /* remove the first node */ + ret = l->head; l->head = l->head->next; l->head->prev = NULL; - --l->elms; - free(n); - return ret; } else if (idx >= (int)(l->elms - 1)) { - n = l->tail; - ret = n->data; - l->tail = n->prev; - n->prev->next = NULL; - --l->elms; - free(n); - return ret; - } - - int i; - if (idx <= (int)l->elms / 2) { - n = dll_first_node(l); - for (i = 1; i < idx; i++) - n = n->next; + ret = l->tail; + l->tail = ret->prev; + ret->prev->next = NULL; } else { - /* start from the tail and go backwards! */ - int stop = l->elms - idx; - n = dll_last_node(l); - for (i = 0; i < stop; i++) - n = n->prev; + int i; + if (idx <= (int)l->elms / 2) { + ret = dll_first_node(l); + for (i = 1; i < idx; i++) + ret = ret->next; + } else { + /* start from the tail and go backwards! */ + int stop = l->elms - idx; + ret = dll_last_node(l); + for (i = 0; i < stop; i++) + ret = ret->prev; + } + + /* move the nodes before and after's pointers around */ + ret->prev->next = ret->next; + ret->next->prev = ret->prev; } - - /* mode the nodes before and after's pointers around */ - n->prev->next = n->next; - n->next->prev = n->prev; - ret = n->data; - --l->elms; - free(n); - return ret; + data = ret->data; + --(l->elms); + free(ret); + return data; } diff --git a/src/dllist.h b/src/dllist.h index 48a30b1..e5fef4d 100644 --- a/src/dllist.h +++ b/src/dllist.h @@ -6,7 +6,7 @@ *** Author: Tyler Barrus *** email: barrust@gmail.com *** -*** Version: 0.1.0 +*** Version: 0.1.1 *** Purpose: Generic doubly linked list implementation *** *** License: MIT 2019 @@ -32,11 +32,9 @@ *** *******************************************************************************/ - #include - typedef struct __doubly_linked_list dllist; typedef struct __doubly_linked_list *dllist_t; diff --git a/src/llist.c b/src/llist.c index 274bf88..a51a5e1 100644 --- a/src/llist.c +++ b/src/llist.c @@ -45,80 +45,57 @@ ll_node* ll_first_node(llist_t l) { } int ll_append(llist_t l, void* data) { - ll_node* n = calloc(1, sizeof(ll_node)); - if (n == NULL) - return LL_FAILURE; - - n->data = data; /* no copying; required by the user */ - n->next = NULL; - /* now we need to place it at the end of the list... or at the beginning! */ - if (l->elms == 0) { - l->head = n; - ++l->elms; - return LL_SUCCESS; - } - ll_node* q = l->head; - ll_node* t = l->head; - while (q != NULL) { - t = q; - q = q->next; - } - t->next = n; - ++l->elms; - return LL_SUCCESS; + return ll_insert(l, data, l->elms + 1); } int ll_insert(llist_t l, void* data, size_t idx) { - if (idx >= l->elms) - return LL_FAILURE; ll_node* t = calloc(1, sizeof(ll_node)); + if (t == NULL) + return LL_FAILURE; + t->data = data; + t->next = NULL; - if (idx == 0) { + if (l->elms == 0 || idx == 0) { t->next = l->head; l->head = t; - ++l->elms; - return LL_SUCCESS; - } - - ll_node* n = l->head; - ll_node* trail = NULL; - size_t i; - for (i = 1; i < idx; i++) { - trail = n; - n = n->next; + } else { + ll_node* n = l->head; + size_t i = 1; + while (n->next != NULL) { + if (i++ == idx) + break; + n = n->next; + } + t->next = n->next; + n->next = t; } - trail->next = t; - t->next = n; - ++l->elms; + ++(l->elms); return LL_SUCCESS; } void* ll_remove(llist_t l, size_t idx) { - if (idx >= l->elms) { + if (idx >= l->elms) return NULL; - } ll_node* ret = l->head; if (idx == 0) { l->head = ret->next; - --l->elms; - void* data = ret->data; - free(ret); - return data; + } else { + ll_node* t = NULL; + size_t i; + for (i = 0; i < idx; i++) { + t = ret; + ret = ret->next; + } + /* update the trailing "next" */ + t->next = ret->next; } - ll_node* t = NULL; - size_t i; - for (i = 0; i < idx; i++) { - t = ret; - ret = ret->next; - } - /* update the trailing "next" */ - t->next = ret->next; + void* data = ret->data; free(ret); - --l->elms; + --(l->elms); return data; } diff --git a/src/llist.h b/src/llist.h index cc50629..3183c43 100644 --- a/src/llist.h +++ b/src/llist.h @@ -6,7 +6,7 @@ *** Author: Tyler Barrus *** email: barrust@gmail.com *** -*** Version: 0.1.0 +*** Version: 0.1.1 *** Purpose: Generic singly linked list implementation *** *** License: MIT 2019 diff --git a/tests/doubly_linked_list_test.c b/tests/doubly_linked_list_test.c index 976b5aa..2a99c02 100644 --- a/tests/doubly_linked_list_test.c +++ b/tests/doubly_linked_list_test.c @@ -62,6 +62,7 @@ MU_TEST(test_traverse) { *t = i; dll_append(l, t); } + mu_assert_int_eq(5, dll_num_elements(l)); int j = 0; dll_node* n; @@ -222,6 +223,23 @@ MU_TEST(test_insert_negative) { mu_assert_string_eq(NULL, (void*)n->next); } +MU_TEST(test_insert_negative_mid) { + int i; + for (i = 0; i < 5; i++) { + int* t = calloc(1, sizeof(int)); + *t = i; + dll_append(l, t); + } + + int* q = calloc(1, sizeof(int)); + *q = 15; + dll_insert(l, q, -2); /* insert in the second half, using a negatvie index */ + + dll_node* n = dll_last_node(l)->prev; + mu_assert_int_eq(15, *(int*)n->data); + mu_assert_string_eq((void*)dll_last_node(l), (void*)n->next); +} + MU_TEST(test_insert_error) { int i; for (i = 0; i < 5; i++) { @@ -497,6 +515,7 @@ MU_TEST_SUITE(test_suite) { MU_RUN_TEST(test_insert_first_half); MU_RUN_TEST(test_insert_second_half); MU_RUN_TEST(test_insert_negative); + MU_RUN_TEST(test_insert_negative_mid); MU_RUN_TEST(test_insert_error); /* remove */ diff --git a/tests/linked_list_test.c b/tests/linked_list_test.c index 7c48021..ba373cb 100644 --- a/tests/linked_list_test.c +++ b/tests/linked_list_test.c @@ -16,6 +16,20 @@ void test_teardown(void) { } +/* private test function - uncomment if needed! */ +/* +static void printlist(llist_t l) { + int i = 0; + ll_node* n = ll_first_node(l); + printf("\n"); + while (n != NULL) { + int* val = (int*) n->data; + printf("idx: %d\tval: %d\n", i++, *val); + n = n->next; + } +} +*/ + /******************************************************************************* * Test the setup *******************************************************************************/ @@ -93,6 +107,7 @@ MU_TEST(test_insert_beginning) { ll_node* n = ll_first_node(l); mu_assert_int_eq(15, *(int*)n->data); + mu_assert_int_eq(0, *(int*)n->next->data); mu_assert_int_eq(6, ll_num_elements(l)); int w = 0; @@ -114,7 +129,8 @@ MU_TEST(test_insert_mid) { int* q = calloc(1, sizeof(int)); *q = 15; - ll_insert(l, q, 3); // insert somewhere in the middle + ll_insert(l, q, 2); // insert somewhere in the middle + // printlist(l); mu_assert_int_eq(6, ll_num_elements(l)); ll_node* n = ll_first_node(l); @@ -122,6 +138,20 @@ MU_TEST(test_insert_mid) { mu_assert_int_eq(15, *(int*)n->data); } +MU_TEST(test_insert_end_to_large) { + int i; + for (i = 0; i < 5; i++) { + int* t = calloc(1, sizeof(int)); + *t = i; + ll_append(l, t); + } + + int* q = calloc(1, sizeof(int)); + *q = 15; + ll_insert(l, q, 7); // insert somewhere after the end... + mu_assert_int_eq(6, ll_num_elements(l)); +} + /******************************************************************************* * Test removing elements @@ -200,6 +230,21 @@ MU_TEST(test_remove_first_element) { } } +MU_TEST(test_remove_final_element) { + /* do something here! */ + int i; + for (i = 0; i < 1; i++) { + int* t = calloc(1, sizeof(int)); + *t = i; + ll_append(l, t); + } + + void* res = ll_remove(l, 0); /* the only element */ + mu_assert_int_eq(0, ll_num_elements(l)); + mu_assert_int_eq(0, *(int*)res); + free(res); +} + MU_TEST(test_remove_alt) { /* remove alt free's the memory if desired */ int i; @@ -243,12 +288,14 @@ MU_TEST_SUITE(test_suite) { MU_RUN_TEST(test_append); MU_RUN_TEST(test_insert_beginning); MU_RUN_TEST(test_insert_mid); + MU_RUN_TEST(test_insert_end_to_large); /* remove */ MU_RUN_TEST(test_remove_last_element); MU_RUN_TEST(test_remove_mid_element); MU_RUN_TEST(test_remove_first_element); MU_RUN_TEST(test_remove_alt); + MU_RUN_TEST(test_remove_final_element); MU_RUN_TEST(test_remove_error); }