%! TeX program = lualatex \documentclass[a4paper]{article} % packages \usepackage{microtype} % Slightly tweak font spacing for aesthetics \usepackage[english]{babel} % Language hyphenation and typographical rules \usepackage[final, colorlinks = false, urlcolor = cyan]{hyperref} \usepackage{changepage} % adjust margins on the fly \usepackage{fontspec} \usepackage{minted} \usepackage{xcolor} \usepackage{pgfplots} \pgfplotsset{width=\textwidth,compat=1.9} \usepackage{caption} \newenvironment{code}{\captionsetup{type=listing, skip=0pt}}{} % \captionsetup{skip=0pt} % \setlength{\abovecaptionskip}{3pt} % \setlength{\belowcaptionskip}{5pt} \usepackage[yyyymmdd]{datetime} \renewcommand{\dateseparator}{--} \setmainfont{EB Garamond} \setmonofont[Scale=MatchLowercase]{Deja Vu Sans Mono} \usepackage{titlesec} % \titleformat{\section}{\LARGE\bfseries}{}{}{}[\titlerule] % \titleformat{\subsection}{\Large\bfseries}{}{0em}{} % \titlespacing{\subsection}{0em}{-0.7em}{0em} % % \titleformat{\subsubsection}{\large\bfseries}{}{0em}{$\bullet$ } % \titlespacing{\subsubsection}{1em}{-0.7em}{0em} % margins \addtolength{\hoffset}{-2.25cm} \addtolength{\textwidth}{4.5cm} \addtolength{\voffset}{-3.25cm} \addtolength{\textheight}{5cm} \setlength{\parskip}{0pt} \setlength{\parindent}{0in} % \setcounter{secnumdepth}{0} \begin{document} \hrule \medskip \begin{minipage}{0.295\textwidth} \raggedright \footnotesize Name: Andrew Hayes \\ E-mail: \href{mailto://a.hayes18@universityofgalway.ie}{\texttt{a.hayes18@universityofgalway.ie}} \hfill\\ ID: 21321503 \hfill \end{minipage} \begin{minipage}{0.4\textwidth} \centering \vspace{0.4em} \Large \textbf{CT331} \\ \end{minipage} \begin{minipage}{0.295\textwidth} \raggedleft \today \end{minipage} \medskip\hrule \begin{center} \normalsize Assignment 1: Procedural Programming with C \end{center} \hrule \section{Question 1} \subsection{Part (A): Code} \begin{code} \inputminted[texcl, mathescape, linenos, breaklines, frame=single]{C}{../code/question1/question1.c} \caption{\texttt{question1.c}} \end{code} \begin{figure}[H] \centering \includegraphics[width=0.8\textwidth]{./images/question1.png} \caption{Console Output of \texttt{question1.c}} \end{figure} \subsection{Part (B): Comments} The amount of memory allocated to variables of different types in C is determined at compile-time, and is dependent on the architecture of the machine for which it is being compiled and the compiler used. \begin{itemize} \item On my machine, using GCC, an \verb|int| is allocated 4 bytes. This is the usual amount allocated on both 32-bit and 64-bit systems (my machine being of the latter kind), although older 32-bit systems used 2 bytes for an \verb|int| (the same amount as for a \verb|short|). 4 bytes is used even on 64-bit machines to maintain backwards compatibility with older 32-bit architectures. \item An \verb|int*| (a pointer to a variable of type \verb|int|) is allocated 8 bytes on my machine. This is because that my machine has a 64-bit architecture, and therefore an address in memory is represented using 64 bits (8 bytes). If this were compiled for a 32-bit machine, the size of an pointer would be 4 bytes since addresses are 32-bit. \item A \verb|long| is allocated 8 bytes on my machine. This is because my machine is 64-bit and a \verb|long| is typically 8 bytes in length on such machines. On 32-bit machines, a \verb|long| is typically 4 bytes. \item The size of a pointer to a \verb|double| is the same as the size of any other pointer on the same machine; on 64-bit machines, pointers are 8 bytes, and on 32-bit machines, they are 4 bytes. The type of data to which a pointer points has no effect on the size of the pointer, as the pointer is just a memory address. \item A pointer to a \verb|char| pointer is the same size as any other pointer: 8 bytes on a 64-bit machine and 4 bytes on a 32-bit machine. Note: it might be more intuitive to refer to a ``character pointer pointer'' as a pointer to a string in certain situations, as strings are character arrays, and an array variable acts as a pointer to the first element in the array. \end{itemize} \section{Question 2} \begin{code} \begin{minted}[texcl, mathescape, linenos, breaklines, frame=single]{C} // returns the number of elements in the list int length(listElement* list); // push a new element onto the head of a list and update the list reference using side effects void push(listElement** list, char* data, size_t size); // pop an element from the head of a list and update the list reference using side effects listElement* pop(listElement** list); // enque a new element onto the head of the list and update the list reference using side effects void enqueue(listElement** list, char* data, size_t size); // dequeue an element from the tail of the list listElement* dequeue(listElement* list); \end{minted} \caption{My Additions to \texttt{linkedList.h}} \end{code} \begin{code} \begin{minted}[texcl, mathescape, linenos, breaklines, frame=single]{C} // returns the number of elements in the list int length(listElement* list) { int length = 0; listElement* current = list; // traversing the list and counting each element while(current != NULL){ length++; current = current->next; } return length; } // push a new element onto the head of a list and update the list reference using side effects void push(listElement** list, char* data, size_t size) { // create the new element listElement* newElement = createEl(data, size); // handle malloc errors if (newElement == NULL) { fprintf(stderr, "Memory allocation failed.\n"); exit(EXIT_FAILURE); } // make the the new element point to the current head of the list newElement->next = *list; // make the list reference to point to the new head element *list = newElement; } // pop an element from the head of a list and update the list reference using side effects // assuming that the desired return value here is the popped element, as is standard for POP operations listElement* pop(listElement** list) { // don't bother if list is non existent if (*list == NULL) { return NULL; } ; // getting reference to the element to be popped listElement* poppedElement = *list; // make the the second element the new head of the list -- this could be NULL, so the list would be NULL also *list = (*list)->next; // detach the popped element from the list poppedElement->next = NULL; return poppedElement; } // enque a new element onto the head of the list and update the list reference using side effects // essentially the same as push void enqueue(listElement** list, char* data, size_t size) { // create the new element listElement* newElement = createEl(data, size); // handle malloc errors if (newElement == NULL) { fprintf(stderr, "Memory allocation failed.\n"); exit(EXIT_FAILURE); } // make the the new element point to the current head of the list newElement->next = *list; // make the list reference to point to the new head element *list = newElement; } // dequeue an element from the tail of the list by removing the element from the list via side effects, and returning the removed item // assuming that we want to return the dequeued element rather than the list itself, as enqueue returns nothing and uses side effects, so dequeue should also use side effects listElement* dequeue(listElement* list) { // there are three cases that we must consider: a list with 0 elements, a list with 1 element, & a list with >=2 elements // don't bother if list is non existent if (list == NULL) { return NULL; } // if there is only one element in the list, i.e. the head element is also the tail element, just returning this element // this means that the listElement pointer that was passed to this function won't be updated // ideally, we would set it to NULL but we can't do that since `list` is a pointer that has been passed by value, so we can't update the pointer itself. we would need a pointer to a pointer to have been passed if (list->next == NULL) { return list; } // traversing the list to find the second-to-last element listElement* current = list; while (current->next->next != NULL) { current = current->next; } // get reference to the element to be dequeued listElement* dequeuedElement = current->next; // make the penultimate element the tail by removing reference to the old tail current->next = NULL; return list; } \end{minted} \caption{My Additions to \texttt{linkedList.c}} \end{code} \begin{code} \begin{minted}[texcl, mathescape, linenos, breaklines, frame=single]{C} // test length function printf("Testing length()\n"); int l_length = length(l); printf("The length of l is %d\n\n", l_length); // test push printf("Testing push()\n"); push(&l, "yet another test string", sizeof("yet another test string")); traverse(l); printf("\n\n"); // test pop printf("Testing pop()\n"); listElement* popped = pop(&l); traverse(l); printf("\n\n"); // Test delete after printf("Testing deleteAfter()\n"); deleteAfter(l); traverse(l); printf("\n"); // test enqueue printf("Testing enqueue()\n"); enqueue(&l, "enqueued test string", sizeof("enqueued test string")); traverse(l); printf("\n"); // test dequeue printf("Testing dequeue()\n"); dequeue(l); traverse(l); printf("\n"); printf("\nTests complete.\n"); \end{minted} \caption{My Additions to \texttt{tests.c}} \end{code} \begin{figure}[H] \centering \includegraphics[width=0.9\textwidth]{./images/question2.png} \caption{Console Output for Question 2} \end{figure} \section{Question 3} \begin{code} \inputminted[linenos, breaklines, frame=single]{C}{../code/question3/genericLinkedList.h} \caption{\texttt{genericLinkedList.h}} \end{code} \begin{code} \inputminted[linenos, breaklines, frame=single]{C}{../code/question3/genericLinkedList.c} \caption{\texttt{genericLinkedList.c}} \end{code} \begin{code} \inputminted[linenos, breaklines, frame=single]{C}{../code/question3/tests.c} \caption{\texttt{tests.c}} \end{code} \begin{figure}[H] \centering \includegraphics[width=0.9\textwidth]{./images/question3.png} \caption{Console Output for Question 3} \end{figure} \section{Question 4} \subsection{Part 1} Any algorithm for traversing a singly linked list in reverse will always first require traversing the list forwards, and will therefore be \emph{at least} somewhat less efficient than a forwards traversal. One of the simplest ways to traverse a linked list in reverse is to use a recursive function. \begin{code} \begin{minted}[linenos, breaklines, frame=single]{C} void reverse_traverse(listElement* current){ if (current == NULL) { return; } reverse_traverse(current->next); current->printFunction(current->data); } \end{minted} \caption{Recursive Function to Traverse a Singly Linked List in Reverse} \end{code} This is quite inefficient as it requires that the function call for each node persists on the stack until the last node is reached, using a lot of stack memory. Another approach would be to iteratively reverse the linked list, by making some kind of data structure, linked list or otherwise, that contains the data of the original linked list but in reverse, and then iterating over that forwards. This would likely be more efficient in terms of memory \& computation. \\\\ Because traversing a linked list in reverse always requires traversing it forwards first, any reverse algorithm will take at least twice as much memory \& computation as traversing it forwards, which is $O(n)$. It will also require that some way of storing the data in reverse in memory, either explicitly with a data, like in the iterative approach, or in the same manner as the recursive approach, wherein the data is stored in reverse by the nested structure of the function calls: as each function call returns, the call structure is iterated through in reverse. Therefore, we also have at least $O(n)$ memory usage, as we have to store some sort of reverse data structure. \subsection{Part 2} The simplest way in which the structure of a linked list could be changed to make backwards traversal less intensive is to change it from a singly linked list to a doubly linked list, i.e. instead of each node in the list containing a pointer to just the next node, make each node contain a pointer to both the next node \& the previous node. The backwards traversal of a doubly linked list is no more intensive than the forwards traversal of a linked list. The drawback of using a doubly linked list is that it requires slightly more memory per node than a singly linked list, as you're storing an additional pointer for every node. \end{document}