# Week 2 - Dynamic Arrays ## Team >Members: > >-Sean Bos [532785] >-Boxing Jiang [521516] > > > Date: ## Activities Make sure to have the activities signed off regularly to ensure progress is tracked. Before you start, download the project from Blackboard and explore the code contained in it. ### Activity 1: Random access Rewrite the code by replacing **all** expressions in which pointers are dereferenced with equivalent ones that use the *array index operator* ("[..]"). ````c #include <stdio.h> #define COUNT (50) void fill_fibonacci(unsigned long long * storage, int count) { *storage = 0; *(storage + 1) = 1; for (int i = 2; i < count; i++) { *(storage + i) = *(storage + i - 1) + *(storage + i - 2); } } int main(void) { unsigned long long array[COUNT]; fill_fibonacci(array, COUNT); for (int i = 1; i < COUNT; i++) { printf("f(%d) = %llu\n", i, array[i]); } } ```` Rewritten: ```c #include <stdio.h> #define COUNT 50 void fill_fibonacci(unsigned long long * storage, int count) { storage[0] = 0; storage[1] = 1; for (int i = 2; i < count; i++) { storage[i] = storage[i - 1] + storage[i - 2]; } } int main(void) { unsigned long long array[COUNT]; fill_fibonacci(array, COUNT); for (int i = 1; i < COUNT; i++) { printf("f(%d) = %llu\n", i, array[i]); } } ``` ### Activity 2: Array definition Explain what each of the three fields of the `array_t` structure listed below is used for. Find out, for your system, what the maximum number of elements is that can be stored in the array. ```C typedef struct array { size_t capacity; size_t count; float *data; } array_t; ``` `Capacity` is the maximun amount of elements the array can hold. `Count` is the current amount of elements the array is holding. `Data` is the pointer for us to access the array values. From my system, the maxinum value of size_t is 18446744073709551615 bytes, which is about 18 quintillion, so the array can hold about 2.25 quintillion elements. ### Activity 3: Correct initialization Version 1 is incorrect because it is returning the pointer of a local variable, which can result in undefined behavior. Also, it is not allocating memory on the heap so it is not creating a dynamic array. To fix this, we can use the malloc function to allocate some memory to make a actual dynamic array just like in version 2. ```C array_t* array_init(array_t *p_array, size_t capacity) { // version 2 if (p_array != NULL) { p_array->data = malloc(sizeof(float[capacity])); p_array->count = 0; //If p_array->data is not NULL, then capacity is assigned to p_array->capacity, p_array->capacity = p_array->data != NULL ? capacity : 0; } return p_array; } ``` ### Activity 4: Cleaning up Record your answer here. Program 1 is correct. On program 2, the variable array didn't allocate any memory unlike program 1, it's freeing the first element of the automatic lifetime variable array which would result in undefined behavaior. ```c //program 1 int main(void) { array_t *array = (array_t *) malloc(sizeof(array_t)); array_init(array, 10); // .... do some work ... // finally, clean up array_destroy(array); free(array); } // program 2 int main(void) { array_t array; array_init(&array, 10); // .... do some work ... // finally, clean up array_destroy(&array); free(&array); } ``` ### Activity 5: Resizing an array Record your function definition here. The growth rate of dynamic array can be 1.5. ```c // enlarges the array geometrically so that it can contain at least min_capacity elements #define ARRAY_GROWTH_RATE 1.5 void array_reserve(array_t *p_array, size_t min_capacity) { size_t capacity = p_array->capacity; while (capacity < min_capacity) { capacity = (capacity + 1) * ARRAY_GROWTH_RATE; } if (capacity != p_array->capacity) { p_array->data = realloc(p_array->data, (sizeof(float) * capacity)); if (p_array != NULL){ // check if allocation worked p_array->capacity = capacity; } else{ p_array->capacity = 0; } } } ``` ### Activity 6: Appending values ```c void array_append(array_t * arr, float value){ //check if there's enough space and extend the space if not if (arr->capacity == arr->count){ array_reserve(arr, arr->count + 1); //we are just adding one new element } arr->data[arr->count] = value; arr->count++; } ``` ### Activity 7: Inserting values Record your function definition here. ```c void array_insert(array_t * arr, size_t index, float value) { if (index <= arr->capacity) //must be a valid index { array_append(arr, 0); //since we are inserting, we need extra slot for the old values for (size_t i = 0; i < (arr->count - index); i++){ arr->data[arr->count - i] = arr->data[(arr->count - i) - 1]; //make the 'next' element value to be the 'previous' element value } arr->data[index] = value; } } ``` ### Activity 8: Removing by index Record your function definition here. ```c void array_remove(array_t * arr, size_t index) { for (size_t i = index; i < arr->count - index; i++) { arr->data[i] = arr->data[i + 1]; } arr->count--; } ``` ### Activity 9: Constant time complexity Consant time complexity means the program takes the same time to run no matter what the input size is. ![](https://i.imgur.com/5hDuH1J.png) ### Activity 10: Worst-case time complexity | Operation | Worst-case time complexity | | --------------- | -------------------------- | | Insert | Add value to the first index O(n) | | Remove | Remove the first index value O(n) | | Lookup / access | Since it's random access, so O(1) | At both the worst-case of `Insert` and `Remove` operations, all elements of the array needs to be shifted, so it's O(n), it scales with the size of the array as it's shifting n amount of elements. ### Activity 11: Complexity of *append* The average time complexity should be O(1) because you are just inserting a new element. When you don't enouh enough array size and you need to allocate new size, you will just allocate a bigger capacity with realloc and the array doesn't need a new address. ### Activity 12: Storing grades in a dynamically growing array ```c // keep asking the user to enter a grade, stop when user enters 0 void input_grades(array_t * grades){ float userInput; int studentNumber = 1; printf("Enter student %d's grade: \n", studentNumber); scanf("%f", &userInput); array_append(grades, userInput); while (userInput != 0){ studentNumber++; printf("Enter student %d's grade: \n", studentNumber); scanf("%f", &userInput); array_append(grades, userInput); } } // computes and returns the average grade float average(const array_t * grades){ float gradeSum = 0; for (int i = 0; i < (grades->count - 1); i++){ gradeSum = gradeSum + grades->data[i]; } return (float) gradeSum / (grades->count - 1); } // counts and returns the number of grades that are at least the threshold int num_passed(const array_t * grades, float threshold){ int numPassedAmount = 0; for (int i = 0; i < (grades->count - 1); i++){ if(grades->data[i] >= threshold){ numPassedAmount++; } } return numPassedAmount; } int main() { array_t grades; array_init(&grades, 0); input_grades(&grades); float averageNum = average(&grades); int passed = num_passed(&grades, 5.5f); printf("Average grade: %.2f\n", averageNum); printf("%d out of %zu students passed\n", passed, grades.count - 1); array_destroy(&grades); } ``` ## Looking back ### What we've learnt Formulate at least one lesson learned. ### What were the surprises Fill in... ### What problems we've encountered Fill in... ### What was or still is unclear Fill in... ### How did the group perform? How was the collaboration? What were the reasons for hick-ups? What worked well? What can be improved next time? > Written with [StackEdit](https://stackedit.io/).