/* * SPDX-FileCopyrightText: 2013-2019 Tom G. Huang * * SPDX-License-Identifier: BSD-3-Clause */ /******************************************************************************* * arg_hashtable: Implements the hash table utilities * * This file is part of the argtable3 library. * * Copyright (C) 2013-2019 Tom G. Huang * <tomghuang@gmail.com> * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of STEWART HEITMANN nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #ifndef ARG_AMALGAMATION #include "argtable3_private.h" #endif #include <math.h> #include <stdio.h> #include <stdlib.h> #include <string.h> /* * This hash table module is adapted from the C hash table implementation by * Christopher Clark. Here is the copyright notice from the library: * * Copyright (c) 2002, Christopher Clark * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of the original author; nor the names of any contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Credit for primes table: Aaron Krowne * http://br.endernet.org/~akrowne/ * http://planetmath.org/encyclopedia/GoodHashTablePrimes.html */ static const unsigned int primes[] = {53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741}; const unsigned int prime_table_length = sizeof(primes) / sizeof(primes[0]); const float max_load_factor = (float)0.65; static unsigned int enhanced_hash(arg_hashtable_t* h, const void* k) { /* * Aim to protect against poor hash functions by adding logic here. * The logic is taken from Java 1.4 hash table source. */ unsigned int i = h->hashfn(k); i += ~(i << 9); i ^= ((i >> 14) | (i << 18)); /* >>> */ i += (i << 4); i ^= ((i >> 10) | (i << 22)); /* >>> */ return i; } static unsigned int index_for(unsigned int tablelength, unsigned int hashvalue) { return (hashvalue % tablelength); } arg_hashtable_t* arg_hashtable_create(unsigned int minsize, unsigned int (*hashfn)(const void*), int (*eqfn)(const void*, const void*)) { arg_hashtable_t* h; unsigned int pindex; unsigned int size = primes[0]; /* Check requested hash table isn't too large */ if (minsize > (1u << 30)) return NULL; /* * Enforce size as prime. The reason is to avoid clustering of values * into a small number of buckets (yes, distribution). A more even * distributed hash table will perform more consistently. */ for (pindex = 0; pindex < prime_table_length; pindex++) { if (primes[pindex] > minsize) { size = primes[pindex]; break; } } h = (arg_hashtable_t*)xmalloc(sizeof(arg_hashtable_t)); h->table = (struct arg_hashtable_entry**)xmalloc(sizeof(struct arg_hashtable_entry*) * size); memset(h->table, 0, size * sizeof(struct arg_hashtable_entry*)); h->tablelength = size; h->primeindex = pindex; h->entrycount = 0; h->hashfn = hashfn; h->eqfn = eqfn; h->loadlimit = (unsigned int)ceil(size * (double)max_load_factor); return h; } static int arg_hashtable_expand(arg_hashtable_t* h) { /* Double the size of the table to accommodate more entries */ struct arg_hashtable_entry** newtable; struct arg_hashtable_entry* e; unsigned int newsize; unsigned int i; unsigned int index; /* Check we're not hitting max capacity */ if (h->primeindex == (prime_table_length - 1)) return 0; newsize = primes[++(h->primeindex)]; newtable = (struct arg_hashtable_entry**)xmalloc(sizeof(struct arg_hashtable_entry*) * newsize); memset(newtable, 0, newsize * sizeof(struct arg_hashtable_entry*)); /* * This algorithm is not 'stable': it reverses the list * when it transfers entries between the tables */ for (i = 0; i < h->tablelength; i++) { while (NULL != (e = h->table[i])) { h->table[i] = e->next; index = index_for(newsize, e->h); e->next = newtable[index]; newtable[index] = e; } } xfree(h->table); h->table = newtable; h->tablelength = newsize; h->loadlimit = (unsigned int)ceil(newsize * (double)max_load_factor); return -1; } unsigned int arg_hashtable_count(arg_hashtable_t* h) { return h->entrycount; } void arg_hashtable_insert(arg_hashtable_t* h, void* k, void* v) { /* This method allows duplicate keys - but they shouldn't be used */ unsigned int index; struct arg_hashtable_entry* e; if ((h->entrycount + 1) > h->loadlimit) { /* * Ignore the return value. If expand fails, we should * still try cramming just this value into the existing table * -- we may not have memory for a larger table, but one more * element may be ok. Next time we insert, we'll try expanding again. */ arg_hashtable_expand(h); } e = (struct arg_hashtable_entry*)xmalloc(sizeof(struct arg_hashtable_entry)); e->h = enhanced_hash(h, k); index = index_for(h->tablelength, e->h); e->k = k; e->v = v; e->next = h->table[index]; h->table[index] = e; h->entrycount++; } void* arg_hashtable_search(arg_hashtable_t* h, const void* k) { struct arg_hashtable_entry* e; unsigned int hashvalue; unsigned int index; hashvalue = enhanced_hash(h, k); index = index_for(h->tablelength, hashvalue); e = h->table[index]; while (e != NULL) { /* Check hash value to short circuit heavier comparison */ if ((hashvalue == e->h) && (h->eqfn(k, e->k))) return e->v; e = e->next; } return NULL; } void arg_hashtable_remove(arg_hashtable_t* h, const void* k) { /* * TODO: consider compacting the table when the load factor drops enough, * or provide a 'compact' method. */ struct arg_hashtable_entry* e; struct arg_hashtable_entry** pE; unsigned int hashvalue; unsigned int index; hashvalue = enhanced_hash(h, k); index = index_for(h->tablelength, hashvalue); pE = &(h->table[index]); e = *pE; while (NULL != e) { /* Check hash value to short circuit heavier comparison */ if ((hashvalue == e->h) && (h->eqfn(k, e->k))) { *pE = e->next; h->entrycount--; xfree(e->k); xfree(e->v); xfree(e); return; } pE = &(e->next); e = e->next; } } void arg_hashtable_destroy(arg_hashtable_t* h, int free_values) { unsigned int i; struct arg_hashtable_entry *e, *f; struct arg_hashtable_entry** table = h->table; if (free_values) { for (i = 0; i < h->tablelength; i++) { e = table[i]; while (NULL != e) { f = e; e = e->next; xfree(f->k); xfree(f->v); xfree(f); } } } else { for (i = 0; i < h->tablelength; i++) { e = table[i]; while (NULL != e) { f = e; e = e->next; xfree(f->k); xfree(f); } } } xfree(h->table); xfree(h); } arg_hashtable_itr_t* arg_hashtable_itr_create(arg_hashtable_t* h) { unsigned int i; unsigned int tablelength; arg_hashtable_itr_t* itr = (arg_hashtable_itr_t*)xmalloc(sizeof(arg_hashtable_itr_t)); itr->h = h; itr->e = NULL; itr->parent = NULL; tablelength = h->tablelength; itr->index = tablelength; if (0 == h->entrycount) return itr; for (i = 0; i < tablelength; i++) { if (h->table[i] != NULL) { itr->e = h->table[i]; itr->index = i; break; } } return itr; } void arg_hashtable_itr_destroy(arg_hashtable_itr_t* itr) { xfree(itr); } void* arg_hashtable_itr_key(arg_hashtable_itr_t* i) { return i->e->k; } void* arg_hashtable_itr_value(arg_hashtable_itr_t* i) { return i->e->v; } int arg_hashtable_itr_advance(arg_hashtable_itr_t* itr) { unsigned int j; unsigned int tablelength; struct arg_hashtable_entry** table; struct arg_hashtable_entry* next; if (itr->e == NULL) return 0; /* stupidity check */ next = itr->e->next; if (NULL != next) { itr->parent = itr->e; itr->e = next; return -1; } tablelength = itr->h->tablelength; itr->parent = NULL; if (tablelength <= (j = ++(itr->index))) { itr->e = NULL; return 0; } table = itr->h->table; while (NULL == (next = table[j])) { if (++j >= tablelength) { itr->index = tablelength; itr->e = NULL; return 0; } } itr->index = j; itr->e = next; return -1; } int arg_hashtable_itr_remove(arg_hashtable_itr_t* itr) { struct arg_hashtable_entry* remember_e; struct arg_hashtable_entry* remember_parent; int ret; /* Do the removal */ if ((itr->parent) == NULL) { /* element is head of a chain */ itr->h->table[itr->index] = itr->e->next; } else { /* element is mid-chain */ itr->parent->next = itr->e->next; } /* itr->e is now outside the hashtable */ remember_e = itr->e; itr->h->entrycount--; xfree(remember_e->k); xfree(remember_e->v); /* Advance the iterator, correcting the parent */ remember_parent = itr->parent; ret = arg_hashtable_itr_advance(itr); if (itr->parent == remember_e) { itr->parent = remember_parent; } xfree(remember_e); return ret; } int arg_hashtable_itr_search(arg_hashtable_itr_t* itr, arg_hashtable_t* h, void* k) { struct arg_hashtable_entry* e; struct arg_hashtable_entry* parent; unsigned int hashvalue; unsigned int index; hashvalue = enhanced_hash(h, k); index = index_for(h->tablelength, hashvalue); e = h->table[index]; parent = NULL; while (e != NULL) { /* Check hash value to short circuit heavier comparison */ if ((hashvalue == e->h) && (h->eqfn(k, e->k))) { itr->index = index; itr->e = e; itr->parent = parent; itr->h = h; return -1; } parent = e; e = e->next; } return 0; } int arg_hashtable_change(arg_hashtable_t* h, void* k, void* v) { struct arg_hashtable_entry* e; unsigned int hashvalue; unsigned int index; hashvalue = enhanced_hash(h, k); index = index_for(h->tablelength, hashvalue); e = h->table[index]; while (e != NULL) { /* Check hash value to short circuit heavier comparison */ if ((hashvalue == e->h) && (h->eqfn(k, e->k))) { xfree(e->v); e->v = v; return -1; } e = e->next; } return 0; }