| /* $Id$ | |
| * | |
| * Licensed to the Apache Software Foundation (ASF) under one or more | |
| * contributor license agreements. See the NOTICE file distributed with | |
| * this work for additional information regarding copyright ownership. | |
| * The ASF licenses this file to you under the Apache License, Version | |
| * 2.0 (the "License"); you may not use this file except in compliance | |
| * with the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| /* | |
| -------------------------------------------------------------------- | |
| By Bob Jenkins. hashtab.c. Public Domain. | |
| This implements a hash table. | |
| * Keys are unique. Adding an item fails if the key is already there. | |
| * Keys and items are pointed at, not copied. If you change the value | |
| of the key after it is inserted then hfind will not be able to find it. | |
| * The hash table maintains a position that can be set and queried. | |
| * The table length doubles dynamically and never shrinks. The insert | |
| that causes table doubling may take a long time. | |
| * The table length splits when the table length equals the number of items | |
| Comparisons usually take 7 instructions. | |
| Computing a hash value takes 35+6n instructions for an n-byte key. | |
| hcreate - create a hash table | |
| hdestroy - destroy a hash table | |
| hcount - The number of items in the hash table | |
| hkey - key at the current position | |
| hkeyl - key length at the current position | |
| hstuff - stuff at the current position | |
| hfind - find an item in the table | |
| hadd - insert an item into the table | |
| hdel - delete an item from the table | |
| hstat - print statistics about the table | |
| hfirst - position at the first item in the table | |
| hnext - move the position to the next item in the table | |
| -------------------------------------------------------------------- | |
| */ | |
| #include <stddef.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include "jenkstd.h" | |
| #include "jenklook.h" | |
| #include "jenkhtab.h" | |
| #include "jenkrecy.h" | |
| /* sanity check -- make sure ipos, apos, and count make sense */ | |
| static void hsanity(htab *t) | |
| { | |
| ub4 i, end, counter; | |
| hitem *h; | |
| /* test that apos makes sense */ | |
| end = (ub4) 1 << (t->logsize); | |
| if (end < t->apos) | |
| printf("error: end %ld apos %ld\n", end, t->apos); | |
| /* test that ipos is in bucket apos */ | |
| if (t->ipos) | |
| { | |
| for (h=t->table[t->apos]; h && h != t->ipos; h = h->next) | |
| ; | |
| if (h != t->ipos) | |
| printf("error:ipos not in apos, apos is %ld\n", t->apos); | |
| } | |
| /* test that t->count is the number of elements in the table */ | |
| counter=0; | |
| for (counter=0, i=0; i<end; ++i) | |
| for (h=t->table[i]; h; h = h->next) ++counter; | |
| if (counter != t->count) | |
| printf("error: counter %ld t->count %ld\n", counter, t->count); | |
| } | |
| /* | |
| * hgrow - Double the size of a hash table. | |
| * Allocate a new, 2x bigger array, | |
| * move everything from the old array to the new array, then free the old array. | |
| */ | |
| static void hgrow(htab *t) | |
| { | |
| register ub4 newsize = (ub4)1<<(++t->logsize); | |
| register ub4 newmask = newsize-1; | |
| register ub4 i; | |
| register hitem **oldtab = t->table; | |
| register hitem **newtab = (hitem **) malloc(newsize*sizeof(hitem *)); | |
| /* make sure newtab is cleared */ | |
| for (i=0; i<newsize; ++i) newtab[i] = (hitem *)0; | |
| t->table = newtab; | |
| t->mask = newmask; | |
| /* Walk through old table putting entries in new table */ | |
| for (i=newsize>>1; i--;) | |
| { | |
| register hitem *pthis, *pthat, **newplace; | |
| for (pthis = oldtab[i]; pthis;) | |
| { | |
| pthat = pthis; | |
| pthis = pthis->next; | |
| newplace = &newtab[(pthat->hval & newmask)]; | |
| pthat->next = *newplace; | |
| *newplace = pthat; | |
| } | |
| } | |
| /* position the hash table on some existing item */ | |
| hfirst(t); | |
| /* free the old array */ | |
| free((char*)oldtab); | |
| } | |
| /* hcreate - create a hash table initially of size power(2,logsize) */ | |
| htab *hcreate(intx logsize)/* = log base 2 of the size of the hash table */ | |
| { | |
| ub4 i, len; | |
| htab *t = (htab*) malloc(sizeof(htab)); | |
| len = ((ub4) 1 << logsize); | |
| t->table = (hitem **) malloc(sizeof(hitem *)*(ub4)len); | |
| for (i=0; i < len; ++i) t->table[i] = (hitem *)0; | |
| t->logsize = logsize; | |
| t->mask = len-1; | |
| t->count = 0; | |
| t->apos = (ub4)0; | |
| t->ipos = (hitem *)0; | |
| t->space = remkroot(sizeof(hitem)); | |
| t->bcount = 0; | |
| return t; | |
| } | |
| /* hdestroy - destroy the hash table and free all its memory */ | |
| void hdestroy(htab* t) | |
| { | |
| /* hitem *h; // unreferenced local var wng */ | |
| refree(t->space); | |
| free((char *)t->table); | |
| free((char *)t); | |
| } | |
| /* hcount() is a macro, see hashtab.h */ | |
| /* hkey() is a macro, see hashtab.h */ | |
| /* hkeyl() is a macro, see hashtab.h */ | |
| /* hstuff() is a macro, see hashtab.h */ | |
| /** | |
| * hfind - find an item with a given key in a hash table. | |
| * if found, point at the item and return TRUE, otherwise FALSE. | |
| */ | |
| intx hfind(htab* t, ub1* key, ub4 keylen) | |
| { | |
| hitem *h; | |
| ub4 x = lookup(key,keylen,0); /* hash */ | |
| ub4 y; | |
| for (h = t->table[y=(x&t->mask)]; h; h = h->next) | |
| { | |
| if ((x == h->hval) && (keylen == h->keyl) && !memcmp(key, h->key, keylen)) | |
| { | |
| t->apos = y; | |
| t->ipos = h; | |
| return TRUE; | |
| } | |
| } | |
| return FALSE; | |
| } | |
| /** | |
| * hfindx - find an item with a given hashed key in a hash table. | |
| * this function was added by JLD, Cisco CUAE. | |
| * keylen is byte length of original key, not of the hash. | |
| * if found, point at the item and return TRUE, otherwise FALSE. | |
| */ | |
| intx hfindx(htab* t, const ub4 hashval) | |
| { | |
| intx result = FALSE; | |
| ub4 i = hashval & t->mask; | |
| hitem *h = t->table[i]; | |
| for (; h; h = h->next) | |
| if (hashval == h->hval) | |
| { t->apos = i; | |
| t->ipos = h; | |
| result = TRUE; | |
| break; | |
| } | |
| return result; | |
| } | |
| /** | |
| * hadd - add an item to a hash table. | |
| * return FALSE if key is already in the table, otherwise TRUE. | |
| */ | |
| intx hadd(htab *t, ub1 *key, ub4 keylen, void* stuff) | |
| { | |
| register hitem *h, **hp; | |
| register ub4 y; | |
| register ub4 x = lookup(key,keylen,0); | |
| /* make sure the key is not already there */ | |
| for (h = t->table[(y = (x & t->mask))]; h; h = h->next) | |
| { | |
| if ((x == h->hval) && (keylen == h->keyl) && !memcmp(key, h->key, keylen)) | |
| { | |
| t->apos = y; | |
| t->ipos = h; | |
| return FALSE; | |
| } | |
| } | |
| /* find space for a new item */ | |
| /* JLD pointer assumed same size as int -- ouch! */ | |
| h = (hitem*) renew (t->space); | |
| /* make the hash table bigger if it is getting full */ | |
| if (++t->count > (ub4) 1 << (t->logsize)) | |
| { | |
| hgrow(t); | |
| y = (x&t->mask); | |
| } | |
| /* add the new key to the table */ | |
| h->key = key; | |
| h->keyl = keylen; | |
| h->stuff = stuff; | |
| h->hval = x; /* hash */ | |
| hp = &t->table[y]; | |
| h->next = *hp; | |
| *hp = h; | |
| t->ipos = h; | |
| t->apos = y; | |
| #ifdef HSANITY | |
| hsanity(t); | |
| #endif /* HSANITY */ | |
| return TRUE; | |
| } | |
| /** | |
| * haddx - add an item to a hash table. | |
| * this is a version of hadd which expects a pre-calculated hashed key | |
| * in the initial 4 bytes of the key object. | |
| * return FALSE if key is already in the table, otherwise TRUE. | |
| * this function was added by JLD, Cisco CUAE | |
| */ | |
| intx haddx(htab *t, void* keyobj, void* valobj) | |
| { | |
| register hitem *h, **hp; | |
| register ub4 y; | |
| register ub4 hashval = *((ub4*) keyobj); | |
| const ub4 KEYLEN = sizeof(void*); | |
| if (0 == hashval) return FALSE; | |
| for (h = t->table[(y = (hashval & t->mask))]; h; h = h->next) | |
| { | |
| if ((hashval == h->hval) && (KEYLEN == h->keyl) && !memcmp(keyobj, h->key, KEYLEN)) | |
| { | |
| t->apos = y; | |
| t->ipos = h; | |
| return FALSE; | |
| } | |
| } | |
| /* find space for a new item */ | |
| h = (hitem*) renew (t->space); | |
| /* make the hashval table bigger if it is getting full */ | |
| if (++t->count > (ub4) 1 << (t->logsize)) | |
| { | |
| hgrow(t); | |
| y = (hashval&t->mask); | |
| } | |
| /* add the new key and value to the table */ | |
| h->key = keyobj; | |
| h->keyl = KEYLEN; | |
| h->stuff = valobj; | |
| h->hval = hashval; | |
| hp = &t->table[y]; | |
| h->next = *hp; | |
| *hp = h; | |
| t->ipos = h; | |
| t->apos = y; | |
| #ifdef HSANITY | |
| hsanity(t); | |
| #endif /* HSANITY */ | |
| return TRUE; | |
| } | |
| /* hdel - delete the item at the current position */ | |
| intx hdel(htab* t) | |
| { | |
| hitem *h; /* item being deleted */ | |
| hitem **ip; /* a counter */ | |
| /* check for item not existing */ | |
| if (!(h = t->ipos)) return FALSE; | |
| /* remove item from its list */ | |
| for (ip = &t->table[t->apos]; *ip != h; ip = &(*ip)->next) | |
| ; | |
| *ip = (*ip)->next; | |
| --(t->count); | |
| /* adjust position to something that exists */ | |
| if (!(t->ipos = h->next)) hnbucket(t); | |
| /* recycle the deleted hitem node */ | |
| redel(t->space, h); | |
| #ifdef HSANITY | |
| hsanity(t); | |
| #endif /* HSANITY */ | |
| return TRUE; | |
| } | |
| /* hfirst - position on the first element in the table */ | |
| intx hfirst(htab *t) | |
| { | |
| t->apos = (ub4) t->mask; | |
| (void)hnbucket(t); | |
| return (t->ipos != (hitem *)0); | |
| } | |
| /* hnext() is a macro, see hashtab.h */ | |
| /* | |
| * hnbucket - Move position to the first item in the next bucket. | |
| * Return TRUE if we did not wrap around to the beginning of the table | |
| */ | |
| intx hnbucket(htab *t) | |
| { | |
| ub4 oldapos = t->apos; | |
| ub4 end = (ub4) 1 << (t->logsize); | |
| ub4 i; | |
| /* see if the element can be found without wrapping around */ | |
| for (i=oldapos+1; i<end; ++i) | |
| { | |
| if (t->table[i&t->mask]) | |
| { | |
| t->apos = i; | |
| t->ipos = t->table[i]; | |
| return TRUE; | |
| } | |
| } | |
| /* must have to wrap around to find the last element */ | |
| for (i=0; i <= oldapos; ++i) | |
| { | |
| if (t->table[i]) | |
| { | |
| t->apos = i; | |
| t->ipos = t->table[i]; | |
| return FALSE; | |
| } | |
| } | |
| return FALSE; | |
| } | |
| /** | |
| hstat: report table statistics | |
| */ | |
| void hstat(htab *t) | |
| { | |
| ub4 i,j; | |
| double total = 0.0; | |
| hitem *h; | |
| hitem *walk, *walk2, *stat = (hitem *)0; | |
| /* in stat, keyl will store length of list, hval the number of buckets */ | |
| for (i=0; i <= t->mask; ++i) | |
| { | |
| for (h=t->table[i], j=0; h; ++j, h=h->next) | |
| ; | |
| for (walk=stat; walk && (walk->keyl != j); walk=walk->next) | |
| ; | |
| if (walk) | |
| { | |
| ++(walk->hval); | |
| } | |
| else | |
| { | |
| walk = (hitem *)renew(t->space); | |
| walk->keyl = j; | |
| walk->hval = 1; | |
| if (!stat || stat->keyl > j) {walk->next=stat; stat=walk;} | |
| else | |
| { | |
| for (walk2=stat; | |
| walk2->next && (walk2->next->keyl<j); | |
| walk2=walk2->next) | |
| ; | |
| walk->next = walk2->next; | |
| walk2->next = walk; | |
| } | |
| } | |
| } | |
| /* figure out average list length for existing elements */ | |
| for (walk=stat; walk; walk=walk->next) | |
| { | |
| total += (double)walk->hval * (double)walk->keyl * (double)walk->keyl; | |
| } | |
| if (t->count) | |
| total /= (double)t->count; | |
| else total = 0.0; | |
| /* print statistics */ | |
| printf("** hashtable stats follow\n"); | |
| for (walk=stat; walk; walk=walk->next) | |
| printf("** items %ld: %ld buckets\n", walk->keyl, walk->hval); | |
| printf("** buckets: %ld items: %ld existing: %g\n\n", | |
| ((ub4)1<<t->logsize), t->count, total); | |
| /* clean up */ | |
| while (stat) | |
| { | |
| walk = stat->next; | |
| redel(t->space, stat); | |
| stat = walk; | |
| } | |
| } | |