/* +++Date last modified: 05-Jul-1997 */ /* $Id: hash.c,v 1.5 2005/03/05 00:37:14 dasenbro Exp $ */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "hash.h" #include "mpool.h" #include "xmalloc.h" #include "exitcodes.h" /* ** public domain code by Jerry Coffin, with improvements by HenkJan Wolthuis. ** ** Tested with Visual C 1.0 and Borland C 3.1. ** Compiles without warnings, and seems like it should be pretty ** portable. ** ** Modified for use with libcyrus by Ken Murchison. ** - prefixed functions with 'hash_' to avoid symbol clashing ** - use xmalloc() and xstrdup() ** - cleaned up free_hash_table(), doesn't use enumerate anymore ** - added 'rock' to hash_enumerate() ** ** Further modified by Rob Siemborski. ** - xmalloc can never return NULL, so don't worry about it ** - sort the buckets for faster searching ** - actually, we'll just use a memory pool for this sucker ** (atleast, in the cases where it is advantageous to do so) */ /* Initialize the hash_table to the size asked for. Allocates space ** for the correct number of pointers and sets them to NULL. If it ** can't allocate sufficient memory, signals error by setting the size ** of the table to 0. */ hash_table *construct_hash_table(hash_table *table, size_t size, int use_mpool) { if(!table) fatal("construct_hash_table called without a starting table", EC_TEMPFAIL); if(!size) fatal("construct_hash_table called without a size", EC_TEMPFAIL); table->size = size; /* Allocate the table -- different for using memory pools and not */ if(use_mpool) { /* Allocate an initial memory pool for 32 byte keys + the hash table * + the buckets themselves */ table->pool = new_mpool(size * (32 + sizeof(bucket*) + sizeof(bucket))); table->table = (bucket **)mpool_malloc(table->pool,sizeof(bucket *) * size); } else { table->pool = NULL; table->table = xmalloc(sizeof(bucket *) * size); } /* Allocate the table and initilize it */ memset(table->table, 0, sizeof(bucket *) * size); return table; } /* ** Insert 'key' into hash table. ** Returns pointer to old data associated with the key, if any, or ** NULL if the key wasn't in the table previously. */ void *hash_insert(const char *key, void *data, hash_table *table) { unsigned val = strhash(key) % table->size; bucket *ptr, *newptr; bucket **prev; /* ** NULL means this bucket hasn't been used yet. We'll simply ** allocate space for our new bucket and put our data there, with ** the table pointing at it. */ if (!((table->table)[val])) { if(table->pool) { (table->table)[val] = (bucket *)mpool_malloc(table->pool, sizeof(bucket)); (table->table)[val] -> key = mpool_strdup(table->pool, key); } else { (table->table)[val] = (bucket *)xmalloc(sizeof(bucket)); (table->table)[val] -> key = xstrdup(key); } (table->table)[val] -> next = NULL; (table->table)[val] -> data = data; return (table->table)[val] -> data; } /* ** This spot in the table is already in use. See if the current string ** has already been inserted, and if so, increment its count. */ for (prev = &((table->table)[val]), ptr=(table->table)[val]; ptr; prev=&(ptr->next),ptr=ptr->next) { int cmpresult = strcmp(key,ptr->key); if (!cmpresult) { /* Match! Replace this value and return the old */ void *old_data; old_data = ptr->data; ptr -> data = data; return old_data; } else if (cmpresult < 0) { /* The new key is smaller than the current key-- * insert a node and return this data */ if(table->pool) { newptr = (bucket *)mpool_malloc(table->pool, sizeof(bucket)); newptr->key = mpool_strdup(table->pool, key); } else { newptr = (bucket *)xmalloc(sizeof(bucket)); newptr->key = xstrdup(key); } newptr->data = data; newptr->next = ptr; *prev = newptr; return data; } } /* ** This key is the largest one so far. Add it to the end ** of the list (*prev should be correct) */ if(table->pool) { newptr=(bucket *)mpool_malloc(table->pool,sizeof(bucket)); newptr->key = mpool_strdup(table->pool,key); } else { newptr=(bucket *)xmalloc(sizeof(bucket)); newptr->key = xstrdup(key); } newptr->data = data; newptr->next = NULL; *prev = newptr; return data; } /* ** Look up a key and return the associated data. Returns NULL if ** the key is not in the table. */ void *hash_lookup(const char *key, hash_table *table) { unsigned val = strhash(key) % table->size; bucket *ptr; if (!(table->table)[val]) return NULL; for ( ptr = (table->table)[val];NULL != ptr; ptr = ptr->next ) { int cmpresult = strcmp(key, ptr->key); if (!cmpresult) return ptr->data; else if(cmpresult < 0) /* key < ptr->key -- we passed it */ return NULL; } return NULL; } /* ** Delete a key from the hash table and return associated ** data, or NULL if not present. */ /* Warning: use this function judiciously if you are using memory pools, * since it will leak memory until you get rid of the entire hash table */ void *hash_del(char *key, hash_table *table) { unsigned val = strhash(key) % table->size; void *data; bucket *ptr, *last = NULL; if (!(table->table)[val]) return NULL; /* ** Traverse the list, keeping track of the previous node in the list. ** When we find the node to delete, we set the previous node's next ** pointer to point to the node after ourself instead. We then delete ** the key from the present node, and return a pointer to the data it ** contains. */ for (last = NULL, ptr = (table->table)[val]; NULL != ptr; last = ptr, ptr = ptr->next) { int cmpresult = strcmp(key, ptr->key); if (!cmpresult) { if (last != NULL ) { data = ptr -> data; last -> next = ptr -> next; if(!table->pool) { free(ptr->key); free(ptr); } return data; } /* ** If 'last' still equals NULL, it means that we need to ** delete the first node in the list. This simply consists ** of putting our own 'next' pointer in the array holding ** the head of the list. We then dispose of the current ** node as above. */ else { data = ptr->data; (table->table)[val] = ptr->next; if(!table->pool) { free(ptr->key); free(ptr); } return data; } } else if (cmpresult < 0) { /* its not here! */ return NULL; } } /* ** If we get here, it means we didn't find the item in the table. ** Signal this by returning NULL. */ return NULL; } /* ** Frees a complete table by iterating over it and freeing each node. ** the second parameter is the address of a function it will call with a ** pointer to the data associated with each node. This function is ** responsible for freeing the data, or doing whatever is needed with ** it. */ void free_hash_table(hash_table *table, void (*func)(void *)) { unsigned i; bucket *ptr, *temp; /* If we have a function to free the data, apply it everywhere */ /* We also need to traverse this anyway if we aren't using a memory * pool */ if(func || !table->pool) { for (i=0;isize; i++) { ptr = (table->table)[i]; while (ptr) { temp = ptr; ptr = ptr->next; if (func) func(temp->data); if(!table->pool) { free(temp->key); free(temp); } } } } /* Free the main structures */ if(table->pool) { free_mpool(table->pool); table->pool = NULL; } else { free(table->table); } table->table = NULL; table->size = 0; } /* ** Simply invokes the function given as the second parameter for each ** node in the table, passing it the key, the associated data and 'rock'. */ void hash_enumerate(hash_table *table, void (*func)(char *, void *, void *), void *rock) { unsigned i; bucket *temp, *temp_next; for (i=0;isize; i++) { if ((table->table)[i] != NULL) { for (temp = (table->table)[i]; NULL != temp; temp = temp_next) { temp_next = temp->next; func(temp -> key, temp->data, rock); } } } } #ifdef TEST #include void fatal(const char* s, int code) { fprintf(stderr, "hash: %s\r\n", s); exit(code); } void printer(char *string, void *data, void *rock) { printf("%s: %s\n", string, (char *)data); } int main(void) { hash_table table; char *strings[] = { "1","2","3","4","5","A decently long string", NULL }; char *junk[] = { "The first data", "The second data", "The third data", "The fourth data", "The fifth datum", "The sixth piece of data" }; int i; void *j; construct_hash_table(&table,200,1); for (i = 0; NULL != strings[i]; i++ ) hash_insert(strings[i], junk[i], &table); for (i=0;NULL != strings[i];i++) { j = hash_lookup(strings[i], &table); if (!j) printf("\nERROR: %s was not in table.", strings[i]); } for (i=0;NULL != strings[i];i++) { printf("\n"); hash_enumerate(&table, printer, NULL); if(!hash_del(strings[i],&table)) printf("ERROR WITH DELETE of '%s'\n", strings[i]); } for (i=0;NULL != strings[i];i++) { j = hash_lookup(strings[i], &table); if (NULL == j) printf("\n'%s' is not in table",strings[i]); else printf("\nERROR: %s was deleted but is still in table.", strings[i]); } printf("\n"); free_hash_table(&table, NULL); return 0; } #endif /* TEST */