I'm a C beginner. For a tiny personal project, I was in need of an "auto-growing" array and here it is the implementation. I'd like to get some feedback.
At the end there is a test function called in main
. Assertions are used only for debug-testing purposes. INITIAL_CAPACITY
is set to 0
to "stress test" the API.
resize_darray
and enlarge_darray
are not the same function because later I'd like to add a "shrink" function.
I have two concerns:
- Would it have been better if I used
size_t
forn
andcapacity
? I usedint
mainly to avoid the compiler warning while comparing the result ofsize_darray
with anint
in e.g. a for loop iterating over the items. - Is there a "better way" to shift items?
memmove
maybe?
There isn't an header file because I'd like to store this implementation in a single file (as snippet) for future reference. That's also why in resize_darray
I use a void
pointer: if I want to adapt the implementation to another type of data, I don't have to touch that function.
/*
* Dynamic array.
*
* from Wikipedia: "...is a random access, variable-size list data structure
* that allows elements to be added or removed... Dynamic arrays
* overcome a limit of static arrays, which have a fixed capacity that needs to
* be specified at allocation."
*
* Indexing O(1)
* Insert/delete at beginning O(n)
* Insert/delete at end O(1) amortized
* Insert/delete at middle O(n)
* Average space O(n)
*
* https://en.wikipedia.org/wiki/Dynamic_array
*/
#include <assert.h>
#include <time.h>
#include <stdlib.h>
#include <stdbool.h>
#define INITIAL_CAPACITY 0
typedef struct
{
int capacity;
int n;
int *items;
} darray;
/*
* resize_darray: changes array total capacity to new_capacity and returns
* true. On failure returns false and leaves array untouched.
*/
static bool resize_darray(darray *array, int new_capacity);
/*
* enlarge_darray: increases the total capacity of array by a factor of about
* 1.5 and returns true. On failure returns false and leaves
* array untouched.
*
* The formula used to calculate new capacity is:
* new_capacity = old_capacity + old_capacity / 2 + 1
*/
static bool enlarge_darray(darray *array);
/*
* create_darray: creates and returns (a pointer to) a new darray of capacity
* INITIAL_CAPACITY. On failure returns NULL.
*/
darray *create_darray(void);
/*
* size_darray: returns the number of items stored in array.
*/
int size_darray(const darray *array);
/*
* add_item_at: inserts item in array at position index shifting other items to
* the right by one position and returns true. On failure returns
* false.
* If index is not a valid index for inserting in array, the
* behavior is undefined.
*/
bool add_item_at(darray *array, int index, int item);
/*
* prepend_item: inserts item at position 0. It is equivalent to:
* add_item_at(array, 0, item);
*/
bool prepend_item(darray *array, int item);
/*
* append_item: inserts item at the end of array. It is equivalent to:
* add_item_at(array, size_darray(array), item);
*/
bool append_item(darray *array, int item);
/*
* get_item_at: returns (but does not remove) the item at position index.
* If index is not a valid index for array, the behavior is
* undefined.
*/
int get_item_at(const darray *array, int index);
/*
* remove_item_at: removes and returns the item at position index shifting
* other items to the left by one position.
* If index is not a valid index for array, the behavior is
* undefined.
*/
int remove_item_at(darray *array, int index);
/* replace_item_at: replaces the item at position index with item and returns
* the item previously at index.
* If index is not a valid index for array, the behavior is
* undefined.
*/
int replace_item_at(darray *array, int index, int item);
/*
* free_darray: frees memory occupied by array.
*/
void free_darray(darray *array);
static bool resize_darray(darray *array, int new_capacity)
{
void *new_ptr = realloc(array->items,
sizeof(*(array->items)) * new_capacity);
if (new_ptr != NULL) {
array->items = new_ptr;
array->capacity = new_capacity;
return true;
}
return false;
}
static bool enlarge_darray(darray *array)
{
return resize_darray(array, array->capacity + array->capacity / 2 + 1);
}
darray *create_darray(void)
{
darray *new_darray = malloc(sizeof(*new_darray));
if (new_darray == NULL) {
return NULL;
}
new_darray->capacity = 0;
new_darray->n = 0;
new_darray->items = NULL;
if ( ! resize_darray(new_darray, INITIAL_CAPACITY)) {
free_darray(new_darray);
return NULL;
}
return new_darray;
}
int size_darray(const darray *array)
{
return array->n;
}
bool add_item_at(darray *array, int index, int item)
{
assert(index >= 0 && index <= size_darray(array));
if (size_darray(array) == array->capacity && ! enlarge_darray(array)) {
return false;
}
array->n++;
for (int i = size_darray(array) - 1; i > index; i--) {
array->items[i] = array->items[i - 1];
}
array->items[index] = item;
return true;
}
bool prepend_item(darray *array, int item)
{
return add_item_at(array, 0, item);
}
bool append_item(darray *array, int item)
{
return add_item_at(array, size_darray(array), item);
}
int get_item_at(const darray *array, int index)
{
assert(index >= 0 && index < size_darray(array));
return array->items[index];
}
int remove_item_at(darray *array, int index)
{
assert(index >= 0 && index < size_darray(array));
int item = get_item_at(array, index);
for (int i = index + 1; i < size_darray(array); i++) {
array->items[i - 1] = array->items[i];
}
array->n--;
return item;
}
int replace_item_at(darray *array, int index, int item)
{
assert(index >= 0 && index < size_darray(array));
int old_item = get_item_at(array, index);
array->items[index] = item;
return old_item;
}
void free_darray(darray *array)
{
free(array->items);
free(array);
}
static void test_darray(darray *array, int test_size)
{
/* array must be empty for this test */
assert(size_darray(array) == 0);
for (int i = 0; i < test_size; i++) {
append_item(array, i);
}
assert(size_darray(array) == test_size);
for (int i = 0; i < test_size; i++) {
assert(get_item_at(array, i) == i);
}
for (int i = 0; i < test_size; i++) {
int rnd_i = rand() % test_size;
add_item_at(array, rnd_i, i);
assert(size_darray(array) == test_size + 1);
assert(replace_item_at(array, rnd_i, -1) == i);
assert(size_darray(array) == test_size + 1);
assert(remove_item_at(array, rnd_i) == -1);
}
assert(size_darray(array) == test_size);
for (int i = 0; i < test_size; i++) {
assert(remove_item_at(array, 0) == i);
}
assert(size_darray(array) == 0);
for (int i = 0; i < test_size; i++) {
prepend_item(array, i);
}
assert(size_darray(array) == test_size);
for (int i = test_size - 1; i >= 0; i--) {
assert(remove_item_at(array, 0) == i);
}
assert(size_darray(array) == 0);
}
int main(void)
{
srand(time(NULL));
darray *arr = create_darray();
test_darray(arr, 0);
test_darray(arr, 1);
test_darray(arr, 100);
test_darray(arr, 1000);
free_darray(arr);
}
void
trick" used inresize_darray
). I understand that I have to ask for the type size on creation, but then? How do I perform the shift of items? (memmove
?) How do I return a specific element? (pointer arithmetic?) Sorry for all these questions, I'm still a beginner, any suggestion will be appreciated =) \$\endgroup\$