1
0
Fork 0
C_lib/vector/vector.c

391 lines
13 KiB
C

/*
* C_lib Copyright (C) 2021 Wael Karram.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/* This file contains the function implementations for a vector. */
/* Include guard + includes. */
#ifndef VECTOR_C
#define VECTOR_C
#include "vector.h"
#endif /* VECTOR_C */
/* Function implementations. */
/* This function initializes a new empty vector, returns NULL on failure. */
static inline vector_t* initialize_vector() {
/* Local variables. */
vector_t *vector;
/* Attempt to allocate. */
vector = (vector_t*) malloc(sizeof(vector_t));
/* Check the allocation. */
if (vector == NULL)
return NULL;
/* Set the initial values. */
vector->power = INITIAL_POWER_VECTOR_C;
vector->length = powl(GROWTH_CONSTANT_VECTOR_C, INITIAL_POWER_VECTOR_C);
vector->element_size = sizeof(void*); /* For better portability. */
vector->last_element = (size_t) LAST_ELEMENT_EMPTY_INDEX_VECTOR_C;
vector->empty = (char) TRUE;
/* Attempt to allocate the array. */
vector->array = (void_ptr*) malloc(vector->length * sizeof(void*));
/* Check the allocation. */
if (vector->array == NULL) {
free(vector);
return NULL;
}
/* Return the pointer. */
return vector;
}
/* This function appends a value to a vector, user should verify that size of data is compatible! */
/* Note that if a failure code is returned, then the original vector wasn't changed whatsoever. */
/* Returns SUCCESS_CODE_VECTOR_C on success and FAILURE_CODE_VECTOR_C on failure. */
/* Undefined behavior for NULL vectors. */
static inline int append_to_vector(vector_t* vector, void* const data) {
/* Sanity check. */
assert(vector);
/* Check if the vector has enough space. */
if (check_vector_grow(vector, THRESHOLD_GROW_VECTOR_C)) {
/* Grow the vector. */
if (!grow_vector(vector))
return FAILURE_CODE_VECTOR_C;
}
/* Add the value to the end. */
*(vector->array + (((vector->empty) ? 0 : vector->last_element) * vector->element_size)) = data;
/* Set the vector to be non-empty and set the index correctly if needed. */
if (vector->empty) {
vector->empty = (char) FALSE;
vector->last_element = 0L;
} else {
/* Increment the internal index. */
vector->last_element = vector->last_element + 1L;
}
/* Return success. */
return SUCCESS_CODE_VECTOR_C;
}
/* This function prepends a value to a vector, user should verify that size of data is compatible! */
/* Note that if a failure code is returned, then the original vector wasn't changed whatsoever. */
/* Returns SUCCESS_CODE_VECTOR_C on success and FAILURE_CODE_VECTOR_C on failure. */
/* Undefined behavior for NULL vectors. */
static inline int prepend_to_vector(vector_t* const vector, void* const data) {
/* Sanity check. */
assert(vector);
/* Check if the vector has enough space. */
if (check_vector_grow(vector, THRESHOLD_GROW_VECTOR_C)) {
/* Grow the vector. */
if (!grow_vector(vector))
return FAILURE_CODE_VECTOR_C;
}
/* Check if the vector is empty. */
if (vector->empty)
return append_to_vector(vector, data);
/* Push the vector by 1. */
push_vector(vector, 1L);
/* Prepend. */
*(vector->array) = data;
/* Increment the internal index. */
vector->last_element = vector->last_element + 1L;
/* Return success. */
return SUCCESS_CODE_VECTOR_C;
}
/* This function reads from the vector at the given index. */
/* Returns NULL on error. */
/* NULL can also be a perfectly fine value to store and retrieve. */
/* Undefined behavior for NULL vectors. */
static inline void* read_from_vector(const vector_t* const vector, const size_t index) {
/* Sanity check. */
assert(vector && vector->last_element >= index && !(vector->empty));
/* Read and return the pointer. */
return *(vector->array + (index * vector->element_size));
}
/* This function reads from the vector at the given index. */
/* Returns NULL on error and sets error to the error code. */
/* NULL can also be a perfectly fine value to store and retrieve. */
/* Undefined behavior for NULL vectors. */
/* Disallows writes beyond last_index + 1. */
static inline int write_to_vector(vector_t* const vector, const size_t index, void* const data) {
/* Sanity check. */
assert(vector && vector->length > index && (vector->last_element + 1L) > index);
/* Check if the vector is empty. */
if (vector->empty)
return append_to_vector(vector, data);
/* Check if the write is over-writing or not. */
/* Check for an append call. */
if (index == (vector->last_element + 1L))
return append_to_vector(vector, data);
/* Overwriting call! */
*(vector->array + (index * vector->element_size)) = data;
vector->last_element = vector->last_element + 1L;
/* Check if the vector needs to be grown. */
if (check_vector_grow(vector, THRESHOLD_GROW_VECTOR_C))
return (grow_vector(vector)) ? SUCCESS_CODE_VECTOR_C : WRITE_SUCCESS_VECTOR_GROW_FAIL_VECTOR_C;
/* Done. */
return SUCCESS_CODE_VECTOR_C;
}
/* This function deletes from the vector at the given index. */
/* Undefined behavior for NULL vectors. */
/* Disallows deletes out of bounds. */
/* free_function mustn't be NULL. */
/* Silently fails when index is out of bounds. */
static inline void delete_from_vector(vector_t* const vector, const size_t index, void (*free_function)(const void*)) {
/* Sanity checks. */
assert(vector && (vector->array) && !((vector->length <= index) || ((vector->last_element + 1L) < index)));
/* Check if the vector is empty. */
if (vector->empty)
return;
/* Local variables. */
void_ptr pointer = vector->array;
size_t i, j;
/* Free the internal data. */
if (free_function)
free_function((vector->array + (index * vector->element_size)));
else
free((vector->array + (index * vector->element_size)));
*(vector->array + (index * vector->element_size)) = NULL;
/* Compact the vector manually and return the result accordingly. */
/* This can be done in O(n-k) instead of compact_vector's O(n^2). */
for (j = index; j < vector->length - 1; j++) {
/* Current pointer is NULL, swap with the next one. */
swap_void_ptr(pointer + (j * vector->element_size), pointer + (j *(vector->element_size + 1)), vector->element_size);
}
/* Move back the last element by 1. */
vector->last_element -= 1L;
/* Check if the vector should be shrunk. */
if (check_vector_shrink(vector, THRESHOLD_SHRINK_VECTOR_C))
shrink_vector(vector);
}
/* This function serializes a vector into a copy array. */
/* Returns NULL on allocation failure. */
/* Will exhibit undefined behavior if the vector is NULL or is internally undefined. */
/* Caller should free the allocated buffer! */
/* Note that calling this function on an empty vector might result in Undefined Behavior. */
static inline void_ptr* serialize_vector(const vector_t* const vector) {
/* Sanity check. */
assert(vector);
/* Attempt to allocate the new buffer. */
void_ptr *buffer = (void_ptr*) malloc(vector->length * vector->element_size);
/* Check the allocation. */
if (buffer == NULL)
return NULL;
/* Copy. */
return memcpy(buffer, vector->array, vector->length * vector->element_size);
}
/* This function initializes a new vector and copies the source vector into it (including data). */
/* Returns NULL on allocation failure. */
/* Will exhibit undefined behavior if the vector is NULL or is internally undefined. */
/* Caller should free the allocated data! */
static inline int copy_vector(vector_t *restrict source, vector_t **restrict destination) {
/* Sanity check. */
assert(source && source->array && destination);
/* Attempt to intialize an empty vector struct. */
*destination = initialize_vector();
if (*destination == NULL)
return ALLOC_FAILURE_VECTOR_C;
/* Set the fields. */
(*destination)->power = source->power;
(*destination)->length = source->length;
(*destination)->element_size = source->element_size;
(*destination)->last_element = source->last_element;
(*destination)->empty = source->empty;
(*destination)->array = serialize_vector(source);
/* Check if the copy failed. */
if ((*destination)->array == NULL) {
/* Clean-up. */
free(*destination);
*destination = NULL;
return ALLOC_FAILURE_VECTOR_C;
}
/* Done. */
return SUCCESS_CODE_VECTOR_C;
}
/* This function frees a vector, free_function is used to free the data stored within. */
/* Undefined behavior on NULL vectors and malformed vectors. */
/* If free_function is NULL, then the data doesn't get freed. */
static inline void free_vector(vector_t *vector, void (*free_function)(const void*)) {
/* Local variables. */
size_t index;
/* Sanity check. */
assert(vector);
/* Check if to free the data. */
if (free_function != NULL)
for (index = 0L; index <= vector->last_element; index++)
free_function(*(vector->array + (index * vector->element_size)));
/* Free the array. */
free(vector->array);
/* Free the vector. */
free(vector);
}
/* Internal functions. */
/* This function checks if the vector needs to be grown or kept as is. */
static inline int check_vector_grow(const vector_t* const vector, const double threshold) {
/* Sanity check. */
assert(vector);
/* Calculate the threshold and length. */
size_t current_size = vector->last_element;
size_t current_threshold = (size_t)((vector->length) * threshold);
/* Check if it needs to be grown. */
return (current_size >= current_threshold) ? CODE_GROW_VECTOR_C : CODE_KEEP_VECTOR_C;
}
/* This function checks if the vector needs to be shrunk or kept as is. */
static inline int check_vector_shrink(const vector_t* const vector, const double threshold) {
/* Sanity check. */
assert(vector);
/* Calculate the threshold and length. */
size_t current_size = vector->last_element;
size_t current_threshold = (size_t)((vector->length) * (THRESHOLD_WHOLE_VECTOR_C - threshold));
/* Check if it needs to be shrunk. */
return (current_size <= current_threshold) ? CODE_SHRINK_VECTOR_C : CODE_KEEP_VECTOR_C;
}
/* This function grows a vector, doesn't change it on failure. */
/* Returns SUCCESS_CODE_VECTOR_C on success and FAILURE_CODE_VECTOR_C on failure. */
static inline int grow_vector(vector_t* const vector) {
/* Sanity check. */
assert(vector && (vector->array));
/* Local variables. */
size_t size;
void* array;
/* Calculate the new size. */
size = ((vector->length * vector->element_size) * GROWTH_CONSTANT_VECTOR_C);
/* Attempt to reallocate, and return the result. */
array = realloc(vector->array, size);
/* Check if the allocation succeeded. */
if (array != NULL) {
/* Put the new pointer in. */
vector->array = array;
/* Update the length. */
vector->length = size;
/* Return success. */
return SUCCESS_CODE_VECTOR_C;
}
/* Allocation failed. */
return FAILURE_CODE_VECTOR_C;
}
/* This function shrinks a vector, doesn't change it on failure. */
/* Note that if the vector cannot be shrunk according to the growth factor, data loss may occure. */
/* Returns SUCCESS_CODE_VECTOR_C on success and FAILURE_CODE_VECTOR_C on failure. */
static inline int shrink_vector(vector_t* const vector) {
/* Sanity check. */
assert(vector);
/* Local variables. */
size_t size;
void* array;
/* Calculate the new size. */
size = ((vector->length * vector->element_size) / GROWTH_CONSTANT_VECTOR_C);
/* Attempt to reallocate, and return the result. */
array = realloc(vector->array, size);
/* Check if the allocation succeeded. */
if (array != NULL) {
/* Put the new pointer in. */
vector->array = array;
/* Update the length. */
vector->length = size;
/* Return success. */
return SUCCESS_CODE_VECTOR_C;
}
/* Allocation failed. */
return FAILURE_CODE_VECTOR_C;
}
/* This function compacts a vector by pushing all the NULLs to the end. */
/* Try to avoid calling it unless needed as it has an O(n^2) runtime complexity. */
static inline void compact_vector(vector_t* const vector) {
/* Sanity check. */
assert(vector && (vector->array));
/* Loop and push. */
void_ptr *pointer = vector->array;
size_t i, j;
/* This loop handles compacting the vector in O(n^2), sort of like bubble sort for NULL values. */
for (i = 0; i < vector->length; i++)
for (j = 0; j < vector->length - 1; j++)
if (*(pointer + (j * vector->element_size)) == (void_ptr) NULL) {
/* Current pointer is NULL, swap with the next one. */
swap_void_ptr(pointer + (j * vector->element_size), pointer + (j *(vector->element_size + 1)), vector->element_size);
}
}
/* This function creates a "gap" (of NULL) at the start of the vector, doesn't do any sanity checking! */
static inline void push_vector(const vector_t* const vector, const size_t gap_length) {
/* Sanity check. */
assert(vector && (vector->array));
/* Local variables. */
size_t i;
/* Push from the end to the start. */
for (i = vector->last_element; i >= 0L; i--)
swap_void_ptr(vector->array + ((i + gap_length ) * vector->element_size), vector->array + (i * vector->element_size), vector->element_size);
/* Put NULL in the start of the vector. */
for (i = 0L; i < gap_length; i++)
*(vector->array + (i * vector->element_size)) = (void*) NULL;
}