2021-10-11 18:37:13 -03:00

530 lines
14 KiB
C

#ifndef lint
#ifdef sccs
static char sccsid[] = "@(#)hash.c 1.1 94/10/31";
#endif
#endif
/*
* Copyright (c) 1985, 1988 by Sun Microsystems, Inc.
*/
/*
* This file contains a bunch of routines that implement a general purpose
* hash table.
*/
#include <sunwindow/sun.h> /* Get things like TRUE and FALSE defined */
#include <sunwindow/hash.h> /* Include hash table exported routines */
/*
* Internally used routines:
*/
void free_data();
void hash_free_memory();
long *get_data();
char *hash_get_memory();
char hash_int_equal();
int hash_int_hash();
int hash_int_insert();
void hash_initialize();
void hash_resize();
extern char *valloc();
/*
* hash_create(Size, Key_Empty, Key_Equal, Key_Hash, Key_Insert, Value_Empty,
* Value_Insert, 7) will create and return a hash table using the parameters.
* Due to the large number of arguments, the last argument must be the number
* 7 so that a quick check can be made to make sure that they are all there.
* All of the arguments except the last one can be NULL'ed out.
*/
Hash
hash_create(size, key_empty, key_equal, key_hash, key_insert,
value_empty, value_insert, check)
register int size; /* Number of initial slots */
int key_empty; /* Empty key value */
char (*key_equal)(); /* Key equality routine */
int (*key_hash)(); /* Key hash function */
int (*key_insert)();/* Key insertion routine */
int value_empty; /* Empty value */
int (*value_insert)();/* Value insertion routine */
int check; /* Argument count check */
{
register Hash hash; /* New hash table */
if (check != 7){
(void)fprintf(stderr, "HASH_CREATE: Wrong number of arguments\n");
exit(1);
}
hash = (Hash)hash_get_memory(sizeof *hash);
hash->count = 0;
hash->key_empty = key_empty;
hash->key_equal = (key_equal != NULL) ? key_equal : hash_int_equal;
hash->key_hash = (key_hash != NULL) ? key_hash : hash_int_hash;
hash->key_insert =
(key_insert != NULL) ? key_insert : hash_int_insert;
hash->value_empty = value_empty;
hash->value_insert =
(value_insert != NULL) ? value_insert : hash_int_insert;
hash_initialize(hash, size);
return hash;
}
/*
* hash_destroy(Hash, Key_Destroy, Value_Destroy) will deallocate the storage
* associated with Hash. Each key-value pair in Hash will be destroyed by
* calling Key_Destroy(Key) and Value_Destroy(Value). If Key_Destroy is NULL,
* it will not be called. Likewise, if Value_Destroy is NULL, it will not
* be called.
*/
void
hash_destroy(hash, key_destroy, value_destroy)
Hash hash; /* Hash table to destroy */
void (*key_destroy)(); /* Key destroy routine */
void (*value_destroy)(); /* Value destroy routine */
{
register int count; /* Number of remaining slots */
register long key_empty; /* Empty key */
register long key; /* Current key */
register long *keys; /* Hash keys */
register long slots; /* Number of slots */
register long *values; /* Hash values */
key_empty = hash->key_empty;
keys = hash->keys;
slots = hash->slots;
values = hash->values;
if ((key_destroy != NULL) || (value_destroy != NULL)){
for (count = slots; count > 0; count--){
key = *keys++;
if (key != key_empty){
if (key_destroy != NULL)
key_destroy(key);
if (value_destroy != NULL)
value_destroy(*values);
}
values++;
}
}
free_data(hash->keys, (int)slots);
free_data(hash->values, (int)slots);
hash_free_memory((char *)hash, sizeof *hash);
}
/*
* hash_find(Hash, Key, &Value)=>{True,False} will lookup Key in Hash. If Key
* is found in Hash, True will be returned and the associated value will be
* stored into Value, provided Value is non-Null. Otherwise, False will be
* returned.
*/
Bool
hash_find(hash, key, value)
register Hash hash; /* Hash table */
long key; /* Key to lookup */
long *value; /* Place to store value */
{
int index; /* Index into table */
index = hash_index(hash, key);
if (hash->keys[index] == hash->key_empty)
return False;
if (value != NULL)
*value = hash->values[index];
return True;
}
/*
* hash_get(Hash, Key)=>Value will lookup the value for Key in Hash. If Key
* is not in Hash, a fatal error occurs.
*/
long
hash_get(hash, key)
register Hash hash; /* Hash table */
long key; /* Key to lookup */
{
int index; /* Index into table */
index = hash_index(hash, key);
if (hash->keys[index] == hash->key_empty){
(void)fprintf(stderr,
"HASH_GET:Could not find key (%x) in table (%x)\n",
key, hash);
exit(1);
/*NOTREACHED*/
} else return hash->values[index];
}
/*
* hash_insert(Hash, Key, Value)=>{True,False} will insert Value into Hash
* under Key. If Key is already in Hash, False will be returned and the
* previous value will not be changed. Otherwise, True will be returned.
*/
/*VARARGS1*/
Bool
hash_insert(hash, key, value)
Hash hash; /* Hash table */
long key; /* Key to insert under */
long value; /* Value to insert */
{
register int index; /* Index into hash table */
if (hash->count == hash->limit)
hash_resize(hash);
index = hash_index(hash, key);
if (hash->keys[index] == hash->key_empty){
hash->keys[index] = hash->key_insert(key);
hash->values[index] = hash->value_insert(value);
hash->count++;
return True;
} else return False;
}
/*
* hash_lookup(Hash, Key)=>value will lookup Key in Hash. If Key is not
* in Hash, Empty_Value will be returned.
*/
/*VARARGS1*/
long
hash_lookup(hash, key)
Hash hash; /* Hash table */
long key; /* Key to lookup */
{
return hash->values[hash_index(hash, key)];
}
/*
* hash_replace(Hash, Key, Value)=>{True,False} will insert Value into Hash
* under Key. If Key was already in Hash, True will be returned and the
* previous value will be replaced. Otherwise, False will be returned and
* the Value will be inserted under Key.
*/
Bool
hash_replace(hash, key, value)
register Hash hash; /* Hash table */
long key; /* Key to insert under */
long value; /* Value to insert */
{
register int index; /* Index into hash table */
if (hash->count == hash->limit)
hash_resize(hash);
index = hash_index(hash, key);
if (hash->keys[index] == hash->key_empty){
hash->keys[index] = hash->key_insert(key);
hash->values[index] = hash->value_insert(value);
hash->count++;
return False;
}
hash->values[index] = hash->value_insert(value);
return True;
}
/*
* hash_scan(Hash, Routine, Data) will scan the entire contents of the hash
* table calling Routine(Key, Value, Data) for each key-value pair in Hash.
* The sum of the values returned by routine will be returned.
*/
int
hash_scan(hash, routine, data)
Hash hash; /* Hash table */
register int (*routine)(); /* Routine to scan with */
register long data; /* Data value */
{
register int count; /* Remaining slots to scan */
register int sum; /* Sum of returned values */
register long key_empty; /* Empty key value */
register long key; /* Current key */
register long *keys; /* Hash table keys */
register long *values; /* Hash table values */
sum = 0;
key_empty = hash->key_empty;
keys = hash->keys;
values = hash->values;
for (count = hash->slots; count > 0; count--){
key = *keys++;
if (key != key_empty)
sum += routine(key, *values, data);
values++;
}
return sum;
}
/*
* hash_show(Hash) will show the contents of Hash on the console. This
* routine is used for testing and debugging purposes only.
*/
void
hash_show(hash)
register Hash hash; /* Hash table */
{
int index; /* Index into table */
register long *keys; /* Keys array */
int slots; /* Slots in table */
register long *values;/* Values array */
(void)printf("Hash:%x Count:%d Limit:%d Slots:%d\n",
hash, hash->count, hash->limit, hash->slots);
(void)printf("Key_Empty:%d Key_Equal:%x Key_Hash:%x Key_Insert:%x\n",
hash->key_empty, hash->key_equal,
hash->key_hash, hash->key_insert);
(void)printf("Value_Empty:%d Value_Insert:%d Keys:%x Values:%x\n",
hash->value_empty, hash->value_insert,
hash->keys, hash->values);
keys = hash->keys;
slots = hash->slots;
values = hash->values;
for (index = 0; index < slots; index++)
(void)printf("[%d] %d:%d\n", index, *keys++, *values++);
}
/*
* hash_size(Hash) returns the number of entries in Hash.
*/
int
hash_size(hash)
Hash hash; /* Hash table to use */
{
return hash->count;
}
/*****************************************************************/
/* Internal routines: */
/*****************************************************************/
/*
* hash_index(Hash, Key) will compute the index into Hash where Key should
* go (if it is not already there.)
*/
static int
hash_index(hash, key)
register Hash hash; /* Hash table */
long key; /* Key to use */
{
register unsigned int index; /* Index into table */
long key_empty; /* Empty key */
char (*key_equal)(); /* Key equality function */
long key_temp; /* Temporary key */
register long *keys; /* Hash table keys */
int slots; /* Number of slots in table */
key_empty = hash->key_empty;
key_equal = hash->key_equal;
keys = hash->keys;
slots = hash->slots;
index = hash->key_hash(key, slots);
if (index >= slots)
index %= slots;
while (True){
key_temp = keys[index];
if (key_temp == key_empty)
return index;
if (key_equal(key, key_temp))
return index;
if (++index >= slots)
index = 0;
}
}
/*
* hash_initialize(Hash, Size) will initialize Hash to contain Size table
* entries.
*/
static void
hash_initialize(hash, size)
register Hash hash; /* Hash table */
register int size; /* Number of slots */
{
hash->keys = get_data(size, (int)hash->key_empty);
hash->values = get_data(size, (int)hash->value_empty);
hash->slots = size;
hash->limit = size * 8 / 10;
}
/*
* hash_int_equal(Int1, Int2) will return True if Int1 equals Int2.
*/
static char
hash_int_equal(int1, int2)
int int1; /* First integer */
int int2; /* Second integer */
{
return (int1 == int2);
}
/*
* hash_int_hash(number) will return a hash on number.
*/
static int
hash_int_hash(number)
int number; /* Number to hash */
{
return number;
}
/*
* hash_int_insert(number) will return a copy of number.
*/
static int
hash_int_insert(number)
int number; /* Number to insert */
{
return number;
}
/*
* hash_resize(Hash) will increase the number of slots in the hash table.
*/
static void
hash_resize(hash)
register Hash hash; /* Hash table to resize. */
{
int count; /* Number of entries to reinsert */
int index; /* Index into hash table */
long key; /* Current key */
long key_empty; /* Empty key value */
register long *key_pointer; /* Pointer into old keys */
long *new_keys; /* New keys */
long *new_values; /* New values */
long *old_keys; /* Old keys */
long *old_values; /* Old values */
int slots; /* Number of slots in old table */
long value; /* Current value */
register long *value_pointer; /* Pointer into old values */
key_empty = hash->key_empty;
old_keys = hash->keys;
old_values = hash->values;
slots = hash->slots;
hash_initialize(hash, slots<<1);
new_keys = hash->keys;
new_values = hash->values;
key_pointer = old_keys;
value_pointer = old_values;
for (count = slots; count > 0; count--){
key = *key_pointer++;
value = *value_pointer++;
if (key != key_empty){
index = hash_index(hash, key);
new_keys[index] = key;
new_values[index] = value;
}
}
free_data(old_keys, slots);
free_data(old_values, slots);
}
/*
* get_data(Size, Value) will get Size words of data and initialize them to
* Value.
*/
static long *
get_data(size, value)
register int size; /* Number of words to allocate */
register int value; /* Value to initialize to */
{
long *data; /* Data array */
register long *pointer; /* Pointer into data */
data = (long *)hash_get_memory(size<<2); /* Machine dependent! */
pointer = data;
while (size-- > 0)
*pointer++ = value;
return data;
}
/*
* hash_get_memory(Size) will allocate Size bytes of memory.
* If the memory is not available, a fatal error will occur.
*
* Private to defaults, hash and parse packages.
* Replaces the private get_memory routines that use to be in those packages.
* Introduced after 4.0Beta2 to put all defaults related storage into
* separate pages to avoid thrashing through VM.
*
* hash_get_memory() does not support deallocation, thus hash_free_memory()
* is a no-op.
*/
static char *end_plus_one, *next_free; /* = 0 for -A-R */
#ifdef HASH_ALLOC_COUNT
int hash_requested_count, hash_wasted_count; /* = 0 for -A-R */
#endif
char *
hash_get_memory(size)
int size; /* Number of bytes to allocate */
{
register char *data; /* Newly allocated data */
register int page_size, size_to_valloc;
/*
* guarantee at least one word of usable data,
* make sure align on boundary
*/
if (size < SMALLEST_BLK) {
size = SMALLEST_BLK;
} else {
size = roundup(size, ALIGNSIZ);
}
#ifdef HASH_ALLOC_COUNT
hash_requested_count += size;
#endif
if (end_plus_one < (next_free+size)) {
/* Request too big for remaining space in current block, so
* discard rest of current block and get new block.
*/
#ifdef HASH_ALLOC_COUNT
hash_wasted_count += end_plus_one - next_free;
#endif
page_size = getpagesize();
size_to_valloc = ((size + page_size - 1) / page_size) *
page_size;
/* Try to stay in page, so leave room for the malloc node
* overhead if size is not too big.
*/
if (size_to_valloc >= size + 8)
size_to_valloc -= 8;
next_free = valloc(size_to_valloc);
if (next_free == (char *)0) {
(void)fprintf(stderr,
"Could not valloc %d bytes (Out of swap space?)\n",
size_to_valloc);
exit(1);
/*NOTREACHED*/
}
end_plus_one = next_free + size_to_valloc;
}
data = next_free;
next_free += size;
return data;
}
/*
* free_data(Data, Size) will free Size words of Data.
*/
static void
free_data(data, size)
long *data; /* Data to free */
int size; /* Number of words to free */
{
hash_free_memory((char *)data, size<<2); /* Machine dependent! */
}
/*
* hash_free_memory(Data, Size) should free the Size bytes of Data, but due
* to the quick-and-dirty nature of hash_get_memory does not.
*/
/*ARGSUSED*/
static void
hash_free_memory(data, size)
char *data; /* Data to free */
int size; /* Number of bytes to free */
{
}