/* Hash table library using separate chaining > Created (Julienne Walker): August 7, 2005 > Modified (Julienne Walker): August 11, 2005 Added a cast for malloc to enable clean compilation as C++ */ #include "jsw_hlib.h" #ifdef __cplusplus #include using std::malloc; using std::free; #else #include #endif typedef struct jsw_node { void *key; /* Key used for searching */ void *item; /* Actual content of a node */ struct jsw_node *next; /* Next link in the chain */ } jsw_node_t; typedef struct jsw_head { jsw_node_t *first; /* First link in the chain */ size_t size; /* Length of the chain */ } jsw_head_t; struct jsw_hash { jsw_head_t **table; /* Dynamic chained hash table */ size_t size; /* Current item count */ size_t capacity; /* Current table size */ size_t curri; /* Current index for traversal */ jsw_node_t *currl; /* Current link for traversal */ hash_f hash; /* User defined key hash function */ cmp_f cmp; /* User defined key comparison function */ keydup_f keydup; /* User defined key copy function */ itemdup_f itemdup; /* User defined item copy function */ keyrel_f keyrel; /* User defined key delete function */ itemrel_f itemrel; /* User defined item delete function */ }; static jsw_node_t *new_node ( void *key, void *item, jsw_node_t *next ) { jsw_node_t *node = (jsw_node_t *)malloc ( sizeof *node ); if ( node == NULL ) return NULL; node->key = key; node->item = item; node->next = next; return node; } static jsw_head_t *new_chain ( void ) { jsw_head_t *chain = (jsw_head_t *)malloc ( sizeof *chain ); if ( chain == NULL ) return NULL; chain->first = NULL; chain->size = 0; return chain; } /* Create a new hash table with a capacity of size, and user defined functions for handling keys and items. Returns: An empty hash table, or NULL on failure. */ jsw_hash_t *jsw_hnew ( size_t size, hash_f hash, cmp_f cmp, keydup_f keydup, itemdup_f itemdup, keyrel_f keyrel, itemrel_f itemrel ) { jsw_hash_t *htab = (jsw_hash_t *)malloc ( sizeof *htab ); size_t i; if ( htab == NULL ) return NULL; htab->table = (jsw_head_t **)malloc ( size * sizeof *htab->table ); if ( htab->table == NULL ) { free ( htab ); return NULL; } /* Empty chains have no head */ for ( i = 0; i < size; i++ ) htab->table[i] = NULL; htab->size = 0; htab->capacity = size; htab->curri = 0; htab->currl = NULL; htab->hash = hash; htab->cmp = cmp; htab->keydup = keydup; htab->itemdup = itemdup; htab->keyrel = keyrel; htab->itemrel = itemrel; return htab; } /* Release all memory used by the hash table */ void jsw_hdelete ( jsw_hash_t *htab ) { size_t i; /* Release each chain individually */ for ( i = 0; i < htab->capacity; i++ ) { jsw_node_t *save, *it; if ( htab->table[i] == NULL ) continue; it = htab->table[i]->first; for ( ; it != NULL; it = save ) { save = it->next; htab->keyrel ( it->key ); htab->itemrel ( it->item ); free ( it ); } free ( htab->table[i] ); } /* Release the hash table */ free ( htab->table ); free ( htab ); } /* Find an item with the selected key Returns: The item, or NULL if not found */ void *jsw_hfind ( jsw_hash_t *htab, void *key ) { unsigned h = htab->hash ( key ) % htab->capacity; /* Search the chain only if it exists */ if ( htab->table[h] != NULL ) { jsw_node_t *it = htab->table[h]->first; for ( ; it != NULL; it = it->next ) { if ( htab->cmp ( key, it->key ) == 0 ) return it->item; } } return NULL; } /* Insert an item with the selected key Returns: non-zero for success, zero for failure */ int jsw_hinsert ( jsw_hash_t *htab, void *key, void *item ) { unsigned h = htab->hash ( key ) % htab->capacity; void *dupkey, *dupitem; jsw_node_t *new_item; /* Disallow duplicate keys */ if ( jsw_hfind ( htab, key ) != NULL ) return 0; /* Attempt to create a new item */ dupkey = htab->keydup ( key ); dupitem = htab->itemdup ( item ); new_item = new_node ( dupkey, dupitem, NULL ); if ( new_item == NULL ) return 0; /* Create a chain if the bucket is empty */ if ( htab->table[h] == NULL ) { htab->table[h] = new_chain(); if ( htab->table[h] == NULL ) { htab->keyrel ( new_item->key ); htab->itemrel ( new_item->item ); free ( new_item ); return 0; } } /* Insert at the front of the chain */ new_item->next = htab->table[h]->first; htab->table[h]->first = new_item; ++htab->table[h]->size; ++htab->size; return 1; } /* Remove an item with the selected key Returns: non-zero for success, zero for failure */ int jsw_herase ( jsw_hash_t *htab, void *key ) { unsigned h = htab->hash ( key ) % htab->capacity; jsw_node_t *save, *it; if ( htab->table[h] == NULL ) return 0; it = htab->table[h]->first; /* Remove the first node in the chain? */ if ( htab->cmp ( key, it->key ) == 0 ) { htab->table[h]->first = it->next; /* Release the node's memory */ htab->keyrel ( it->key ); htab->itemrel ( it->item ); free ( it ); /* Remove the chain if it's empty */ if ( htab->table[h]->first == NULL ) { free ( htab->table[h] ); htab->table[h] = NULL; } else --htab->table[h]->size; } else { /* Search for the node */ while ( it->next != NULL ) { if ( htab->cmp ( key, it->next->key ) == 0 ) break; it = it->next; } /* Not found? */ if ( it->next == NULL ) return 0; save = it->next; it->next = it->next->next; /* Release the node's memory */ htab->keyrel ( save->key ); htab->itemrel ( save->item ); free ( save ); --htab->table[h]->size; } /* Erasure invalidates traversal markers */ jsw_hreset ( htab ); --htab->size; return 1; } /* Grow or shrink the table, this is a slow operation Returns: non-zero for success, zero for failure */ int jsw_hresize ( jsw_hash_t *htab, size_t new_size ) { jsw_hash_t *new_htab; jsw_node_t *it; size_t i; /* Build a new hash table, then assign it to the old one */ new_htab = jsw_hnew ( new_size, htab->hash, htab->cmp, htab->keydup, htab->itemdup, htab->keyrel, htab->itemrel ); if ( new_htab == NULL ) return 0; for ( i = 0; i < htab->capacity; i++ ) { if ( htab->table[i] == NULL ) continue; for ( it = htab->table[i]->first; it != NULL; it = it->next ) jsw_hinsert ( new_htab, it->key, it->item ); } /* A hash table holds copies, so release the old table */ jsw_hdelete ( htab ); htab = new_htab; return 1; } /* Reset the traversal markers to the beginning */ void jsw_hreset ( jsw_hash_t *htab ) { size_t i; htab->curri = 0; htab->currl = NULL; /* Find the first non-empty bucket */ for ( i = 0; i < htab->capacity; i++ ) { if ( htab->table[i] != NULL ) break; } htab->curri = i; /* Set the link marker if the table was not empty */ if ( i != htab->capacity ) htab->currl = htab->table[i]->first; } /* Traverse forward by one key */ int jsw_hnext ( jsw_hash_t *htab ) { if ( htab->currl != NULL ) { htab->currl = htab->currl->next; /* At the end of the chain? */ if ( htab->currl == NULL ) { /* Find the next chain */ while ( ++htab->curri < htab->capacity ) { if ( htab->table[htab->curri] != NULL ) break; } /* No more chains? */ if ( htab->curri == htab->capacity ) return 0; htab->currl = htab->table[htab->curri]->first; } } return 1; } /* Get the current key */ const void *jsw_hkey ( jsw_hash_t *htab ) { return htab->currl != NULL ? htab->currl->key : NULL; } /* Get the current item */ void *jsw_hitem ( jsw_hash_t *htab ) { return htab->currl != NULL ? htab->currl->item : NULL; } /* Current number of items in the table */ size_t jsw_hsize ( jsw_hash_t *htab ) { return htab->size; } /* Total allowable number of items without resizing */ size_t jsw_hcapacity ( jsw_hash_t *htab ) { return htab->capacity; } /* Get statistics for the hash table */ jsw_hstat_t *jsw_hstat ( jsw_hash_t *htab ) { jsw_hstat_t *stat; double sum = 0, used = 0; size_t i; /* No stats for an empty table */ if ( htab->size == 0 ) return NULL; stat = (jsw_hstat_t *)malloc ( sizeof *stat ); if ( stat == NULL ) return NULL; stat->lchain = 0; stat->schain = (size_t)-1; for ( i = 0; i < htab->capacity; i++ ) { if ( htab->table[i] != NULL ) { sum += htab->table[i]->size; ++used; /* Non-empty buckets */ if ( htab->table[i]->size > stat->lchain ) stat->lchain = htab->table[i]->size; if ( htab->table[i]->size < stat->schain ) stat->schain = htab->table[i]->size; } } stat->load = used / htab->capacity; stat->achain = sum / used; return stat; }