Bigger Example 3: Dynamic Array
Question: Implement a dynamic array data structure in C that supports the following operations:
Initialization with an initial capacity.
Accessing an element at a given index.
Setting the value at a given index.
Inserting a new element at a specific index (with automatic capacity adjustment if necessary).
Removing an element at a specific index (with automatic capacity adjustment if necessary).
Your implementation should dynamically resize the array to accommodate additional elements and efficiently manage memory.
#include <stdio.h>
#include <stdlib.h>
#define INITIAL_CAPACITY 10
#define GROWTH_FACTOR 2
typedef struct {
int *array;
size_t size;
size_t capacity;
} DynamicArray;
// Function prototypes
DynamicArray* initDynamicArray(size_t initialCapacity);
int get(DynamicArray *arr, size_t index);
void set(DynamicArray *arr, size_t index, int value);
void insert(DynamicArray *arr, size_t index, int value);
void removeAt(DynamicArray *arr, size_t index);
void resizeIfNeeded(DynamicArray *arr);
// Function to initialize a dynamic array with a given initial capacity
DynamicArray* initDynamicArray(size_t initialCapacity) {
DynamicArray *arr = (DynamicArray *)malloc(sizeof(DynamicArray));
arr->array = (int *)malloc(initialCapacity * sizeof(int));
arr->size = 0;
arr->capacity = initialCapacity;
return arr;
}
// Function to get the value at a specific index
int get(DynamicArray *arr, size_t index) {
if (index < arr->size) {
return arr->array[index];
} else {
// Handle index out of bounds
fprintf(stderr, "Error: Index out of bounds\n");
exit(EXIT_FAILURE);
}
}
// Function to set the value at a specific index
void set(DynamicArray *arr, size_t index, int value) {
if (index < arr->size) {
arr->array[index] = value;
} else {
// Handle index out of bounds
fprintf(stderr, "Error: Index out of bounds\n");
exit(EXIT_FAILURE);
}
}
// Function to insert a value at a specific index
void insert(DynamicArray *arr, size_t index, int value) {
if (index > arr->size) {
// Handle index out of bounds
fprintf(stderr, "Error: Index out of bounds\n");
exit(EXIT_FAILURE);
}
resizeIfNeeded(arr);
// Shift elements to make space for the new element
for (size_t i = arr->size; i > index; --i) {
arr->array[i] = arr->array[i - 1];
}
// Insert the new element
arr->array[index] = value;
arr->size++;
}
// Function to remove an element at a specific index
void removeAt(DynamicArray *arr, size_t index) {
if (index >= arr->size) {
// Handle index out of bounds
fprintf(stderr, "Error: Index out of bounds\n");
exit(EXIT_FAILURE);
}
// Shift elements to fill the gap left by the removed element
for (size_t i = index; i < arr->size - 1; ++i) {
arr->array[i] = arr->array[i + 1];
}
arr->size--;
// Resize the array if the size becomes significantly smaller than the capacity
resizeIfNeeded(arr);
}
// Function to resize the array if needed
void resizeIfNeeded(DynamicArray *arr) {
if (arr->size >= arr->capacity) {
arr->capacity *= GROWTH_FACTOR;
arr->array = (int *)realloc(arr->array, arr->capacity * sizeof(int));
if (arr->array == NULL) {
// Handle memory allocation failure
fprintf(stderr, "Error: Memory allocation failed\n");
exit(EXIT_FAILURE);
}
} else if (arr->size <= arr->capacity / (2 * GROWTH_FACTOR)) {
// Reduce the capacity if the size becomes significantly smaller than the capacity
arr->capacity /= GROWTH_FACTOR;
arr->array = (int *)realloc(arr->array, arr->capacity * sizeof(int));
if (arr->array == NULL) {
// Handle memory allocation failure
fprintf(stderr, "Error: Memory allocation failed\n");
exit(EXIT_FAILURE);
}
}
}
// Main function for testing
int main() {
// Example usage
DynamicArray *arr = initDynamicArray(INITIAL_CAPACITY);
for (int i = 0; i < 15; ++i) {
insert(arr, i, i * 2);
}
for (int i = 0; i < arr->size; ++i) {
printf("%d ", get(arr, i));
}
printf("\n");
set(arr, 2, 99);
for (int i = 0; i < arr->size; ++i) {
printf("%d ", get(arr, i));
}
printf("\n");
removeAt(arr, 3);
for (int i = 0; i < arr->size; ++i) {
printf("%d ", get(arr, i));
}
printf("\n");
// Free allocated memory
free(arr->array);
free(arr);
return 0;
}
Let's examine the code in detail:
1. Structure Definition:
typedef struct {
int *array;
size_t size;
size_t capacity;
} DynamicArray;
DynamicArray
is a structure representing the dynamic array.array
is a pointer to an array of integers, where the actual data is stored.size
is the current number of elements in the array.capacity
is the maximum number of elements that the current array can hold.
2. Function Prototypes:
DynamicArray* initDynamicArray(size_t initialCapacity);
int get(DynamicArray *arr, size_t index);
void set(DynamicArray *arr, size_t index, int value);
void insert(DynamicArray *arr, size_t index, int value);
void removeAt(DynamicArray *arr, size_t index);
void resizeIfNeeded(DynamicArray *arr);
initDynamicArray
: Allocates memory for aDynamicArray
structure and initializes its fields.get
: Retrieves the value at a specified index in the array.set
: Sets the value at a specified index in the array.insert
: Inserts a new element at a specified index, shifting existing elements.removeAt
: Removes an element at a specified index, shifting elements to fill the gap.resizeIfNeeded
: Checks if resizing is necessary based on the current size and capacity of the array.
3. initDynamicArray
Function:
DynamicArray* initDynamicArray(size_t initialCapacity) {
DynamicArray *arr = (DynamicArray *)malloc(sizeof(DynamicArray));
arr->array = (int *)malloc(initialCapacity * sizeof(int));
arr->size = 0;
arr->capacity = initialCapacity;
return arr;
}
Allocates memory for a
DynamicArray
structure and its internal array.Sets the initial size to 0 and the capacity to the specified initial capacity.
Returns a pointer to the created
DynamicArray
.
4. get
and set
Functions:
int get(DynamicArray *arr, size_t index) {
// ...
}
void set(DynamicArray *arr, size_t index, int value) {
// ...
}
get
: Returns the value at the specified index if it's within bounds, otherwise, prints an error message and exits.set
: Sets the value at the specified index if it's within bounds, otherwise, prints an error message and exits.
5. insert
Function:
void insert(DynamicArray *arr, size_t index, int value) {
// ...
}
Checks if the index is out of bounds and handles the error.
Calls
resizeIfNeeded
to ensure there's enough space for the new element.Shifts elements to the right to make space for the new element.
Inserts the new element at the specified index and increments the size.
6. removeAt
Function:
void removeAt(DynamicArray *arr, size_t index) {
// ...
}
Checks if the index is out of bounds and handles the error.
Shifts elements to the left to fill the gap left by the removed element.
Decrements the size.
Calls
resizeIfNeeded
to adjust the capacity if necessary.
7. resizeIfNeeded
Function:
void resizeIfNeeded(DynamicArray *arr) {
// ...
}
Checks if the size exceeds the capacity. If true, it doubles the capacity.
Checks if the size is significantly smaller than the capacity. If true, it halves the capacity.
Uses
realloc
to resize the internal array.Handles errors if memory allocation fails.
8. main
Function:
int main() {
// ...
}
Example usage of the dynamic array functions to insert, set, and remove elements.
Prints the array at different stages.
Frees the allocated memory.
9. Memory Deallocation:
free(arr->array);
free(arr);
- Frees the memory allocated for the internal array and the
DynamicArray
structure.
In the provided C code, the growth factor is defined as 2
in the line:
#define GROWTH_FACTOR 2
The dynamic array is resized using the growth factor. The array's capacity is doubled by the growth factor whenever its current size exceeds its capacity, indicating that inserting a new element would surpass the current size. Alternatively, the array can be enlarged by dividing its capacity by the growth factor. This is done when the size becomes significantly smaller than the capacity, which can occur during removal operations. If additional elements need to be added to the array, its size will be doubled due to the growth factor of 2 in this specific implementation. Memory consumption and the frequency of resizing operations are two factors to consider when choosing an appropriate growth factor. A growth factor of 2 is often used because it strikes a reasonable balance between these two factors.