Allow passing an element-freeing function to hashmap_new

If present, the function is then used in hashmap_clear and
hashmap_free to free any data referenced by the hashmap elements.
pull/11/head
Tom Copeland 3 years ago
parent c546ff8928
commit 14ed10f4c5

@ -45,7 +45,7 @@ int main() {
// argument is the initial capacity. The third and fourth arguments are // argument is the initial capacity. The third and fourth arguments are
// optional seeds that are passed to the following hash function. // optional seeds that are passed to the following hash function.
struct hashmap *map = hashmap_new(sizeof(struct user), 0, 0, 0, struct hashmap *map = hashmap_new(sizeof(struct user), 0, 0, 0,
user_hash, user_compare, NULL); user_hash, user_compare, NULL, NULL);
// Here we'll load some users into the hash map. Each set operation // Here we'll load some users into the hash map. Each set operation
// performs a copy of the data that is pointed to in the second argument. // performs a copy of the data that is pointed to in the second argument.
@ -98,6 +98,7 @@ hashmap_count # returns the number of items in the hash map
hashmap_set # insert or replace an existing item and return the previous hashmap_set # insert or replace an existing item and return the previous
hashmap_get # get an existing item hashmap_get # get an existing item
hashmap_delete # delete and return an item hashmap_delete # delete and return an item
hashmap_clear # clear the hash map
``` ```
### Iteration ### Iteration

@ -44,6 +44,7 @@ struct hashmap {
uint64_t seed1; uint64_t seed1;
uint64_t (*hash)(const void *item, uint64_t seed0, uint64_t seed1); uint64_t (*hash)(const void *item, uint64_t seed0, uint64_t seed1);
int (*compare)(const void *a, const void *b, void *udata); int (*compare)(const void *a, const void *b, void *udata);
void (*elfree)(void *item);
void *udata; void *udata;
size_t bucketsz; size_t bucketsz;
size_t nbuckets; size_t nbuckets;
@ -80,6 +81,7 @@ struct hashmap *hashmap_new_with_allocator(
uint64_t seed0, uint64_t seed1), uint64_t seed0, uint64_t seed1),
int (*compare)(const void *a, const void *b, int (*compare)(const void *a, const void *b,
void *udata), void *udata),
void (*elfree)(void *item),
void *udata) void *udata)
{ {
_malloc = _malloc ? _malloc : malloc; _malloc = _malloc ? _malloc : malloc;
@ -111,6 +113,7 @@ struct hashmap *hashmap_new_with_allocator(
map->seed1 = seed1; map->seed1 = seed1;
map->hash = hash; map->hash = hash;
map->compare = compare; map->compare = compare;
map->elfree = elfree;
map->udata = udata; map->udata = udata;
map->spare = ((char*)map)+sizeof(struct hashmap); map->spare = ((char*)map)+sizeof(struct hashmap);
map->edata = (char*)map->spare+bucketsz; map->edata = (char*)map->spare+bucketsz;
@ -147,28 +150,44 @@ struct hashmap *hashmap_new_with_allocator(
// Param `compare` is a function that compares items in the tree. See the // Param `compare` is a function that compares items in the tree. See the
// qsort stdlib function for an example of how this function works. // qsort stdlib function for an example of how this function works.
// The hashmap must be freed with hashmap_free(). // The hashmap must be freed with hashmap_free().
// Param `elfree` is a function that frees a specific item. This should be NULL
// unless you're storing some kind of reference data in the hash.
struct hashmap *hashmap_new(size_t elsize, size_t cap, struct hashmap *hashmap_new(size_t elsize, size_t cap,
uint64_t seed0, uint64_t seed1, uint64_t seed0, uint64_t seed1,
uint64_t (*hash)(const void *item, uint64_t (*hash)(const void *item,
uint64_t seed0, uint64_t seed1), uint64_t seed0, uint64_t seed1),
int (*compare)(const void *a, const void *b, int (*compare)(const void *a, const void *b,
void *udata), void *udata),
void (*elfree)(void *item),
void *udata) void *udata)
{ {
return hashmap_new_with_allocator( return hashmap_new_with_allocator(
(_malloc?_malloc:malloc), (_malloc?_malloc:malloc),
(_realloc?_realloc:realloc), (_realloc?_realloc:realloc),
(_free?_free:free), (_free?_free:free),
elsize, cap, seed0, seed1, hash, compare, udata elsize, cap, seed0, seed1, hash, compare, elfree, udata
); );
} }
static void free_elements(struct hashmap *map) {
if (map->elfree) {
for (size_t i = 0; i < map->nbuckets; i++) {
struct bucket *bucket = bucket_at(map, i);
if (bucket->dib) map->elfree(bucket_item(bucket));
}
}
}
// hashmap_clear quickly clears the map. // hashmap_clear quickly clears the map.
// Every item is called with the element-freeing function given in hashmap_new,
// if present, to free any data referenced in the elements of the hashmap.
// When the update_cap is provided, the map's capacity will be updated to match // When the update_cap is provided, the map's capacity will be updated to match
// the currently number of allocated buckets. This is an optimization to ensure // the currently number of allocated buckets. This is an optimization to ensure
// that this operation does not perform any allocations. // that this operation does not perform any allocations.
void hashmap_clear(struct hashmap *map, bool update_cap) { void hashmap_clear(struct hashmap *map, bool update_cap) {
map->count = 0; map->count = 0;
free_elements(map);
if (update_cap) { if (update_cap) {
map->cap = map->nbuckets; map->cap = map->nbuckets;
} else if (map->nbuckets != map->cap) { } else if (map->nbuckets != map->cap) {
@ -189,7 +208,7 @@ void hashmap_clear(struct hashmap *map, bool update_cap) {
static bool resize(struct hashmap *map, size_t new_cap) { static bool resize(struct hashmap *map, size_t new_cap) {
struct hashmap *map2 = hashmap_new(map->elsize, new_cap, map->seed1, struct hashmap *map2 = hashmap_new(map->elsize, new_cap, map->seed1,
map->seed1, map->hash, map->compare, map->seed1, map->hash, map->compare,
map->udata); map->elfree, map->udata);
if (!map2) { if (!map2) {
return false; return false;
} }
@ -357,8 +376,11 @@ size_t hashmap_count(struct hashmap *map) {
} }
// hashmap_free frees the hash map // hashmap_free frees the hash map
// Every item is called with the element-freeing function given in hashmap_new,
// if present, to free any data referenced in the elements of the hashmap.
void hashmap_free(struct hashmap *map) { void hashmap_free(struct hashmap *map) {
if (!map) return; if (!map) return;
free_elements(map);
map->free(map->buckets); map->free(map->buckets);
map->free(map); map->free(map);
} }
@ -631,10 +653,22 @@ static int compare_ints_udata(const void *a, const void *b, void *udata) {
return *(int*)a - *(int*)b; return *(int*)a - *(int*)b;
} }
static int compare_strs(const void *a, const void *b, void *udata) {
return strcmp(*(char**)a, *(char**)b);
}
static uint64_t hash_int(const void *item, uint64_t seed0, uint64_t seed1) { static uint64_t hash_int(const void *item, uint64_t seed0, uint64_t seed1) {
return hashmap_murmur(item, sizeof(int), seed0, seed1); return hashmap_murmur(item, sizeof(int), seed0, seed1);
} }
static uint64_t hash_str(const void *item, uint64_t seed0, uint64_t seed1) {
return hashmap_murmur(*(char**)item, strlen(*(char**)item), seed0, seed1);
}
static void free_str(void *item) {
xfree(*(char**)item);
}
static void all() { static void all() {
int seed = getenv("SEED")?atoi(getenv("SEED")):time(NULL); int seed = getenv("SEED")?atoi(getenv("SEED")):time(NULL);
int N = getenv("N")?atoi(getenv("N")):2000; int N = getenv("N")?atoi(getenv("N")):2000;
@ -656,7 +690,7 @@ static void all() {
struct hashmap *map; struct hashmap *map;
while (!(map = hashmap_new(sizeof(int), 0, seed, seed, while (!(map = hashmap_new(sizeof(int), 0, seed, seed,
hash_int, compare_ints_udata, NULL))) {} hash_int, compare_ints_udata, NULL, NULL))) {}
shuffle(vals, N, sizeof(int)); shuffle(vals, N, sizeof(int));
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
// // printf("== %d ==\n", vals[i]); // // printf("== %d ==\n", vals[i]);
@ -757,6 +791,29 @@ static void all() {
xfree(vals); xfree(vals);
while (!(map = hashmap_new(sizeof(char*), 0, seed, seed,
hash_str, compare_strs, free_str, NULL)));
for (int i = 0; i < N; i++) {
char *str;
while (!(str = xmalloc(16)));
sprintf(str, "s%i", i);
while(!hashmap_set(map, &str));
}
hashmap_clear(map, false);
assert(hashmap_count(map) == 0);
for (int i = 0; i < N; i++) {
char *str;
while (!(str = xmalloc(16)));
sprintf(str, "s%i", i);
while(!hashmap_set(map, &str));
}
hashmap_free(map);
if (total_allocs != 0) { if (total_allocs != 0) {
fprintf(stderr, "total_allocs: expected 0, got %lu\n", total_allocs); fprintf(stderr, "total_allocs: expected 0, got %lu\n", total_allocs);
exit(1); exit(1);
@ -814,7 +871,7 @@ static void benchmarks() {
shuffle(vals, N, sizeof(int)); shuffle(vals, N, sizeof(int));
map = hashmap_new(sizeof(int), 0, seed, seed, hash_int, compare_ints_udata, map = hashmap_new(sizeof(int), 0, seed, seed, hash_int, compare_ints_udata,
NULL); NULL, NULL);
bench("set", N, { bench("set", N, {
int *v = hashmap_set(map, &vals[i]); int *v = hashmap_set(map, &vals[i]);
assert(!v); assert(!v);
@ -832,7 +889,7 @@ static void benchmarks() {
hashmap_free(map); hashmap_free(map);
map = hashmap_new(sizeof(int), N, seed, seed, hash_int, compare_ints_udata, map = hashmap_new(sizeof(int), N, seed, seed, hash_int, compare_ints_udata,
NULL); NULL, NULL);
bench("set (cap)", N, { bench("set (cap)", N, {
int *v = hashmap_set(map, &vals[i]); int *v = hashmap_set(map, &vals[i]);
assert(!v); assert(!v);

@ -17,6 +17,7 @@ struct hashmap *hashmap_new(size_t elsize, size_t cap,
uint64_t seed0, uint64_t seed1), uint64_t seed0, uint64_t seed1),
int (*compare)(const void *a, const void *b, int (*compare)(const void *a, const void *b,
void *udata), void *udata),
void (*elfree)(void *item),
void *udata); void *udata);
struct hashmap *hashmap_new_with_allocator( struct hashmap *hashmap_new_with_allocator(
void *(*malloc)(size_t), void *(*malloc)(size_t),
@ -28,6 +29,7 @@ struct hashmap *hashmap_new_with_allocator(
uint64_t seed0, uint64_t seed1), uint64_t seed0, uint64_t seed1),
int (*compare)(const void *a, const void *b, int (*compare)(const void *a, const void *b,
void *udata), void *udata),
void (*elfree)(void *item),
void *udata); void *udata);
void hashmap_free(struct hashmap *map); void hashmap_free(struct hashmap *map);
void hashmap_clear(struct hashmap *map, bool update_cap); void hashmap_clear(struct hashmap *map, bool update_cap);

Loading…
Cancel
Save