view trunk/chipmunkd/cpHashSet.d @ 4:7ebbd4d05553

initial commit
author Extrawurst
date Thu, 02 Dec 2010 02:11:26 +0100
parents
children df4ebc8add66
line wrap: on
line source


// written in the D programming language

module chipmunkd.cpHashSet;

import chipmunkd.chipmunk;
import chipmunkd.chipmunk_types_h;
import chipmunkd.cpArray;
import chipmunkd.prime;

// cpHashSet uses a chained hashtable implementation.
// Other than the transformation functions, there is nothing fancy going on.

// cpHashSetBin's form the linked lists in the chained hash table.
struct cpHashSetBin {
	// Pointer to the element.
	void *elt;
	// Hash value of the element.
	cpHashValue hash;
	// Next element in the chain.
	cpHashSetBin *next;
}

// Equality function. Returns true if ptr is equal to elt.
alias cpBool function(void *ptr, void *elt) cpHashSetEqlFunc;
// Used by cpHashSetInsert(). Called to transform the ptr into an element.
alias void* function(void *ptr, void *data) cpHashSetTransFunc;

struct cpHashSet {
	// Number of elements stored in the table.
	int entries;
	// Number of cells in the table.
	int size;
	
	cpHashSetEqlFunc eql;
	cpHashSetTransFunc trans;
	
	// Default value returned by cpHashSetFind() when no element is found.
	// Defaults to null.
	void *default_value;
	
	// The table and recycled bins
	cpHashSetBin **table;
	cpHashSetBin *pooledBins;
	
	cpArray *allocatedBuffers;
}

alias void function(void *elt, void *data) cpHashSetIterFunc;
alias cpBool function(void *elt, void *data) cpHashSetFilterFunc;

static void freeWrap(void *ptr, void *unused){cpfree(ptr);}

void
cpHashSetDestroy(cpHashSet *set)
{
	// Free the table.
	cpfree(set.table);
	
	cpArrayEach(set.allocatedBuffers, &freeWrap, null);
	cpArrayFree(set.allocatedBuffers);
}

void
cpHashSetFree(cpHashSet *set)
{
	if(set){
		cpHashSetDestroy(set);
		cpfree(set);
	}
}

cpHashSet *
cpHashSetAlloc()
{
	return cast(cpHashSet *)cpcalloc(1, cpHashSet.sizeof);
}

cpHashSet *
cpHashSetInit(cpHashSet *set, int size, cpHashSetEqlFunc eqlFunc, cpHashSetTransFunc trans)
{
	set.size = next_prime(size);
	set.entries = 0;
	
	set.eql = eqlFunc;
	set.trans = trans;
	
	set.default_value = null;
	
	set.table = cast(cpHashSetBin **)cpcalloc(set.size, (cpHashSetBin *).sizeof);
	set.pooledBins = null;
	
	set.allocatedBuffers = cpArrayNew(0);
	
	return set;
}

cpHashSet *
cpHashSetNew(int size, cpHashSetEqlFunc eqlFunc, cpHashSetTransFunc trans)
{
	return cpHashSetInit(cpHashSetAlloc(), size, eqlFunc, trans);
}

static int
setIsFull(cpHashSet *set)
{
	return (set.entries >= set.size);
}

static void
cpHashSetResize(cpHashSet *set)
{
	// Get the next approximate doubled prime.
	int newSize = next_prime(set.size + 1);
	// Allocate a new table.
	cpHashSetBin **newTable = cast(cpHashSetBin **)cpcalloc(newSize, (cpHashSetBin *).sizeof);
	
	// Iterate over the chains.
	for(int i=0; i<set.size; i++){
		// Rehash the bins into the new table.
		cpHashSetBin *bin = set.table[i];
		while(bin){
			cpHashSetBin *next = bin.next;
			
			int idx = bin.hash%newSize;
			bin.next = newTable[idx];
			newTable[idx] = bin;
			
			bin = next;
		}
	}
	
	cpfree(set.table);
	
	set.table = newTable;
	set.size = newSize;
}

