From c323f800f663e8f92f1cdcdca537848caa79ac86 Mon Sep 17 00:00:00 2001 From: saad0105050 Date: Sat, 13 Sep 2014 04:03:44 -0400 Subject: [PATCH] Added LinearProbingHashTable, but did not do testing. To do: testing. --- YASI_12/common.h | 1 + YASI_12/ds.hashtable.h | 362 +++++++++++++++++++++++++++++++++++------ 2 files changed, 315 insertions(+), 48 deletions(-) diff --git a/YASI_12/common.h b/YASI_12/common.h index 948dce4..7cbe827 100644 --- a/YASI_12/common.h +++ b/YASI_12/common.h @@ -12,6 +12,7 @@ #endif #endif // _DEBUG ///////////////////////////////////////////////////////// +#include #include "test.h" diff --git a/YASI_12/ds.hashtable.h b/YASI_12/ds.hashtable.h index 77cc643..e91aa88 100644 --- a/YASI_12/ds.hashtable.h +++ b/YASI_12/ds.hashtable.h @@ -8,16 +8,23 @@ using namespace std; namespace yasi{ namespace ds{ + template class IKeyValueStore{ +protected: + enum { + RESIZE_GROW = 0, + RESIZE_SHRINK + }; + virtual void resize(const int growOrShrink, const unsigned int logFactor) = 0; + void grow() { resize(RESIZE_GROW, 1); } // doubles the capacity + void shrink() { resize(RESIZE_SHRINK, 1); } // halves the capacity public: virtual ~IKeyValueStore(){} virtual Value* get(const Key&) const = 0; virtual void put(const Key&, const Value&) = 0; virtual bool contains(const Key&) const = 0; virtual void remove(const Key&) = 0; - virtual void grow() = 0; // doubles the capacity - virtual void shrink() = 0; // halves the capacity }; template @@ -51,10 +58,6 @@ template< class Key, > class HashTableBase : public IKeyValueStore < Key, Value >{ protected: - enum { - RESIZE_GROW = 0, - RESIZE_SHRINK - }; // size of the table // must be a power of two unsigned int _size; @@ -70,8 +73,11 @@ class HashTableBase : public IKeyValueStore < Key, Value >{ const float DENSITY_MAX; const float DENSITY_MIN; // compute hashCode modulo table size + inline int modSize(const unsigned int k) const{ + return k & ((1 << _logSize) - 1); + } inline int index(const Key& k) const{ - return hash(k) & ((1 << _logSize) - 1); // hash(k) % _size + return modSize(hash(k)); // hash(k) % _size } BucketType& bucket(const Key& k) const{ return table[index(k)]; @@ -81,15 +87,60 @@ class HashTableBase : public IKeyValueStore < Key, Value >{ memset(*pTable, 0, numElems * sizeof(BucketType)); } inline float density(){ return ((float) _population) / _size; } + // true if the specified population would be too dense for current table + inline float needGrow(const int pop) const { return (((float)pop) / _size) >= DENSITY_MAX; } + // true if the specified population would be too sparse for current table + inline float needShrink(const int pop) const { return (((float)pop) / _size) <= DENSITY_MIN; } + + virtual void copy(BucketType* oldTable, const unsigned int oldSize) = 0; + // grows/shrinks by specified logFactor + // that is, newSize is either oldSize << logFactor (grow) + // or oldSize >> logFactor (shrink) + virtual void resize(int growOrShrink, unsigned int logFactor = 1) { + unsigned int oldLogSize = _logSize; + unsigned int oldSize = _size; + unsigned int oldPopulation = _population; + BucketType* oldTable = table; + unsigned int newLogSize, newSize; + + if (growOrShrink == RESIZE_GROW){ + newLogSize = _logSize + logFactor; + newSize = _size << logFactor; + } + else if (growOrShrink == RESIZE_SHRINK){ + newLogSize = _logSize - logFactor; + newSize = _size >> logFactor; + } + else{ + // do nothing + return; + } + + // great; now we either grow or shrink + _logSize = newLogSize; + _size = newSize; + _population = 0; + table = new BucketType[newSize]; // twice the current size + initTable(&table, newSize); // initialize with zero + // copy old elements + copy(oldTable, oldSize); + // now delete oldTable + DELETE_ARR_SAFE(oldTable); + } // method resize public: // the type of the entries in the hash table HashTableBase():HashTableBase(INIT_LOGSIZE){} HashTableBase(unsigned int logSize = INIT_LOGSIZE) : _logSize(logSize), _size(1 << logSize), _population(0), DENSITY_MAX(0.75), DENSITY_MIN(0.25){ + assert(_logSize > 0); table = new BucketType[_size]; initTable(&table, _size); } virtual ~HashTableBase(){ DELETE_ARR_SAFE(table); _size = _population = _logSize = 0; } + inline unsigned int size(){ return _size; } + inline unsigned int population(){ return _population; } + + string toString(){ stringstream buf; for (int i = 0; i < _size; i++){ @@ -159,6 +210,24 @@ class SeparateChainingHashTable : public HashTableBase< } return false; } + virtual void copy(BucketType* oldTable, const unsigned int oldSize) override { + //BucketType* oldTable = (BucketType*)prevTable; + // copy old elements + for (unsigned int i = 0; i < oldSize; i++){ + BucketType pList; + if (pList = oldTable[i]){ // assigning oldTable[i] to pList + // bucket exists; copy elements + for (List::iterator n = pList->begin(); n != pList->end(); n++){ + Pair p = *n; + put(p.key, p.value); + } + // now delete bucket + pList->clear(); + DELETE_SAFE(oldTable[i]); + } + } + + } public: typedef Key KeyType; typedef Value ValueType; @@ -246,62 +315,259 @@ class SeparateChainingHashTable : public HashTableBase< return false; } - int size(){ return _size; } - int population(){ return _population; } +}; - // grows/shrinks by specified logFactor - // that is, newSize is either oldSize << logFactor (grow) - // or oldSize >> logFactor (shrink) - void resize(int growOrShrink, unsigned int logFactor = 1) { - unsigned int oldLogSize = _logSize; - unsigned int oldSize = _size; - unsigned int oldPopulation = _population; - BucketType* oldTable = table; - unsigned int newLogSize, newSize; +template< + class Key, + class Value = Key, + class HashFunction = IntHashFunction, + class Pred = KVPairEqualityPredicate +> +class LinearProbingHashTable : public HashTableBase < Key, Value, KVPair*, HashFunction > { + ///////////////// enable testing /////////////////// + friend class LinearProbingHashTableTest; +protected: + typedef KVPair Pair; +public: + typedef Pair* BucketType; + typedef Key KeyType; + typedef Value ValueType; +protected: + // flag about the usage of the first [0] slot + bool _zeroUsed; // true if an entry with key=0 exists + Value _zeroValue; + Pair _zeroPair; // the entry that was mapped to zero index - if (growOrShrink == RESIZE_GROW){ - newLogSize = _logSize + logFactor; - newSize = _size << logFactor; + inline unsigned int circularNext(const int index) const{ + return modSize(index + 1); + } + inline unsigned int circularPrev(const int index) const{ + return modSize(index - 1); + } + + + void insert(const Key& k, const Value& v){ + Pair* pair = insertKey(k); + if (pair){ + pair->value = v; } - else if (growOrShrink == RESIZE_SHRINK){ - newLogSize = _logSize - logFactor; - newSize = _size >> logFactor; + else{ + // something is wrong, insertKey() failed + } + } + + // the key must be present in the table + + Pair* lookupKey(const Key& k) const { + // test the zero key + if (k == 0){ + if (_zeroUsed) return const_cast(& _zeroPair); + else return NULL; } else{ - // do nothing - return; + // non-zero key + Pred keyEquals; + unsigned int firstBucket = index(k); + int cur = firstBucket; + do{ + Pair* pEntry = table[cur]; + + if (pEntry == NULL){ + // this slot must be empty + // because we started from the firstBucket, + // the key is not present in the table + return NULL; + } + else{ + // this slot is occupied; check key + if (keyEquals(k, pEntry->key)){ + // found match + return pEntry; + } + else{ + // move on to the next slot + cur = modSize(cur + 1); + } + } // + } while (cur != firstBucket); + // we checked all slots of the table; no match + return NULL; } + } - // great; now we either grow or shrink - _logSize = newLogSize; - _size = newSize; - _population = 0; - table = new BucketType[newSize]; // twice the current size - initTable(&table, newSize); // initialize with zero + Pair* insertKey(const Key& k) { - // copy old elements + // insert/update the entry with key 0 + if (((int)k) == 0){ + // we will use a special slot for this entry + if (_zeroUsed == false) + _zeroUsed = true; + _zeroPair.key = k; + _population++; + // see if we need to size up + if (needGrow(_population )) + grow(); + return &_zeroPair; + } + else{ + // key is non-zero + Pred keyEquals; + + // try all cells in the table, starting from the first (hashed) bucket + // if all cells are occupied, grow the table and keep trying + while (true){ + unsigned int firstBucket = index(k); + int cur = firstBucket; + do{ + Pair* pEntry = table[cur]; + + if (pEntry == NULL){ + // this slot must be empty, + // insert here + pEntry->key = k; + _population++; + // see if we need to size up + if (needGrow(_population)){ + grow(); + } + return pEntry; + } + else{ + // this slot is occupied; check key + if (keyEquals(k, pEntry->key)){ + // found match + return pEntry; + } + else{ + // move on to the next slot + cur = modSize(cur + 1); + } + } // + } while (cur != firstBucket); + // we checked all slots of the table; no match + // try again after resizing + grow(); + } + return NULL; + } + } + + virtual void copy(BucketType* oldTable, const unsigned int oldSize) override { + // the zeroPair stays intact because it is determined by key, not hash value + // copy table elements + //BucketType* oldTable = (BucketType*)prevTable; for (unsigned int i = 0; i < oldSize; i++){ - BucketType pList; - if (pList = oldTable[i]){ - // bucket exists; copy elements - for (List::iterator n = pList->begin(); n != pList->end(); n++){ - Pair p = *n; - put(p.key, p.value); - } - // now delete bucket - pList->clear(); + Pair* p = oldTable[i]; + if (p){ + insert(p->key, p->value); DELETE_SAFE(oldTable[i]); } } - // now delete oldTable - DELETE_ARR_SAFE(oldTable); } - // doubles the size and copy existing items - void grow() override { resize(RESIZE_GROW); } - // halves the size and copy existing items - void shrink() override { resize(RESIZE_SHRINK); } + +public: + typedef Pair* BucketType; + LinearProbingHashTable(unsigned int logSize = INIT_LOGSIZE) : _zeroUsed(false), HashTableBase(logSize){ + + } + virtual ~LinearProbingHashTable(){ + if (table){ + for (unsigned int i = 0; i < _size; i++){ + DELETE_SAFE(table[i]); + } + } + _zeroUsed = false; + } + virtual Value* get(const Key& key) const override { + Pair* kv = lookupKey(key); + if (kv) + return & (kv->value); + else + return NULL; + } + + virtual void put(const Key& key, const Value& value) override { return insert(key, value); } + virtual bool contains(const Key& key) const override { return lookupKey(key) == NULL; } + virtual void remove(const Key& k) override { + // zero key + if (k == 0){ + if (_zeroUsed) { + _zeroUsed = false; + _population--; + if (needShrink(_population)) + shrink(); + } + } + else{ + // non-zero key + Pred keyEquals; + unsigned int firstBucket = index(k); + int cur = firstBucket; + do{ + Pair* pEntry = table[cur]; + + if (pEntry == NULL){ + // this slot must be empty + // because we started from the firstBucket, + // the key is not present in the table + return; + } + else{ + // this slot is occupied; check key + if (keyEquals(k, pEntry->key)){ + // remove + DELETE_SAFE(table[cur]); + _population--; + if (needShrink(_population)) + shrink(); + else{ + // shuffle the entries from right to left until an empty slot is found + // (there must be one because we just deleted one) + // this will fix all other's linear probing sequence + const unsigned int startBucket = cur; + unsigned int neighbor = circularNext(cur); + while (neighbor != startBucket && + table[neighbor] != NULL){ + table[cur] = table[neighbor]; + + cur = neighbor; + neighbor = circularNext(cur); + } + table[cur] = NULL; + } + // done + return; + } + else{ + // key didn't match + // move on to the next slot + cur = modSize(cur + 1); + } + } // + } while (cur != firstBucket); + // we checked all slots of the table; no key match + // cannot remove; done + } + return; + } + +}; + +class LinearProbingHashTableTest : public yasi::Test{ +protected: + typedef LinearProbingHashTable IntHashTable; + typedef KVPair Pair; + typedef IntHashTable::BucketType BucketType; + + IntHashTable h; +public: + void all(){ + ASSERT_EQ(0, 1) << "incomplete test"; + } }; +ADD_TEST_F(LinearProbingHashTableTest, all); + class SeparateChainingHashTableTest : public yasi::Test{ protected: typedef SeparateChainingHashTable IntHashTable;