mindex.c

In-memory ordered store and fetch library
git clone git://git.finwo.net/lib/mindex.c
Log | Files | Refs | README | LICENSE

commit aa8f2b06aabaa472ea1a87df14ce2a06df335947
parent a96cd70139497ca4ee7603e6bb006a7619a7b5d7
Author: Yersa Nordman <yersa@finwo.nl>
Date:   Thu,  2 Mar 2023 22:24:36 +0100

Initial implementation & basic testing

Diffstat:
MMakefile | 3+++
Msrc/mindex.c | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/mindex.h | 5++++-
Mtest.c | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 217 insertions(+), 8 deletions(-)

diff --git a/Makefile b/Makefile @@ -2,6 +2,9 @@ SRC:= SRC+=$(wildcard src/*.c) SRC+=test.c +INCLUDES?= +INCLUDES+=-I src + override CFLAGS?=-Wall -g -O2 include lib/.dep/config.mk diff --git a/src/mindex.c b/src/mindex.c @@ -1,33 +1,175 @@ +#define _GNU_SOURCE + +#include <stdlib.h> +#include <string.h> + #include "mindex.h" -struct mindex_t * mindex_init(int (*compare)(const void *a, const void *b, void *udata), void *udata) { +#ifndef MINDEX_SPARE +#define MINDEX_SPARE 16 +#endif + +struct mindex_t * mindex_init(int (*compare)(const void *a, const void *b, void *udata), void (*purge)(const void *item, void *udata), void *udata) { struct mindex_t *mindex = malloc(sizeof(struct mindex_t)); mindex->compare = compare; - mindex->items = NULL; + mindex->purge = purge; + mindex->udata = udata; + mindex->items = malloc(MINDEX_SPARE * sizeof(void*)); mindex->length = 0; - return NULL; + mindex->spare = MINDEX_SPARE; + return mindex; } void mindex_set(struct mindex_t *mindex, void *item) { + // Check if the item is already in the list + void *found = mindex_get(mindex, item); + + // The exact pointer already there = done + if (found && (item == found)) { + return; + } + + // Different pointer, so remove from the list + // Indicate to lib user that it needs to be purged from mem + if (found) { + mindex_delete(mindex, found); + mindex->purge(found, mindex->udata); + } + + // Allocate more space if we ran out + if (!mindex->spare) { + mindex->items = realloc(mindex->items, (mindex->length + MINDEX_SPARE) * sizeof(void*)); + mindex->spare = MINDEX_SPARE; + } + + // Add the new item to the end of the list + mindex->items[mindex->length] = item; + mindex->length++; + mindex->spare--; + + // Sort the list + qsort_r(mindex->items, mindex->length, sizeof(void *), mindex->compare, mindex->udata); + + // Done +} + +// Returns the INDEX within the items, not the item itself +int mindex_get_internal(void **items, size_t length, void *pattern, int (*compare)(const void *a, const void *b, void *udata), void *udata) { + int sub; + + // No entries = not found + if (length < 1) { + return -1; + } + + // Test the middle of the array + size_t testIdx = length / 2; + int compare_result = compare(items[testIdx], pattern, udata); + + // Match = found + if (compare_result == 0) { + return testIdx; + } + + // Pattern = to the right + if (compare_result < 0) { + sub = mindex_get_internal( + &(items[testIdx+1]), + length - testIdx - 1, + pattern, + compare, + udata + ); + + // Pass along error code + if (sub < 0) { + return sub; + } + + // Return mapped location + return sub + testIdx + 1; + } + + // Pattern to the left + if (compare_result > 0) { + return mindex_get_internal( + items, + testIdx, + pattern, + compare, + udata + ); + } + + // Should never be called + return -2; } void * mindex_get(struct mindex_t *mindex, void *pattern) { - return NULL; + int idx = mindex_get_internal(mindex->items, mindex->length, pattern, mindex->compare, mindex->udata); + + // Not found + if (idx < 0) { + return NULL; + } + + return mindex->items[idx]; } void * mindex_rand(struct mindex_t *mindex) { - return NULL; + if (!mindex->length) { + return NULL; + } + return mindex->items[rand() % mindex->length]; } -void mindex_delete(struct mindex_t *mindex, void *item) { +// TODO: mindex_delete_internal(..., items, idx) + +void mindex_delete(struct mindex_t *mindex, void *pattern) { + int idx = mindex_get_internal(mindex->items, mindex->length, pattern, mindex->compare, mindex->udata); + void *item = mindex->items[idx]; + // Purge if not an exact match + if (item != pattern) { + mindex->purge(item, mindex->udata); + } + + // Move everything on it's right to it + void *dst = &(mindex->items[idx]); + void *src = &(mindex->items[idx+1]); + memmove(dst, src, mindex->length - idx - 1); + + // Update our size trackers + mindex->length--; + mindex->spare++; + + // Free some memory if we have a lot of spares + if (mindex->spare > (MINDEX_SPARE*2)) { + mindex->items = realloc(mindex->items, (mindex->length + MINDEX_SPARE) * sizeof(void*)); + mindex->spare = MINDEX_SPARE; + } + + // Done } size_t mindex_length(struct mindex_t *mindex) { - return 0; + return mindex->length; } void mindex_free(struct mindex_t *mindex) { + // Step 1, purge all entries + int i; + for (i=0; i < mindex->length; i++) { + mindex->purge(mindex->items[i], mindex->udata); + } + + // Step 2, free the list + free(mindex->items); + + // Step 3, free the mindex itself + free(mindex); + + // Done } diff --git a/src/mindex.h b/src/mindex.h @@ -5,11 +5,14 @@ struct mindex_t { int (*compare)(const void *a, const void *b, void *udata); + void (*purge)(const void *item, void *udata); + void *udata; size_t length; + size_t spare; void **items; }; -struct mindex_t * mindex_init(int (*compare)(const void *a, const void *b, void *udata), void *udata); +struct mindex_t * mindex_init(int (*compare)(const void *a, const void *b, void *udata), void (*purge)(const void *item, void *udata), void *udata); void mindex_set(struct mindex_t *mindex, void *item); void * mindex_get(struct mindex_t *mindex, void *pattern); void * mindex_rand(struct mindex_t *mindex); diff --git a/test.c b/test.c @@ -1,5 +1,66 @@ +#include <stdlib.h> +#include <string.h> + #include "finwo/assert.h" +#include "mindex.h" + +static void fn_purge(const void *item, void *udata) { + // Intentionally empty +} + +static int fn_compare(const void *a, const void *b, void *udata) { + const char *sa = a; + const char *sb = b; + return strcmp(sa, sb); +} + +char *test_a00 = "Hello World"; +char *test_a01 = "Hello World"; +char *test_a02 = "Foobar"; + +void test_mindex_basics() { + struct mindex_t *mindex = mindex_init(fn_compare, fn_purge, NULL); + + ASSERT("mindex_init returns non-null", mindex != NULL); + + mindex_set(mindex, test_a01); + mindex_set(mindex, test_a00); + mindex_set(mindex, test_a02); + + ASSERT("length = 2 after inserting 3 strings with 1 duplicate", mindex_length(mindex) == 2); + + char *found = mindex_get(mindex, test_a00); + + ASSERT("Fetching by original = get original", found == test_a00); + + found = mindex_get(mindex, test_a01); + + ASSERT("Fetching by same content = get original", found == test_a00); + + found = mindex_get(mindex, test_a02); + + ASSERT("Fetching by other = get other", found == test_a02); + + mindex_set(mindex, test_a01); + found = mindex_get(mindex, test_a00); + + ASSERT("Overridden get by org returns new", found == test_a01); + + found = mindex_get(mindex, test_a01); + + ASSERT("Overridden get by new returns new", found == test_a01); +} int main() { + + // Seed random + unsigned int seed; + FILE* urandom = fopen("/dev/urandom", "r"); + fread(&seed, sizeof(int), 1, urandom); + fclose(urandom); + srand(seed); + + // Run the actual tests + RUN(test_mindex_basics); return TEST_REPORT(); }