static void
recycleBin(cpHashSet *set, cpHashSetBin *bin)
{
	bin.next = set.pooledBins;
	set.pooledBins = bin;
	bin.elt = null;
}

static cpHashSetBin *
getUnusedBin(cpHashSet *set)
{
	cpHashSetBin *bin = set.pooledBins;
	
	if(bin){
		set.pooledBins = bin.next;
		return bin;
	} else {
		// Pool is exhausted, make more
		int count = CP_BUFFER_BYTES/cpHashSetBin.sizeof;
		assert(count!=0, "Buffer size is too small.");
		
		cpHashSetBin *buffer = cast(cpHashSetBin *)cpmalloc(CP_BUFFER_BYTES);
		cpArrayPush(set.allocatedBuffers, buffer);
		
		// push all but the first one, return the first instead
		for(int i=1; i<count; i++) recycleBin(set, buffer + i);
		return buffer;
	}
}

void *
cpHashSetInsert(cpHashSet *set, cpHashValue hash, void *ptr, void *data)
{
	int idx = hash%set.size;
	
	// Find the bin with the matching element.
	cpHashSetBin *bin = set.table[idx];
	while(bin && !set.eql(ptr, bin.elt))
		bin = bin.next;
	
	// Create it necessary.
	if(!bin){
		bin = getUnusedBin(set);
		bin.hash = hash;
		bin.elt = set.trans(ptr, data); // Transform the pointer.
		
		bin.next = set.table[idx];
		set.table[idx] = bin;
		
		set.entries++;
		
		// Resize the set if it's full.
		if(setIsFull(set))
			cpHashSetResize(set);
	}
	
	return bin.elt;
}

void *
cpHashSetRemove(cpHashSet *set, cpHashValue hash, void *ptr)
{
	int idx = hash%set.size;
	
	// Pointer to the previous bin pointer.
	cpHashSetBin **prev_ptr = &set.table[idx];
	// Pointer the the current bin.
	cpHashSetBin *bin = set.table[idx];
	
	// Find the bin
	while(bin && !set.eql(ptr, bin.elt)){
		prev_ptr = &bin.next;
		bin = bin.next;
	}
	
	// Remove it if it exists.
	if(bin){
		// Update the previous bin pointer to point to the next bin.
		(*prev_ptr) = bin.next;
		set.entries--;
		
		void *return_value = bin.elt;
		
		recycleBin(set, bin);
		
		return return_value;
	}
	
	return null;
}

void *
cpHashSetFind(cpHashSet *set, cpHashValue hash, void *ptr)
{	
	int idx = hash%set.size;
	cpHashSetBin *bin = set.table[idx];
	while(bin && !set.eql(ptr, bin.elt))
		bin = bin.next;
		
	return (bin ? bin.elt : set.default_value);
}

void
cpHashSetEach(cpHashSet *set, cpHashSetIterFunc func, void *data)
{
	for(int i=0; i<set.size; i++){
		cpHashSetBin *bin = set.table[i];
		while(bin){
			cpHashSetBin *next = bin.next;
			func(bin.elt, data);
			bin = next;
		}
	}
}

void
cpHashSetFilter(cpHashSet *set, cpHashSetFilterFunc func, void *data)
{
	// Iterate over all the chains.
	for(int i=0; i<set.size; i++){
		// The rest works similarly to cpHashSetRemove() above.
		cpHashSetBin **prev_ptr = &set.table[i];
		cpHashSetBin *bin = set.table[i];
		while(bin){
			cpHashSetBin *next = bin.next;
			
			if(func(bin.elt, data)){
				prev_ptr = &bin.next;
			} else {
				(*prev_ptr) = next;

				set.entries--;
				recycleBin(set, bin);
			}
			
			bin = next;
		}
	}
}