commit 26b5169bb24445b3bce88a7a8ff53322613374e6 Author: biglyderv Date: Mon Mar 24 14:01:41 2025 -0400 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f2a1c7c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/elem \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5b20b64 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +CC=gcc + +make: src/main.c + $(CC) -o elem src/map.c src/main.c \ No newline at end of file diff --git a/combos.txt b/combos.txt new file mode 100644 index 0000000..a71a4bd --- /dev/null +++ b/combos.txt @@ -0,0 +1,11 @@ +wind;air;air +dust;air;earth +heat;air;fire +cloud;air;water +land;earth;earth +lava;earth;fire +mud;earth;water +wildfire;fire;fire +smoke;fire;water +puddle;water;water +elements;air;earth;fire;water diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..2fe5817 --- /dev/null +++ b/src/main.c @@ -0,0 +1,113 @@ +#include "map.h" +#include +#include +#include +#include +#include + +#define MAX_FILE_SIZE 1024 * 16 +#define MAX_BUF_LENGTH 1024 +#define MAX_COMBO_LENGTH 1024 + +void load_elements(hashmap *m, char *table) { + FILE *fptr; + + fptr = fopen(table, "r"); + + char *str; + + while (1) { + str = calloc(MAX_FILE_SIZE, sizeof(char)); + if (!fgets(str, MAX_FILE_SIZE, fptr)) + break; + + char *combo = strstr(str, ";"); + combo[0] = '\0'; + combo++; + + hashmap_set(m, combo, strlen(combo) - 1, (uintptr_t)str); + } + + // todo: properly free this +} + +int fix_delim(char *command, char *needle) { + char *delim = strstr(command, needle); + + if (!delim) + return 0; + + delim[0] = ';'; + + return 1; +} + +int sort_comp(const void *a, const void *b) { + return strcmp(*(char **)a, *(char **)b); +} + +int main(int argc, char *argv[]) { + hashmap *elements = hashmap_create(); + hashmap *inv = hashmap_create(); + + char *command = calloc(MAX_BUF_LENGTH, sizeof(char)); + char *command_re = calloc(MAX_BUF_LENGTH, sizeof(char)); + char **sort_tmp = calloc(MAX_COMBO_LENGTH, sizeof(char **)); + + load_elements(elements, "combos.txt"); + + while (1) { + // todo: separate into functions + fgets(command, MAX_BUF_LENGTH - 1, stdin); + + while (1) { + int get_out = 0; + get_out |= fix_delim(command, "+"); + get_out |= fix_delim(command, ","); + if (!get_out) + break; + } + int cl = strlen(command); + + command[cl - 1] = '\0'; + + for (int i = 0; i < cl - 1; i++) { + command[i] = tolower(command[i]); + } + + sort_tmp[0] = command; + int combos = 0; + for (int i = 1; i < MAX_COMBO_LENGTH; i++) { + combos = i; + sort_tmp[i] = strstr(command, ";"); + if (sort_tmp[i] == 0) + break; + sort_tmp[i][0] = '\0'; + sort_tmp[i]++; + } + + qsort(sort_tmp, combos, sizeof(char *), sort_comp); + + char *command_re_tmp = command_re; + for (int i = 0; i < combos; i++) { + if (i != 0) { + command_re_tmp[0] = ';'; + command_re_tmp++; + } + char *last = sort_tmp[i]; + strcpy(command_re_tmp, last); + command_re_tmp += strlen(last); + } + + uintptr_t result; + hashmap_get(elements, command_re, strlen(command_re), &result); + + if (result == 0) { + printf("You didn't make anything.\n"); + continue; + } + printf("You made %s!\n", (char *)result); + } + + // free(command); +} \ No newline at end of file diff --git a/src/map.c b/src/map.c new file mode 100644 index 0000000..a93b2b4 --- /dev/null +++ b/src/map.c @@ -0,0 +1,316 @@ +// +// map.h +// +// Created by Mashpoe on 1/15/21. +// + +#include "map.h" +#include +#include + +#define HASHMAP_DEFAULT_CAPACITY 20 +#define HASHMAP_MAX_LOAD 0.75f +#define HASHMAP_RESIZE_FACTOR 2 + +hashmap *hashmap_create(void) { + hashmap *m = malloc(sizeof(hashmap)); + if (m == NULL) { + return NULL; + } + + m->capacity = HASHMAP_DEFAULT_CAPACITY; + m->count = 0; + + m->tombstone_count = 0; + + m->buckets = calloc(HASHMAP_DEFAULT_CAPACITY, sizeof(struct bucket)); + if (m->buckets == NULL) { + free(m); + return NULL; + } + + m->first = NULL; + + // this prevents branching in hashmap_set. + // m->first will be treated as the "next" pointer in an imaginary bucket. + // when the first item is added, m->first will be set to the correct address. + m->last = (struct bucket *)&m->first; + return m; +} + +void hashmap_free(hashmap *m) { + free(m->buckets); + free(m); +} + +// puts an old bucket into a resized hashmap +static struct bucket *resize_entry(hashmap *m, struct bucket *old_entry) { + uint32_t index = old_entry->hash % m->capacity; + for (;;) { + struct bucket *entry = &m->buckets[index]; + + if (entry->key == NULL) { + *entry = *old_entry; // copy data from old entry + return entry; + } + + index = (index + 1) % m->capacity; + } +} + +static int hashmap_resize(hashmap *m) { + int old_capacity = m->capacity; + struct bucket *old_buckets = m->buckets; + + m->capacity *= HASHMAP_RESIZE_FACTOR; + // initializes all bucket fields to null + m->buckets = calloc(m->capacity, sizeof(struct bucket)); + if (m->buckets == NULL) { + m->capacity = old_capacity; + m->buckets = old_buckets; + return -1; + } + + // same trick; avoids branching + m->last = (struct bucket *)&m->first; + + m->count -= m->tombstone_count; + m->tombstone_count = 0; + + // assumes that an empty map won't be resized + do { + // skip entry if it's a "tombstone" + struct bucket *current = m->last->next; + if (current->key == NULL) { + m->last->next = current->next; + // skip to loop condition + continue; + } + + m->last->next = resize_entry(m, m->last->next); + m->last = m->last->next; + } while (m->last->next != NULL); + + free(old_buckets); + return 0; +} + +#define HASHMAP_HASH_INIT 2166136261u + +// FNV-1a hash function +static inline uint32_t hash_data(const unsigned char *data, size_t size) { + size_t nblocks = size / 8; + uint64_t hash = HASHMAP_HASH_INIT; + for (size_t i = 0; i < nblocks; ++i) { + hash ^= (uint64_t)data[0] << 0 | (uint64_t)data[1] << 8 | + (uint64_t)data[2] << 16 | (uint64_t)data[3] << 24 | + (uint64_t)data[4] << 32 | (uint64_t)data[5] << 40 | + (uint64_t)data[6] << 48 | (uint64_t)data[7] << 56; + hash *= 0xbf58476d1ce4e5b9; + data += 8; + } + + uint64_t last = size & 0xff; + switch (size % 8) { + case 7: + last |= (uint64_t)data[6] << 56; /* fallthrough */ + case 6: + last |= (uint64_t)data[5] << 48; /* fallthrough */ + case 5: + last |= (uint64_t)data[4] << 40; /* fallthrough */ + case 4: + last |= (uint64_t)data[3] << 32; /* fallthrough */ + case 3: + last |= (uint64_t)data[2] << 24; /* fallthrough */ + case 2: + last |= (uint64_t)data[1] << 16; /* fallthrough */ + case 1: + last |= (uint64_t)data[0] << 8; + hash ^= last; + hash *= 0xd6e8feb86659fd93; + } + + // compress to a 32-bit result. + // also serves as a finalizer. + return (uint32_t)(hash ^ hash >> 32); +} + +static struct bucket *find_entry(hashmap *m, const void *key, size_t ksize, + uint32_t hash) { + uint32_t index = hash % m->capacity; + + for (;;) { + struct bucket *entry = &m->buckets[index]; + + // compare sizes, then hashes, then key data as a last resort. + // check for tombstone + if ((entry->key == NULL && entry->value == 0) || + // check for valid matching entry + (entry->key != NULL && entry->ksize == ksize && entry->hash == hash && + memcmp(entry->key, key, ksize) == 0)) { + // return the entry if a match or an empty bucket is found + return entry; + } + + // kind of a thicc condition; + // I didn't want this to span multiple if statements or functions. + if (entry->key == NULL || + // compare sizes, then hashes, then key data as a last resort. + (entry->ksize == ksize && entry->hash == hash && + memcmp(entry->key, key, ksize) == 0)) { + // return the entry if a match or an empty bucket is found + return entry; + } + + // printf("collision\n"); + index = (index + 1) % m->capacity; + } +} + +int hashmap_set(hashmap *m, const void *key, size_t ksize, uintptr_t val) { + if (m->count + 1 > HASHMAP_MAX_LOAD * m->capacity) { + if (hashmap_resize(m) == -1) + return -1; + } + + uint32_t hash = hash_data(key, ksize); + struct bucket *entry = find_entry(m, key, ksize, hash); + if (entry->key == NULL) { + m->last->next = entry; + m->last = entry; + entry->next = NULL; + + ++m->count; + + entry->key = key; + entry->ksize = ksize; + entry->hash = hash; + } + entry->value = val; + return 0; +} + +int hashmap_get_set(hashmap *m, const void *key, size_t ksize, + uintptr_t *out_in) { + if (m->count + 1 > HASHMAP_MAX_LOAD * m->capacity) { + if (hashmap_resize(m) == -1) + return -1; + } + + uint32_t hash = hash_data(key, ksize); + struct bucket *entry = find_entry(m, key, ksize, hash); + if (entry->key == NULL) { + m->last->next = entry; + m->last = entry; + entry->next = NULL; + + ++m->count; + + entry->value = *out_in; + entry->key = key; + entry->ksize = ksize; + entry->hash = hash; + + return 0; + } + *out_in = entry->value; + return 1; +} + +int hashmap_set_free(hashmap *m, const void *key, size_t ksize, uintptr_t val, + hashmap_callback c, void *usr) { + if (m->count + 1 > HASHMAP_MAX_LOAD * m->capacity) { + if (hashmap_resize(m) == -1) + return -1; + } + + uint32_t hash = hash_data(key, ksize); + struct bucket *entry = find_entry(m, key, ksize, hash); + if (entry->key == NULL) { + m->last->next = entry; + m->last = entry; + entry->next = NULL; + + ++m->count; + + entry->key = key; + entry->ksize = ksize; + entry->hash = hash; + entry->value = val; + // there was no overwrite, exit the function. + return 0; + } + // allow the callback to free entry data. + // use old key and value so the callback can free them. + // the old key and value will be overwritten after this call. + int error = c(entry->key, ksize, entry->value, usr); + + // overwrite the old key pointer in case the callback frees it. + entry->key = key; + entry->value = val; + return error; +} + +int hashmap_get(hashmap *m, const void *key, size_t ksize, uintptr_t *out_val) { + uint32_t hash = hash_data(key, ksize); + struct bucket *entry = find_entry(m, key, ksize, hash); + + // if there is no match, output val will just be NULL + *out_val = entry->value; + + return entry->key != NULL ? 1 : 0; +} + +// doesn't "remove" the element per se, but it will be ignored. +// the element will eventually be removed when the map is resized. +void hashmap_remove(hashmap *m, const void *key, size_t ksize) { + uint32_t hash = hash_data(key, ksize); + struct bucket *entry = find_entry(m, key, ksize, hash); + + if (entry->key != NULL) { + + // "tombstone" entry is signified by a NULL key with a nonzero value + // element removal is optional because of the overhead of tombstone checks + entry->key = NULL; + entry->value = 0xDEAD; // I mean, it's a tombstone... + + ++m->tombstone_count; + } +} + +void hashmap_remove_free(hashmap *m, const void *key, size_t ksize, + hashmap_callback c, void *usr) { + uint32_t hash = hash_data(key, ksize); + struct bucket *entry = find_entry(m, key, ksize, hash); + + if (entry->key != NULL) { + c(entry->key, entry->ksize, entry->value, usr); + + // "tombstone" entry is signified by a NULL key with a nonzero value + // element removal is optional because of the overhead of tombstone checks + entry->key = NULL; + entry->value = 0xDEAD; // I mean, it's a tombstone... + + ++m->tombstone_count; + } +} + +int hashmap_size(hashmap *m) { return m->count - m->tombstone_count; } + +int hashmap_iterate(hashmap *m, hashmap_callback c, void *user_ptr) { + // loop through the linked list of valid entries + // this way we can skip over empty buckets + struct bucket *current = m->first; + int error = 0; + + while (current != NULL) { + // "tombstone" check + if (current->key != NULL) + error = c(current->key, current->ksize, current->value, user_ptr); + if (error == -1) + break; + + current = current->next; + } + return error; +} \ No newline at end of file diff --git a/src/map.h b/src/map.h new file mode 100644 index 0000000..b6543a9 --- /dev/null +++ b/src/map.h @@ -0,0 +1,110 @@ +// +// map.h +// +// Created by Mashpoe on 1/15/21. +// + +#ifndef map_h +#define map_h + +#define hashmap_str_lit(str) (str), sizeof(str) - 1 +#define hashmap_static_arr(arr) (arr), sizeof(arr) + +// removal of map elements is disabled by default because of its slight +// overhead. if you want to enable this feature, uncomment the line below: +// #define __HASHMAP_REMOVABLE + +#include +#include + +struct bucket { + // `next` must be the first struct element. + // changing the order will break multiple functions + struct bucket *next; + + // key, key size, key hash, and associated value + const void *key; + size_t ksize; + uint32_t hash; + uintptr_t value; +}; + +struct hashmap { + struct bucket *buckets; + int capacity; + int count; + + // "tombstones" are empty buckets from removing elements + int tombstone_count; + + // a linked list of all valid entries, in order + struct bucket *first; + // lets us know where to add the next element + struct bucket *last; +}; + +// hashmaps can associate keys with pointer values or integral types. +typedef struct hashmap hashmap; + +// a callback type used for iterating over a map/freeing entries: +// `int (const void* key, size_t size, uintptr_t value, void* +// usr)` `usr` is a user pointer which can be passed through `hashmap_iterate`. +typedef int (*hashmap_callback)(const void *key, size_t ksize, uintptr_t value, + void *usr); + +hashmap *hashmap_create(void); + +// only frees the hashmap object and buckets. +// does not call free on each element's `key` or `value`. +// to free data associated with an element, call `hashmap_iterate`. +void hashmap_free(hashmap *map); + +// does not make a copy of `key`. +// you must copy it yourself if you want to guarantee its lifetime, +// or if you intend to call `hashmap_key_free`. +// returns -1 on error. +int hashmap_set(hashmap *map, const void *key, size_t ksize, uintptr_t value); + +// adds an entry if it doesn't exist, using the value of `*out_in`. +// if it does exist, it sets value in `*out_in`, meaning the value +// of the entry will be in `*out_in` regardless of whether or not +// it existed in the first place. +// returns -1 on error. +// returns 1 if the entry already existed, returns 0 otherwise. +int hashmap_get_set(hashmap *map, const void *key, size_t ksize, + uintptr_t *out_in); + +// similar to `hashmap_set()`, but when overwriting an entry, +// you'll be able properly free the old entry's data via a callback. +// unlike `hashmap_set()`, this function will overwrite the original key +// pointer, which means you can free the old key in the callback if applicable. +int hashmap_set_free(hashmap *map, const void *key, size_t ksize, + uintptr_t value, hashmap_callback c, void *usr); + +int hashmap_get(hashmap *map, const void *key, size_t ksize, + uintptr_t *out_val); + +#ifdef __HASHMAP_REMOVABLE +void hashmap_remove(hashmap *map, const void *key, size_t ksize); + +// same as `hashmap_remove()`, but it allows you to free an entry's data first +// via a callback. +void hashmap_remove_free(hashmap *m, const void *key, size_t ksize, + hashmap_callback c, void *usr); +#endif + +int hashmap_size(hashmap *map); + +// iterate over the map, calling `c` on every element. +// goes through elements in the order they were added. +// the element's key, key size, value, and `usr` will be passed to `c`. +// if `c` returns -1 the iteration is aborted. +// returns the last result of `c` +int hashmap_iterate(hashmap *map, hashmap_callback c, void *usr); + +// dumps bucket info for debugging. +// allows you to see how many collisions you are getting. +// `0` is an empty bucket, `1` is occupied, and `x` is removed. +// void bucket_dump(hashmap *m); + +#endif // map_h