587 lines
13 KiB
C++
587 lines
13 KiB
C++
/*
|
|
** 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/****************************************************************************\
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
#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 K,class V>
|
|
class DNode
|
|
{
|
|
public:
|
|
K key;
|
|
V value;
|
|
DNode<K,V> *hashNext;
|
|
};
|
|
|
|
template <class K,class V>
|
|
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<K,V> **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 <class K,class V>
|
|
Dictionary<K,V>::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<<tableBits; //Just in case MIN_TABLE_SIZE wasn't a power of 2
|
|
entries=0;
|
|
keepSize=FALSE;
|
|
|
|
//Table is a pointer to a list of pointers (the hash table)
|
|
table=(DNode<K,V> **)new DNode<K,V>* [size];
|
|
assert(table!=NULL);
|
|
|
|
memset((void *)table,0,size*sizeof(void *));
|
|
hashFunc=hashFn;
|
|
}
|
|
|
|
//Free all the memory...
|
|
template <class K,class V>
|
|
Dictionary<K,V>::~Dictionary()
|
|
{
|
|
clear(); // Remove the entries
|
|
delete[](table); // And the table as well
|
|
}
|
|
|
|
// Remove all the entries and free the memory
|
|
template <class K,class V>
|
|
void Dictionary<K,V>::clear()
|
|
{
|
|
DNode<K,V> *temp,*del;
|
|
uint32 i;
|
|
//free all the data
|
|
for (i=0; i<size; i++)
|
|
{
|
|
temp=table[i];
|
|
while(temp!=NULL)
|
|
{
|
|
del=temp;
|
|
temp=temp->hashNext;
|
|
delete(del);
|
|
}
|
|
table[i]=NULL;
|
|
}
|
|
entries=0;
|
|
|
|
while ((getSize()>(uint32)MIN_TABLE_SIZE)&&(keepSize==FALSE))
|
|
shrink();
|
|
}
|
|
|
|
template <class K,class V>
|
|
uint32 Dictionary<K,V>::keyHash(IN K &key)
|
|
{
|
|
uint32 retval=hashFunc(key);
|
|
retval &= ((1<<tableBits)-1);
|
|
assert(retval<getSize());
|
|
return(retval);
|
|
}
|
|
|
|
|
|
template <class K,class V>
|
|
void Dictionary<K,V>::print(IN FILE *out) const
|
|
{
|
|
DNode<K,V> *temp;
|
|
uint32 i;
|
|
|
|
fprintf(out,"--------------------\n");
|
|
for (i=0; i<getSize(); i++)
|
|
{
|
|
temp=table[i];
|
|
|
|
fprintf(out," |\n");
|
|
fprintf(out,"[ ]");
|
|
|
|
while (temp!=NULL)
|
|
{
|
|
fprintf(out,"--[ ]");
|
|
temp=temp->hashNext;
|
|
}
|
|
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 <class K,class V>
|
|
bit8 Dictionary<K,V>::iterate(INOUT int &index,INOUT int &offset,
|
|
OUT V &value) const
|
|
{
|
|
DNode<K,V> *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 <class K,class V>
|
|
uint32 Dictionary<K,V>::getSize(void) const
|
|
{ return(size); }
|
|
|
|
|
|
// Return the current number of entries in the table
|
|
template <class K,class V>
|
|
uint32 Dictionary<K,V>::getEntries(void) const
|
|
{ return(entries); }
|
|
|
|
|
|
// Does the Dictionary contain the key?
|
|
template <class K,class V>
|
|
bit8 Dictionary<K,V>::contains(IN K &key)
|
|
{
|
|
int offset;
|
|
DNode<K,V> *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 <class K,class V>
|
|
bit8 Dictionary<K,V>::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 <class K, class V>
|
|
bit8 Dictionary<K,V>::add(IN K &key,IN V &value)
|
|
{
|
|
int offset;
|
|
DNode<K,V> *node,*item,*temp;
|
|
float percent;
|
|
|
|
item=(DNode<K,V> *)new DNode<K,V>;
|
|
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 <class K,class V>
|
|
bit8 Dictionary<K,V>::remove(IN K &key,OUT V &value)
|
|
{
|
|
int offset;
|
|
DNode<K,V> *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 <class K,class V>
|
|
bit8 Dictionary<K,V>::remove(IN K &key)
|
|
{
|
|
V temp;
|
|
return(remove(key,temp));
|
|
}
|
|
|
|
|
|
// Remove some random K/V pair that's in the Dictionary
|
|
template <class K,class V>
|
|
bit8 Dictionary<K,V>::removeAny(OUT K &key,OUT V &value)
|
|
{
|
|
int offset;
|
|
DNode<K,V> *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 <class K,class V>
|
|
bit8 Dictionary<K,V>::getValue(IN K &key,OUT V &value)
|
|
{
|
|
int offset;
|
|
DNode<K,V> *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 <class K,class V>
|
|
void Dictionary<K,V>::shrink(void)
|
|
{
|
|
int i;
|
|
int oldsize;
|
|
uint32 offset;
|
|
DNode<K,V> **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<K,V> **)new DNode<K,V>*[size];
|
|
assert(table!=NULL);
|
|
memset((void *)table,0,size*sizeof(void *));
|
|
|
|
for (i=0; i<oldsize; i++)
|
|
{
|
|
temp=oldtable[i];
|
|
while (temp!=NULL)
|
|
{
|
|
offset=keyHash(temp->key);
|
|
first=table[offset];
|
|
table[offset]=temp;
|
|
next=temp->hashNext;
|
|
temp->hashNext=first;
|
|
temp=next;
|
|
}
|
|
}
|
|
delete[](oldtable);
|
|
}
|
|
|
|
|
|
template <class K,class V>
|
|
void Dictionary<K,V>::expand(void)
|
|
{
|
|
int i;
|
|
int oldsize;
|
|
uint32 offset;
|
|
DNode<K,V> **oldtable,*temp,*first,*next;
|
|
|
|
if (keepSize==TRUE)
|
|
return;
|
|
|
|
//fprintf(stderr,"Expanding...\n");
|
|
|
|
oldtable=table;
|
|
oldsize=size;
|
|
size*=2;
|
|
tableBits++;
|
|
|
|
table=(DNode<K,V> **)new DNode<K,V>* [size];
|
|
assert(table!=NULL);
|
|
memset((void *)table,0,size*sizeof(void *));
|
|
|
|
for (i=0; i<oldsize; i++)
|
|
{
|
|
temp=oldtable[i];
|
|
while (temp!=NULL)
|
|
{
|
|
offset=keyHash(temp->key);
|
|
first=table[offset];
|
|
table[offset]=temp;
|
|
next=temp->hashNext;
|
|
temp->hashNext=first;
|
|
temp=next;
|
|
}
|
|
}
|
|
delete[](oldtable);
|
|
}
|
|
|
|
#endif
|