UFO: Alien Invasion
Loading...
Searching...
No Matches
hashtable.cpp
Go to the documentation of this file.
1
5
6/*
7Copyright (C) 2002-2025 UFO: Alien Invasion.
8
9This program is free software; you can redistribute it and/or
10modify it under the terms of the GNU General Public License
11as published by the Free Software Foundation; either version 2
12of the License, or (at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18See the GNU General Public License for more details.
19
20You should have received a copy of the GNU General Public License
21along with this program; if not, write to the Free Software
22Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24*/
25
26#include "hashtable.h"
27
28#include <assert.h>
29#include <stdlib.h>
30#include <stddef.h>
31#include <string.h>
32
33#include "common.h"
34#include "mem.h"
35
36/*
37 The memory structure being build by the hash table looks like this:
38
39 T = hashTable_s
40 B = hashBucket_s
41 I = hashItem_s
42 0 = NULL
43
44 [T]
45 ...
46 [0]
47 [B]
48 [I]->[0]
49 [B]
50 [I]->[0]
51 [0]
52 ...
53 [B]
54 [I]->[I]->[I]->[0]
55 [0]
56 ...
57*/
58
59// the hash table index size */
60#define HASH_TABLE_SIZE 256
61
62// forward declarations
63struct hashBucket_s;
64
82
92
117
118#ifdef __DEBUG__
119static unsigned long _num_allocs = 0u;
120#endif // __DEBUG__
121
122static void* _hash_alloc (memPool_t* pool, int n, size_t s) {
123 #ifdef __DEBUG__
124 _num_allocs++;
125 #endif // __DEBUG__
126 if (pool) return Mem_PoolAlloc (n * s, pool, 0);
127 return Mem_Alloc(n * s);
128}
129
130static void _hash_free (void* p) {
131 #ifdef __DEBUG__
132 _num_allocs--;
133 #endif // __DEBUG__
134 Mem_Free(p);
135}
136
143static HASH_INDEX default_hash(const void* key, int len) {
144 const unsigned char* p = (const unsigned char*) key;
145 unsigned int h = 2166136261u;
146 int i;
147
148 for (i = 0; i < len; i++) {
149 h = (h*16777619u) ^ p[i];
150 }
151
152 return (h % HASH_TABLE_SIZE);
153}
154
165static int default_compare(const void* key1, int len1, const void* key2, int len2) {
166 int n = (len1 < len2) ? len1 : len2;
167 return memcmp(key1, key2, n);
168}
169
181static hashItem_s* _find_bucket_entry (hashBucket_s* b, hashTable_compare c, const void* key, int nkey, hashItem_s** prev) {
182 hashItem_s* p = NULL; // points to preceding item in linked list or NULL if there is none
183 hashItem_s* i = b->root; // points to current item in linked list
184 while (i) {
185 if (c(key, nkey, i->key, i->nkey) == 0) {
186 // return preceding item only if reference for this value is supplied
187 if (prev) (*prev) = p;
188 return i;
189 }
190 p = i;
191 i = i->next;
192 }
193 if (prev) (*prev) = NULL;
194 return NULL;
195}
196
200static void _release_entry (hashItem_s** i, bool ownsKeys, bool ownsValues) {
201 if (ownsKeys) _hash_free ((*i)->key);
202 if (ownsValues) _hash_free ((*i)->value);
203 _hash_free (*i);
204 (*i)=NULL;
205}
206
210static void _release_bucket (hashBucket_s** b, bool ownsKeys, bool ownsValues) {
211 // delete entries
212 hashItem_s* i = (*b)->root;
213 while (i) {
214 hashItem_s* p=i;
215 i = i->next;
216 _release_entry(&p, ownsKeys, ownsValues);
217 }
218 (*b)->root = NULL;
219 // delete the bucket
220 _hash_free (*b);
221 (*b) = NULL;
222}
223
229static void _release_table (hashTable_s** t, bool full) {
230 // delete buckets
231 for (int i=0; i<HASH_TABLE_SIZE; i++) {
232 if ((*t)->table[i]) _release_bucket(&((*t)->table[i]), (*t)->ownsKeys, (*t)->ownsValues);
233 }
234 if (full) {
235 // delete table
236 _hash_free ((*t));
237 (*t) = NULL;
238 }
239}
240
245 if (t) {
246 for (int ti = 0; ti < HASH_TABLE_SIZE; ti++) {
247 if (t->table[ti]) return t->table[ti]->root;
248 }
249 }
250 return nullptr;
251}
252
258 if (i) {
259 if (i->next) {
260 return i->next;
261 }
262 // compute the hash key of the last item iterated
263 int h = t->hash(i->key, i->nkey);
264 // now get the next filled bucket in the array
265 h++;
266 while (h < HASH_TABLE_SIZE) {
267 hashBucket_s* b = (t->table)[h];
268 if (b) {
269 return b->root;
270 }
271 h++;
272 }
273 }
274 return nullptr;
275}
276
287hashTable_s* HASH_NewTable (bool ownsKeys, bool ownsValues, bool duplicateOverwrite) {
288 return HASH_NewTable (ownsKeys, ownsValues, duplicateOverwrite, &default_hash, &default_compare);
289}
290
291
303hashTable_s* HASH_NewTable (bool ownsKeys, bool ownsValues, bool duplicateOverwrite, hashTable_hash h, hashTable_compare c) {
304 return HASH_NewTable(ownsKeys, ownsValues, duplicateOverwrite, h, c, nullptr, nullptr, nullptr);
305}
306
322hashTable_s* HASH_NewTable (bool ownsKeys, bool ownsValues, bool duplicateOverwrite, hashTable_hash h, hashTable_compare c, memPool_t* keys, memPool_t* values, memPool_t* table) {
323 // allocate table
324 hashTable_s* t = (hashTable_s*) _hash_alloc (table, 1, sizeof(hashTable_s));
325 t->hash = h;
326 t->compare = c;
327 t->ownsKeys = ownsKeys;
328 t->ownsValues = ownsValues;
329 t->duplicateOverwrite = duplicateOverwrite;
330 t->keyPool = keys;
331 t->valuePool = values;
332 t->internalPool = table;
333 return t;
334}
335
337 hashTable_s* t = HASH_NewTable(source->ownsKeys, source->ownsValues, source->duplicateOverwrite, source->hash, source->compare);
338
339 hashItem_s* item = _iterator_first (source);
340 while (item) {
341 HASH_Insert (t, item->key, item->nkey, item->value, item->nvalue);
342 item = _iterator_next (source, item);
343 }
344 return t;
345}
346
352 _release_table (t, true);
353}
354
369bool HASH_Insert (hashTable_s* t, const void* key, int nkey, const void* value, int nvalue) {
370 // compute hash index
371 int idx=t->hash(key, nkey);
372 // find the bucket, if no bucket available create a new one
373 hashBucket_s* b = t->table[idx];
374 if (b == NULL) {
375 t->table[idx] = b = (hashBucket_s*)_hash_alloc (t->internalPool, 1, sizeof(hashBucket_s));
376 }
377 // find entry
378 hashItem_s* i = _find_bucket_entry(b, t->compare, key, nkey, NULL);
379 if (i) {
380 // entry already found, so we are overwriting the current value here
381 // check table settings to see if we need to assert here
382 if (!t->duplicateOverwrite) {
383 assert(false);
384 return false;
385 }
386 // if the table owns the items, create a copy
387 if (t->ownsValues) {
388 i->value = _hash_alloc (t->valuePool, 1, nvalue);
389 memcpy (i->value, value, nvalue);
390 }
391 else {
392 i->value = const_cast<void*>(value);
393 }
394 i->nvalue = nvalue;
395 }
396 else {
397 // create a new entry, add it to the top of the list
398 i = (hashItem_s*) _hash_alloc (t->internalPool, 1, sizeof(hashItem_s));
399 if (t->ownsKeys) {
400 i->key = _hash_alloc (t->keyPool, 1, nkey);
401 memcpy (i->key, key, nkey);
402 }
403 else {
404 i->key = const_cast<void*>(key);
405 }
406 i->nkey = nkey;
407 if (t->ownsValues) {
408 i->value = _hash_alloc (t->valuePool, 1, nvalue);
409 memcpy (i->value, value, nvalue);
410 }
411 else {
412 i->value = const_cast<void*>(value);
413 }
414 i->nvalue = nvalue;
415
416 i->next = b->root;
417 b->root = i;
418 }
419 // increase the count of the bucket
420 b->count++;
421
422 return true;
423}
424
429void* HASH_Remove (hashTable_s* t, const void* key, int nkey) {
430 // return value
431 void* v = NULL;
432 // compute index
433 int idx = t->hash (key, nkey);
434 if (t->table[idx]) {
435 // find hash entry
436 hashItem_s* prev;
437 hashItem_s* i=_find_bucket_entry (t->table[idx], t->compare, key, nkey, &prev);
438 if (i) {
439 if (!t->ownsValues) v = i->value;
440 // link prev item with next item (effectively unchaining i from the linked list)
441 if (prev) {
442 prev->next = i->next;
444 }
445 else {
446 // special case: the entry found was the root entry
447 t->table[idx]->root = i->next;
449 }
450 // drop the count in this bucket by one
451 t->table[idx]->count--;
452 }
453 }
454 return v;
455}
456
461 _release_table (&t, false);
462}
463
467void* HASH_Get (hashTable_s* t, const void* key, int nkey) {
468 // compute bucket index
469 int idx = t->hash(key, nkey);
470 if (t->table[idx]) {
471 // from this bucket, scan for exact match
472 hashItem_s* i = _find_bucket_entry(t->table[idx], t->compare, key, nkey, NULL);
473 if (i) return i->value;
474 }
475 return NULL;
476}
477
482 // iteratie the table
483 int cnt=0;
484 for (int i=0; i<HASH_TABLE_SIZE;i++) {
485 if (t->table[i]) cnt += (t->table[i]->count);
486 }
487 return cnt;
488}
489
490#ifdef __DEBUG__
491
492#include <time.h>
493#include <stdlib.h>
494#include <stdio.h>
495
499bool HASH_test () {
500 // return value
501 bool result = true;
502
503 /* test 1: create the hash table, delete the hash table */
504 hashTable_s* table = HASH_NewTable(true, true, true);
505 HASH_DeleteTable (&table);
506 /* check table pointer is correctly set to nil */
507 result = result && (table == NULL);
508 if (!result) return false;
509 /* check alloc total */
510 result = result && (_num_allocs == 0);
511 if (!result) return false;
512
513 /* test 2: create the hash table, insert 3 values, delete the hash table */
514 table = HASH_NewTable(true, true, true);
515 HASH_Insert (table, "AAA", 4, "AAA", 4);
516 HASH_Insert (table, "BBB", 4, "BBB", 4);
517 HASH_Insert (table, "CCC", 4, "CCC", 4);
518 if (HASH_Count(table) != 3) return false;
519 HASH_Clear (table);
520 if (HASH_Count(table) != 0) return false;
521 HASH_DeleteTable (&table);
522 /* check table pointer is correctly set to nil */
523 result = result && (table == NULL);
524 if (!result) return false;
525 /* check alloc total */
526 result = result && (_num_allocs == 0);
527 if (!result) return false;
528
529 /* test 3: create the hash table, insert/remove 3 values, delete the hash table */
530 table = HASH_NewTable(true, true, true);
531 HASH_Insert (table, "AAA", 4, "AAA", 4);
532 HASH_Remove (table, "AAA", 4);
533 HASH_Insert (table, "BBB", 4, "BBB", 4);
534 HASH_Remove (table, "BBB", 4);
535 HASH_Insert (table, "CCC", 4, "CCC", 4);
536 HASH_Remove (table, "CCC", 4);
537 HASH_DeleteTable (&table);
538 /* check table pointer is correctly set to nil */
539 result = result && (table == NULL);
540 if (!result) return false;
541 /* check alloc total */
542 result = result && (_num_allocs == 0);
543 if (!result) return false;
544
545 /* test 4: create the hash table, insert/count/search/delete 3 values, delete the hash table */
546 table = HASH_NewTable(true, true, true);
547 HASH_Insert (table, "AAA", 4, "AAA", 4);
548 HASH_Insert (table, "BBB", 4, "BBB", 4);
549 HASH_Insert (table, "CCC", 4, "CCC", 4);
550 /* check count items */
551 if (HASH_Count(table) != 3) return false;
552 char* aaa = (char*)HASH_Get(table, "AAA", 4);
553 if (strncmp (aaa, "AAA", 4) != 0) return false;
554 char* bbb = (char*)HASH_Get(table, "BBB", 4);
555 if (strncmp (bbb, "BBB", 4) != 0) return false;
556 char* ccc = (char*)HASH_Get(table, "CCC", 4);
557 if (strncmp (ccc, "CCC", 4) != 0) return false;
558 HASH_Remove (table, "AAA", 4);
559 HASH_Remove (table, "BBB", 4);
560 HASH_Remove (table, "CCC", 4);
561 if (HASH_Count(table) != 0) return false;
562 HASH_DeleteTable (&table);
563 /* check table pointer is correctly set to nil */
564 result = result && (table == NULL);
565 if (!result) return false;
566 /* check alloc total */
567 result = result && (_num_allocs == 0);
568 if (!result) return false;
569
570 /* test 5: hash function test */
571 const char AZ[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
572 char buffer[26];
573 /* compute the averag of the hash index of 400 random keys */
574 int total = 0;
575 srand(time(NULL));
576 for(int i=0; i < 4000; i++) {
577 /* create random key of random length between 4..23 charachters */
578 int len = 4 + (rand() % 20);
579 memset (buffer, 0, sizeof(buffer));
580 for (int j=0; j < len; j++) {
581 int v = rand() % sizeof(AZ);
582 buffer[j]=AZ[v];
583 }
584 /* compute the hash index */
585 int idx = default_hash (buffer, len);
586 /* sum index */
587 total += idx;
588 }
589 /* now compute the average of the indices */
590 int avg = (total / 4000);
591 /* the average idx should be somewhere halfway, allow a %10 error margin */
592 int idx_low = (HASH_TABLE_SIZE/2) - (HASH_TABLE_SIZE/10);
593 int idx_high = (HASH_TABLE_SIZE/2) + (HASH_TABLE_SIZE/10);
594 if ( !((idx_low <= avg) && (idx_high >= avg)) ) return false;
595
596 /* test 6: hash table without ownership test */
597 table = HASH_NewTable(true, false, true);
598 char item1[] = "AAA";
599 char item2[] = "BBB";
600 char item3[] = "CCC";
601 HASH_Insert (table, item1, 4, item1, 4);
602 HASH_Insert (table, item2, 4, item2, 4);
603 HASH_Insert (table, item3, 4, item3, 4);
604 /* check if we get the correct value pointers */
605 aaa = (char*)HASH_Get(table, "AAA", 4);
606 if (aaa != item1) return false;
607 bbb = (char*)HASH_Get(table, "BBB", 4);
608 if (bbb != item2) return false;
609 ccc = (char*)HASH_Get(table, "CCC", 4);
610 if (ccc != item3) return false;
611 HASH_DeleteTable (&table);
612 /* check table pointer is correctly set to nil */
613 result = result && (table == NULL);
614 if (!result) return false;
615 /* check alloc total */
616 result = result && (_num_allocs == 0);
617 if (!result) return false;
618
619
620 /* end of unit test, everything OK */
621 return true;
622}
623
624#endif // __DEBUG__
unsigned int key
Definition cl_input.cpp:64
definitions common between client and server, but not game lib
static void _release_table(hashTable_s **t, bool full)
Internal function, releases entire table.
int HASH_Count(hashTable_s *t)
Returns the number of entries in the hash table.
static int default_compare(const void *key1, int len1, const void *key2, int len2)
Default compare function for comparing key values.
static void _release_bucket(hashBucket_s **b, bool ownsKeys, bool ownsValues)
Internal function, releases bucket including entries.
void HASH_Clear(hashTable_s *t)
Clears the hash table.
static hashItem_s * _iterator_first(hashTable_s *t)
start iterating with the first item.
static void _hash_free(void *p)
static void _release_entry(hashItem_s **i, bool ownsKeys, bool ownsValues)
Internal function, release an entry including key and value if owned.
void * HASH_Get(hashTable_s *t, const void *key, int nkey)
Returns the value for a given key.
static HASH_INDEX default_hash(const void *key, int len)
Default hash function to map keys into a 0..255 index.
hashTable_s * HASH_CloneTable(hashTable_s *source)
static void * _hash_alloc(memPool_t *pool, int n, size_t s)
hashTable_s * HASH_NewTable(bool ownsKeys, bool ownsValues, bool duplicateOverwrite)
Creates a new hash table and sets it initial capacity.
static hashItem_s * _find_bucket_entry(hashBucket_s *b, hashTable_compare c, const void *key, int nkey, hashItem_s **prev)
Internal function to scan the list of entries in the bucket for an exact match.
bool HASH_Insert(hashTable_s *t, const void *key, int nkey, const void *value, int nvalue)
Inserts a new value with given key into the hash table.
#define HASH_TABLE_SIZE
Definition hashtable.cpp:60
static hashItem_s * _iterator_next(hashTable_s *t, hashItem_s *i)
iterate to next item
void HASH_DeleteTable(hashTable_s **t)
Deletes a hash table and sets the pointer to NULL.
void * HASH_Remove(hashTable_s *t, const void *key, int nkey)
Removes an existing value with given key from the hash table.
Header file for hashtable.
int(* hashTable_compare)(const void *key1, int len1, const void *key2, int len2)
Definition hashtable.h:53
unsigned short int(* hashTable_hash)(const void *key, int len)
Definition hashtable.h:51
unsigned short int HASH_INDEX
Definition hashtable.h:49
Memory handling with sentinel checking and pools with tags for grouped free'ing.
#define Mem_Free(ptr)
Definition mem.h:35
#define Mem_Alloc(size)
Definition mem.h:40
#define Mem_PoolAlloc(size, pool, tagNum)
Definition mem.h:41
QGL_EXTERN GLuint GLchar GLuint * len
Definition r_gl.h:99
QGL_EXTERN int GLboolean GLfloat * v
Definition r_gl.h:120
QGL_EXTERN GLint i
Definition r_gl.h:113
The hash bucket structure, contains a linked list of items.
Definition hashtable.cpp:86
hashItem_s * root
Definition hashtable.cpp:90
The linked list item.
Definition hashtable.cpp:68
void * key
Definition hashtable.cpp:70
hashItem_s * next
Definition hashtable.cpp:78
hashBucket_s * root
Definition hashtable.cpp:80
void * value
Definition hashtable.cpp:74
The hash table structure, contains an array of buckets being indexed by the hash function.
Definition hashtable.cpp:96
hashTable_compare compare
hashTable_hash hash
bool duplicateOverwrite
memPool_t * valuePool
memPool_t * keyPool
hashBucket_s * table[HASH_TABLE_SIZE]
Definition hashtable.cpp:98
memPool_t * internalPool