CnC_Red_Alert/LAUNCHER/DICTIONARY.H

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