init
This commit is contained in:
commit
26b5169bb2
6 changed files with 555 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/elem
|
4
Makefile
Normal file
4
Makefile
Normal file
|
@ -0,0 +1,4 @@
|
|||
CC=gcc
|
||||
|
||||
make: src/main.c
|
||||
$(CC) -o elem src/map.c src/main.c
|
11
combos.txt
Normal file
11
combos.txt
Normal file
|
@ -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
|
113
src/main.c
Normal file
113
src/main.c
Normal file
|
@ -0,0 +1,113 @@
|
|||
#include "map.h"
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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);
|
||||
}
|
316
src/map.c
Normal file
316
src/map.c
Normal file
|
@ -0,0 +1,316 @@
|
|||
//
|
||||
// map.h
|
||||
//
|
||||
// Created by Mashpoe on 1/15/21.
|
||||
//
|
||||
|
||||
#include "map.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
110
src/map.h
Normal file
110
src/map.h
Normal file
|
@ -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 <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
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 <function name>(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
|
Loading…
Reference in a new issue