Structs and Linked Lists
Problem
Linked Lists are a simple data structure that consist of a node that has a piece of data, and a
pointer to the next node as it’s elements. This is simple to represent in C using structs, where if
we have a Node
struct, it would have two elements: a piece of data, and a pointer to a Node
struct.
Your goal is to create 5 functions that you’ll need to work with Linked Lists in C.
The Node struct
Before begining, we need to know what our linked list will be represented as. You need to create a
new type struct called Node
that contains 2 attributes: the first one will be the data for that
element. We’re going to assume that it’s a string for now, although you can make it anything. The
second attribute is going to be a pointer to the next Node
.
// We use typedef so that we can avoid writing struct Node everywhere // instead of just Node. // Note that with typedef we also need to include the name after since // the struct and the type don't necessarily need to have the same name. typedef struct Node { char *data; struct Node *next; } Node;
llCons
The llCons
function will return a Node
struct and should take in 2 arguments: a variable that
contains data as the same type as your Node
struct. We’ll assume a string for now. And the second
should be a struct that will become the tail of the returned Node
.
Example usage:
int main() { Node el3 = llCons("element 3", null); Node el2 = llCons("element 2", &el3); Node el1 = llCons("element 1", &el2); printf("%s", el1.data); // element 1 printf("%s", el1.next.data); // element 2 printf("%s", el1.next.next.data); // element 3 return 0; }
Node *llCons(char *data, Node *next) { Node *newNode = malloc(sizeof(Node)); newNode->data = data; newNode->next = next; return newNode; }
llNew
The llNew
function should return a new list given the elements in an array. Given an array of
strings (or whatever the datatype you’re using is) and the length of the array, llNew
should create
a new linked list and return it’s head.
int main() { Node list = llNew({"element 1", "element 2", "element 3"}, 3); printf("%s", list.data); // element 1 printf("%s", list.next.data); // element 2 printf("%s", list.next.next.data); // element 3 return 0; }
Node *llNew(char **strs, int count) { if (count == 0) { return NULL; } // Here we're doing pointer arithmetic and manually incrementing the // value of our pointer by 1 so that the "first element" in the array // passed in recursively becomes the next element sequentially since // arrays are contiguous in memory. Node *next = llNew(strs + 1, count - 1); return llCons(strs[0], next); }
llHead
llHead
should accept a Node as an argument and return the data for the first element in the linked
list.
int main() { Node list = llNew({"element 1", "element 2", "element 3"}, 3); printf("%s", llHead(list)); // element 1 return 0; }
char *llHead(Node *list) { // We use -> because list is a pointer, this dereferences for us. return list->data; }
llTail
llTail
should take in a Node and return the tail of the list.
int main() { Node list = llNew({"element 1", "element 2", "element 3"}, 3); printf("%s", llHead(llTail(list))); // element 2 return 0; }
char *llTail(Node *list) { // Pretty much just the same as llHead return list->next; }
llNth
llNth
should take a list as an argument and an integer, and return the data for the nth element in
the list. If the number is greater than the length of the list, return null.
int main() { Node list = llNew({"element 1", "element 2", "element 3"}, 3); printf("%s", llNth(list, 0)); // element 1 printf("%s", llNth(list, 1)); // element 2 printf("%s", llNth(list, 2)); // element 3 return 0; }
char *llNth(Node *list, int n) { Node *curr = list; for (int i = 0; i < n; i++) { if (curr->next != NULL) { curr = curr->next; } else { return NULL; } } return curr->data; }
llAppend
llAppend
should take 2 Nodes as arguments and return a Node. The function should find the last
Node in the first list passed in, and make it’s next
be the head of the second list passed in.
int main() { Node list1 = llNew({"element 1", "element 2", "element 3"}, 3); Node list2 = llNew({"element 4", "element 5", "element 6"}, 3); Node appended = llAppend(list1, list2); printf("%s", llNth(appended, 1)); // element 2 printf("%s", llNth(appended, 3)); // element 4 printf("%s", llNth(appended, 5)); // element 6 return 0; }
Node *llAppend(Node *list1, Node *list2) { Node *last = list1; while (last->next != NULL) { last = last->next; } last->next = list2; return list1; }
While this comes after the section of the course on Lisp, I hope that this helps you understand the
Lisp functions a little better. Here we’ve implemented cons
, list
, car
, cdr
, nth
and append
, all
important functions to know for Lisp programming.
Note that the llAppend
function differs from append
in Lisp because in Lisp it will return a new
list, whereas the one we’ve implemented here will mutate the first list passed in. As such, this is
more similar to the nconc
function rather than append
, but we didn’t cover nconc
in class.