/* ** Command & Conquer Red Alert(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ /****************************************************************************\ * C O N F I D E N T I A L --- W E S T W O O D S T U D I O S * ****************************************************************************** Project Name: Carpenter (The RedAlert ladder creator) File Name : dictionary.h Author : Neal Kettler Start Date : June 1, 1997 Last Update : June 17, 1997 This template file implements a hash dictionary. A hash dictionary is used to quickly match a value with a key. This works well for very large sets of data. A table is constructed that has some power of two number of pointers in it. Any value to be put in the table has a hashing function applied to the key. That key/value pair is then put in the linked list at the slot the hashing function specifies. If everything is working well, this is much faster than a linked list, but only if your hashing function is good. \****************************************************************************/ #ifndef DICTIONARY_HEADER #define DICTIONARY_HEADER #include #include #include #include #include "wstypes.h" #include "wdebug.h" // Every entry in the hash dictionary must be an instance of the DNode // template. 'K' and 'V' denote Key and Value. template class DNode { public: K key; V value; DNode *hashNext; }; template class Dictionary { public: Dictionary(uint32 (* hashFn)(K &key)); ~Dictionary(); void clear(void); bit8 add(IN K &key,IN V &value); bit8 getValue(IN K &key, OUT V &value); void print(IN FILE *out) const; uint32 getSize(void) const; uint32 getEntries(void) const; bit8 contains(IN K &key); bit8 updateValue(IN K &key,IN V &value); bit8 remove(IN K &key,OUT V &value); bit8 remove(IN K &key); bit8 removeAny(OUT K &key,OUT V &value); bit8 iterate(INOUT int &index,INOUT int &offset, OUT V &value) const; private: void shrink(void); // halve the number of slots void expand(void); // double the number of slots DNode **table; // This stores the lists at each slot uint32 entries; // number of entries uint32 size; // size of table uint32 tableBits; // table is 2^tableBits big uint32 log2Size; // Junk variable bit8 keepSize; // If true don't shrink or expand uint32 (* hashFunc)(K &key); // User provided hash function uint32 keyHash(IN K &key); // This will reduce to correct range // See initilizer list of constructor for values const double SHRINK_THRESHOLD; // When table is this % full shrink it const double EXPAND_THRESHOLD; // When table is this % full grow it const int MIN_TABLE_SIZE; // must be a power of 2 }; //Create the empty hash dictionary template Dictionary::Dictionary(uint32 (* hashFn)(K &key)) : SHRINK_THRESHOLD(0.20), // When table is only 20% full shrink it EXPAND_THRESHOLD(0.80), // When table is 80% full grow it MIN_TABLE_SIZE(32) // must be a power of 2 { log2Size=MIN_TABLE_SIZE; size=MIN_TABLE_SIZE; assert(size>=4); tableBits=0; while(log2Size) { tableBits++; log2Size>>=1; } tableBits--; size=1< **)new DNode* [size]; assert(table!=NULL); memset((void *)table,0,size*sizeof(void *)); hashFunc=hashFn; } //Free all the memory... template Dictionary::~Dictionary() { clear(); // Remove the entries delete[](table); // And the table as well } // Remove all the entries and free the memory template void Dictionary::clear() { DNode *temp,*del; uint32 i; //free all the data for (i=0; ihashNext; delete(del); } table[i]=NULL; } entries=0; while ((getSize()>(uint32)MIN_TABLE_SIZE)&&(keepSize==FALSE)) shrink(); } template uint32 Dictionary::keyHash(IN K &key) { uint32 retval=hashFunc(key); retval &= ((1< void Dictionary::print(IN FILE *out) const { DNode *temp; uint32 i; fprintf(out,"--------------------\n"); for (i=0; ihashNext; } fprintf(out,"\n"); } fprintf(out,"--------------------\n"); } // // Iterate through all the records. Index is for the table, offset specifies the // element in the linked list. Set both to 0 and continue calling till false // is returned. template bit8 Dictionary::iterate(INOUT int &index,INOUT int &offset, OUT V &value) const { DNode *temp; // index out of range if ((index<0)||(index >= getSize())) return(FALSE); temp=table[index]; while ((temp==NULL)&&((++index) < getSize())) { temp=table[index]; offset=0; } if (temp==NULL) // no more slots with data return(FALSE); uint32 i=0; while ((temp!=NULL) && (i < offset)) { temp=temp->hashNext; i++; } if (temp==NULL) // should never happen return(FALSE); value=temp->value; if (temp->hashNext==NULL) { index++; offset=0; } else offset++; return(TRUE); } // Return the current size of the hash table template uint32 Dictionary::getSize(void) const { return(size); } // Return the current number of entries in the table template uint32 Dictionary::getEntries(void) const { return(entries); } // Does the Dictionary contain the key? template bit8 Dictionary::contains(IN K &key) { int offset; DNode *node; offset=keyHash(key); node=table[offset]; if (node==NULL) { return(FALSE); } // can't find it while(node!=NULL) { if ((node->key)==key) { return(TRUE); } node=node->hashNext; } return(FALSE); } // Try and update the value of an already existing object template bit8 Dictionary::updateValue(IN K &key,IN V &value) { sint32 retval; retval=remove(key); if (retval==FALSE) return(FALSE); add(key,value); return(TRUE); } // Add to the dictionary (if key exists, value is updated with the new V) template bit8 Dictionary::add(IN K &key,IN V &value) { int offset; DNode *node,*item,*temp; float percent; item=(DNode *)new DNode; assert(item!=NULL); #ifdef KEY_MEM_OPS memcpy(&(item->key),&key,sizeof(K)); #else item->key=key; #endif #ifdef VALUE_MEM_OPS memcpy(&(item->value),&value,sizeof(V)); #else item->value=value; #endif item->hashNext=NULL; //If key already exists, it will be overwritten remove(key); offset=keyHash(key); node=table[offset]; if (node==NULL) { table[offset]=item; } else { temp=table[offset]; table[offset]=item; item->hashNext=temp; } entries++; percent=(float)entries; percent/=(float)getSize(); if (percent>= EXPAND_THRESHOLD ) expand(); return(TRUE); } // Remove an item from the dictionary template bit8 Dictionary::remove(IN K &key,OUT V &value) { int offset; DNode *node,*last,*temp; float percent; if (entries==0) return(FALSE); percent=(float)(entries-1); percent/=(float)getSize(); offset=keyHash(key); node=table[offset]; last=node; if (node==NULL) return(FALSE); //special case table points to thing to delete #ifdef KEY_MEM_OPS if (0==memcmp(&(node->key),&key,sizeof(K))) #else if ((node->key)==key) #endif { #ifdef VALUE_MEM_OPS memcpy(&value,&(node->value),sizeof(V)); #else value=node->value; #endif temp=table[offset]->hashNext; delete(table[offset]); table[offset]=temp; entries--; if (percent <= SHRINK_THRESHOLD) shrink(); return(TRUE); } node=node->hashNext; //Now the case if the thing to delete is not the first while (node!=NULL) { #ifdef KEY_MEM_OPS if (0==memcmp(&(node->key),&key,sizeof(K))) #else if (node->key==key) #endif { #ifdef VALUE_MEM_OPS memcpy(&value,&(node->value),sizeof(V)); #else value=node->value; #endif last->hashNext=node->hashNext; entries--; delete(node); break; } last=node; node=node->hashNext; } if (percent <= SHRINK_THRESHOLD) shrink(); return(TRUE); } template bit8 Dictionary::remove(IN K &key) { V temp; return(remove(key,temp)); } // Remove some random K/V pair that's in the Dictionary template bit8 Dictionary::removeAny(OUT K &key,OUT V &value) { int offset; DNode *node,*last,*temp; float percent; if (entries==0) return(FALSE); percent=(entries-1); percent/=(float)getSize(); int i; offset=-1; for (i=0; i<(int)getSize(); i++) if (table[i]!=NULL) { offset=i; break; } if (offset==-1) // Nothing there return(FALSE); node=table[offset]; last=node; #ifdef KEY_MEM_OPS memcpy(&key,&(node->key),sizeof(K)); #else key=node->key; #endif #ifdef VALUE_MEM_OPS memcpy(&value,&(node->value),sizeof(V)); #else value=node->value; #endif temp=table[offset]->hashNext; delete(table[offset]); table[offset]=temp; entries--; if (percent <= SHRINK_THRESHOLD) shrink(); return(TRUE); } template bit8 Dictionary::getValue(IN K &key,OUT V &value) { int offset; DNode *node; offset=keyHash(key); node=table[offset]; if (node==NULL) return(FALSE); #ifdef KEY_MEM_OPS while ((node!=NULL)&&(memcmp(&(node->key),&key,sizeof(K)))) #else while ((node!=NULL)&&( ! ((node->key)==key)) ) // odd syntax so you don't #endif // have to do oper != { node=node->hashNext; } if (node==NULL) { return(FALSE); } #ifdef VALUE_MEM_OPS memcpy(&value,&(node->value),sizeof(V)); #else value=(node->value); #endif return(TRUE); } //A note about Shrink and Expand: They are never necessary, they are //only here to improve performance of the hash table by reducing //the length of the linked list at each table entry. // Shrink the hash table by a factor of 2 (and relocate entries) template void Dictionary::shrink(void) { int i; int oldsize; uint32 offset; DNode **oldtable,*temp,*first,*next; if ((size<=(uint32)MIN_TABLE_SIZE)||(keepSize==TRUE)) return; //fprintf(stderr,"Shrinking....\n"); oldtable=table; oldsize=size; size/=2; tableBits--; table=(DNode **)new DNode*[size]; assert(table!=NULL); memset((void *)table,0,size*sizeof(void *)); for (i=0; ikey); first=table[offset]; table[offset]=temp; next=temp->hashNext; temp->hashNext=first; temp=next; } } delete[](oldtable); } template void Dictionary::expand(void) { int i; int oldsize; uint32 offset; DNode **oldtable,*temp,*first,*next; if (keepSize==TRUE) return; //fprintf(stderr,"Expanding...\n"); oldtable=table; oldsize=size; size*=2; tableBits++; table=(DNode **)new DNode* [size]; assert(table!=NULL); memset((void *)table,0,size*sizeof(void *)); for (i=0; ikey); first=table[offset]; table[offset]=temp; next=temp->hashNext; temp->hashNext=first; temp=next; } } delete[](oldtable); } #endif