diff --git a/YASI_12/YASI_12.vcxproj b/YASI_12/YASI_12.vcxproj
index 514c1ee..89b6a9c 100644
--- a/YASI_12/YASI_12.vcxproj
+++ b/YASI_12/YASI_12.vcxproj
@@ -84,15 +84,17 @@
-
+
false
+
+
diff --git a/YASI_12/YASI_12.vcxproj.filters b/YASI_12/YASI_12.vcxproj.filters
index 7196712..03aea84 100644
--- a/YASI_12/YASI_12.vcxproj.filters
+++ b/YASI_12/YASI_12.vcxproj.filters
@@ -16,6 +16,21 @@
{e771e66c-6407-4d3a-bd6f-97ad63c60a2d}
+
+ {dd3f1d18-aa29-4dd2-b39f-3fee286fb955}
+
+
+ {c53f92c4-0949-4c57-bebb-d65e28421697}
+
+
+ {7c28c024-2e52-4642-b5b0-dd1332b5e474}
+
+
+ {3513b12f-c68b-43a7-8531-76ebdb5b60f7}
+
+
+ {d2827f3c-736f-4f2a-891c-9fa2883e5685}
+
@@ -29,77 +44,83 @@
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
+
+ Header Files\nonproject
-
- Header Files
+
+ Header Files\List
- Header Files
+ Header Files\List
-
- Header Files
+
+ Header Files\Tree
+
+
+ Header Files\Tree
- Header Files
+ Header Files\Tree
-
- Header Files
+
+ Header Files\Tree
-
- Header Files
+
+ Header Files\Tree
+
+
+ Header Files\Heap
- Header Files
+ Header Files\Heap
-
- Header Files
+
+ Header Files\List
-
- Header Files\nonproject
+
+ Header Files\Dictionary
-
- Header Files
+
+ Header Files\Dictionary
-
- Header Files
+
+ Header Files\Common
-
- Header Files
+
+ Header Files\Common
- Header Files
+ Header Files\Common
-
- Header Files
+
+ Header Files\Common
+
+
+ Header Files\Common
- Header Files
+ Header Files\Common
-
- Header Files
+
+ Header Files\Common
+
+
+ Header Files\Common
- Header Files
+ Header Files\Common
+
+
+ Header Files\nonproject
-
- Header Files
+
+ Header Files\Dictionary
-
- Header Files
+
+ Header Files\Dictionary
+
+
+ Header Files\Dictionary
\ No newline at end of file
diff --git a/YASI_12/ds.LinearProbingHashTable.h b/YASI_12/ds.LinearProbingHashTable.h
new file mode 100644
index 0000000..a4ed956
--- /dev/null
+++ b/YASI_12/ds.LinearProbingHashTable.h
@@ -0,0 +1,857 @@
+#pragma once
+#include "common.h"
+#include "ds.hashtablebase.h"
+using namespace std;
+
+namespace yasi{
+namespace ds{
+
+ template<
+ class Key,
+ class Value = Key,
+ class EntryType = KeyOnly, // must have a public member `key'
+ class HashFunction = IntHashFunction,
+ class Pred = KVPairEqualityPredicate
+ >
+ class LinearProbingHashTable : public HashTableBase <
+ Key,
+ Value,
+ EntryType*,
+ HashFunction > {
+ ///////////////// enable testing ///////////////////
+ friend class LinearProbingHashTableTest;
+ public:
+ //typedef KVPair Pair;
+ typedef EntryType Pair; // technically, this pair may not have a value
+ 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
+ char* _pZeroKey; // all zeros up to #bytes of a Key; used to check if a Key is zero
+
+ inline unsigned int circularNext(const int index) const{
+ return modSize(index + 1);
+ }
+ inline unsigned int circularPrev(const int index) const{
+ return modSize(index - 1);
+ }
+ inline bool isKeyZero(const Key* pKey) const{
+ return memcmp(pKey, _pZeroKey, sizeof(Key)) == 0;
+ }
+
+ // if a key is already there, it is updated
+ void insert(const Key& k, const Value& v){
+ Pair* pair = insertKey(k);
+ if (pair && pair->value != v){
+ pair->value = v;
+ }
+ 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{
+ // 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;
+ }
+ }
+
+ Pair* insertKey(const Key& k) {
+
+ // insert/update the entry with hashcode 0
+ if ( ((int)k) == 0){
+ // key is zero
+ // 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){
+ // if this is the zero slot, we should skip it
+ // this slot must be empty,
+ // we can insert here
+
+ // see if we need to size up after this insertion
+ if (needGrow(_population+1)){
+ // current bucket is not appropriate anymore under new size
+ // exit the do{} loop, after which we grow and try again
+ continue;
+ }
+ else{
+ pEntry = table[cur] = new Pair(k);
+ _population++;
+ 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 copyTable(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;
+ _population = _zeroUsed ? 1 : 0;
+
+ for (unsigned int i = 0; i < oldSize; i++){
+ Pair* p = oldTable[i];
+ if (p){
+ insert(p->key, p->value);
+ DELETE_SAFE(oldTable[i]);
+ }
+ }
+ // count the zero element
+
+ }
+
+ // forecefully provide a table (and associated state values)
+ // for test purpose only
+ void forceTable(BucketType* newTable, const unsigned int newLogSize, const unsigned int newPopulation, HashFunction& newHashFunction){
+ clear();
+ table = newTable;
+ _logSize = newLogSize;
+ _size = 1 << _logSize;
+ _population = newPopulation;
+ hash = newHashFunction;
+ }
+ void clear(){
+ if (table){
+ for (unsigned int i = 0; i < _size; i++){
+ DELETE_SAFE(table[i]);
+ }
+ }
+ DELETE_ARR_SAFE(table);
+ _zeroUsed = false;
+ _size = _population = _logSize = 0;
+ }
+ public:
+
+ LinearProbingHashTable(unsigned int logSize = INIT_LOGSIZE) : _zeroUsed(false), _zeroPair(0,0), HashTableBase(logSize){
+ _pZeroKey = new char[sizeof(Key)];
+ memset(_pZeroKey, 0, sizeof(Key));
+ }
+ virtual ~LinearProbingHashTable(){
+ clear();
+ DELETE_SAFE(_pZeroKey);
+ }
+ 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;
+ _zeroPair.value = 0;
+ _population--;
+ if (needShrink(_population))
+ shrink();
+ }
+ }
+ else{
+ // non-zero key
+ Pred keyEquals;
+ unsigned int curFirstBucket = index(k);
+ int cur = curFirstBucket;
+ const int searchStart = curFirstBucket; // remember our first posti
+ 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;
+ //bool crossedBoundary = false; // search crossed the table end and now at the beginning of the table due to mod operation
+ unsigned int neighbor = circularNext(cur);
+ while (neighbor != searchStart && // we have not checked all buckets
+ table[neighbor] != NULL )// there is an entry at the neighboring bucket and
+ {
+ //if (!crossedBoundary && neighbor < cur) {
+ // // our search just wrapped across the table boundary
+ // crossedBoundary = true;
+ //}
+
+ unsigned int neighborFirstBucket = index(table[neighbor]->key);
+ if (neighborFirstBucket == neighbor || // is the neighbor at its own first bucket? then it should not move
+ (
+ (curFirstBucket <= cur) // search did not wrap around the end
+ ? neighborFirstBucket > cur // skip if neighbor's first bucket to the right of cur
+ : neighborFirstBucket < cur // skip if neighbor's first bucket to the left of cur
+ )
+ )
+ {
+ // yes; skip it
+ neighbor = circularNext(neighbor);
+ }
+ else{
+ // the (possibly distant) neighbor is not at its first bucket
+
+ // move it to the left
+ table[cur] = table[neighbor];
+ table[neighbor] = NULL;
+ // prepare for the next hop
+ cur = neighbor;
+ neighbor = circularNext(neighbor);
+ curFirstBucket = neighborFirstBucket;
+ }
+ }
+ }
+ // done
+ return;
+ }
+ else{
+ // key didn't match
+ // move on to the next slot
+ cur = modSize(cur + 1);
+ }
+ } // table[cur] != NULL; go to next iteration of while loop
+ } while (cur != searchStart);
+ // we checked all slots of the table; no key match
+ // cannot remove; done
+ }
+ return;
+ }
+
+ };
+
+ class LinearProbingHashTableTest : public yasi::Test{
+ protected:
+
+ template
+ class IdentityFunction : public IHashFunction{
+ public:
+ virtual int operator()(Key n) const override{
+ return n;
+ }
+ };
+ template
+ class Mod8Function : public IHashFunction{
+ public:
+ static const unsigned int modulus = 8;
+ virtual int operator()(Key n) const override{
+ return n % 8;
+ }
+ };
+ template
+ class Mod16Function : public IHashFunction{
+ public:
+ static const unsigned int modulus = 16;
+ virtual int operator()(Key n) const override{
+ return n % 16;
+ }
+ };
+ template
+ class Mod32Function : public IHashFunction{
+ public:
+ static const unsigned int modulus = 32;
+ virtual int operator()(Key n) const override{
+ return n % 32;
+ }
+ };
+ // mod 16 by default
+ template
+ class ModFunction : public IHashFunction{
+ public:
+ unsigned int modulus;
+ ModFunction() : modulus(16){}
+ virtual int operator()(Key n) const override{
+ return n % modulus;
+ }
+ };
+ public:
+ void insert(){
+ typedef LinearProbingHashTable, Mod16Function > IntHashTable;
+ typedef IntHashTable::BucketType BucketType;
+ typedef IntHashTable::Pair Pair;
+
+ IntHashTable h(4);
+ ASSERT_EQ(16, h.size()) << "size not 16";
+ ASSERT_EQ(0, h.population()) << "pop not 0";
+
+ //int newLogSize = 4; // 16 entries
+ //int newSize = 1 << newLogSize;
+ //int newPopulation = 0;
+ //BucketType* newTable = new BucketType[newSize];
+ //memset(newTable, 0, sizeof(BucketType*) * newSize);
+ //IdentityFunction newHashFunction; // maps n to n
+ //// put some entries into the new table
+ //newTable[4] = new Pair(4, 4); newPopulation++;
+ //newTable[5] = new Pair(5, 5); newPopulation++;
+ //newTable[6] = new Pair(6, 6); newPopulation++;
+ //// force these state into the hashtable
+ //h.forceTable(newTable, newLogSize, newPopulation, newHashFunction);
+
+ {
+ SCOPED_TRACE("insert 4-5-6");
+ h.insert(4, 4);
+ h.insert(5, 5);
+ h.insert(6, 6);
+ //// - - - - 4 5 6 - - - - - - - - -
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ cout << h;
+ ASSERT_EQ(3, h.population()) << "pop not 3";
+ }
+ {
+ string str = "insert 20, which should go to bucket 7";
+ int key = 20;
+ int bucket = 7;
+ SCOPED_TRACE(str);
+ Pair* p = h.insertKey(key);
+ p->value = key; // assign the value
+ cout << str << endl << h;
+ ASSERT_NE(0, (int)h.table[bucket]) << "bucket " << bucket << " NULL";
+ ASSERT_EQ(p, h.table[bucket]) << key << " not in bucket " << bucket;
+ ASSERT_EQ(key, h.table[bucket]->key) << key << " not in bucket " << bucket;
+ }
+ {
+ string str = "insert 23, which should go to bucket 8";
+ int key = 23;
+ int bucket = 8;
+ SCOPED_TRACE(str);
+ Pair* p = h.insertKey(key);
+ p->value = key; // assign the value
+ cout << str << endl << h;
+ ASSERT_NE(0, (int)h.table[bucket]) << "bucket " << bucket << " NULL";
+ ASSERT_EQ(p, h.table[bucket]) << key << " not in bucket " << bucket;
+ ASSERT_EQ(key, h.table[bucket]->key) << key << " not in bucket " << bucket;
+ }
+ {
+ string str = "insert 20, which is already in bucket 7";
+ SCOPED_TRACE(str);
+ int key = 20;
+ int bucket = 7;
+ SCOPED_TRACE(str);
+ Pair* p = h.insertKey(key);
+ cout << str << endl << h;
+ ASSERT_NE(0, (int)h.table[bucket]) << "bucket " << bucket << " NULL";
+ ASSERT_EQ(p, h.table[bucket]) << key << " not in bucket " << bucket;
+ ASSERT_EQ(key, h.table[bucket]->key) << key << " not in bucket " << bucket;
+ ASSERT_EQ(key, h.table[bucket]->value) << key << " not in bucket " << bucket;
+ }
+ {
+ h.insert(13, 13);
+ h.insert(14, 14);
+ h.insert(15, 15);
+ //
+ string str = "search round the table; now insert 16, should go to bucket 0";
+ int key = 16;
+ int bucket = 0;
+ SCOPED_TRACE(str);
+ Pair* p = h.insertKey(key);
+ p->value = key; // assign the value
+ cout << str << endl << h;
+ ASSERT_NE(0, (int)h.table[bucket]) << "bucket " << bucket << " NULL";
+ ASSERT_EQ(p, h.table[bucket]) << key << " not in bucket " << bucket;
+ ASSERT_EQ(key, h.table[bucket]->key) << key << " not in bucket " << bucket;
+
+ }
+ {
+ string str = "search round the table; now insert 29, should go to bucket 1";
+ int key = 29;
+ int bucket = 1;
+ SCOPED_TRACE(str);
+ Pair* p = h.insertKey(key);
+ p->value = key; // assign the value
+ cout << str << endl << h;
+ ASSERT_NE(0, (int)h.table[bucket]) << "bucket " << bucket << " NULL";
+ ASSERT_EQ(p, h.table[bucket]) << key << " not in bucket " << bucket;
+ ASSERT_EQ(key, h.table[bucket]->key) << key << " not in bucket " << bucket;
+
+ }
+ {
+ string str = "insert 0; should go to the zero element";
+ int key = 0;
+ SCOPED_TRACE(str);
+ ASSERT_EQ(false, h._zeroUsed) << "_zeroUsed true";
+ int prevPop = h.population();
+ Pair* p = h.insertKey(key);
+ p->value = 100; // assign the value
+ cout << str << endl << h;
+ ASSERT_EQ(true, h._zeroUsed) << "_zeroUsed false";
+ ASSERT_EQ(p, &h._zeroPair) << " zero key not in zeroPair";
+ ASSERT_EQ(prevPop+1, h.population()) << "population did not increase";
+ ASSERT_EQ(key, h._zeroPair.key) << key << " not in zeroPair";
+ }
+
+ }
+
+ void copyTable(){
+ typedef LinearProbingHashTable, ModFunction > IntHashTable;
+ typedef IntHashTable::BucketType BucketType;
+ typedef IntHashTable::Pair Pair;
+
+ IntHashTable h1(4), h2(5);
+ h1.hash.modulus = 16;
+ h2.hash.modulus = 32;
+
+ ASSERT_EQ(16, h1.size()) << "h1 size not 16";
+ ASSERT_EQ(0, h1.population()) << "h1 pop not 0";
+ ASSERT_EQ(32, h2.size()) << "h2 size not 32";
+ ASSERT_EQ(0, h2.population()) << "h2 pop not 0";
+
+ int h1MaxItemsWithoutGrow = (int)(h1.size() * h1.DENSITY_MAX) - 1;
+ // add some items to h1 without triggering grow()
+ int* pKeys = new int[h1MaxItemsWithoutGrow];
+ srand((unsigned int)time(NULL));
+ for (int i = 0; i < h1MaxItemsWithoutGrow; i++){
+ pKeys[i] = rand();
+ h1.insert(pKeys[i], pKeys[i]);
+ }
+ ASSERT_EQ(16, h1.size()) << "h1 size not 16";
+ ASSERT_EQ(h1MaxItemsWithoutGrow, h1.population()) << "h1 pop not " << h1MaxItemsWithoutGrow;
+
+ // now copy items from h1 to h2
+ h2.copyTable(h1.table, h1.size());
+
+ // check that all items exist
+ // items are rehashed mod 32
+ for (int i = 0; i < h1._size; i++){
+ if (h1.table[i]){
+ int key = h1.table[i]->key;
+ int value = h1.table[i]->value;
+ Pair* p = h2.lookupKey(key);
+ ASSERT_NE(0, (int)p) << "key " << key << " not found in h2";
+ ASSERT_EQ(key, p->key) << "key " << key << " mismatch in h2 key " << p->key;
+ ASSERT_EQ(value, p->value) << "value " << value << " mismatch in h2 key " << p->key << "value: " << p->value;
+ }
+ }
+
+ DELETE_ARR_SAFE(pKeys);
+ }
+
+ void remove(){
+ typedef LinearProbingHashTable, Mod16Function > IntHashTable;
+ typedef LinearProbingHashTable, Mod8Function > IntHashTable8;
+ typedef IntHashTable::BucketType BucketType;
+ typedef IntHashTable::Pair Pair;
+
+ IntHashTable h(4);
+ ASSERT_EQ(16, h.size()) << "size not 16";
+ ASSERT_EQ(0, h.population()) << "pop not 0";
+
+ {
+ SCOPED_TRACE("insert 4-5-6-7");
+ h.insert(4, 4);
+ h.insert(5, 5);
+ h.insert(6, 6);
+ h.insert(7, 7);
+ // add 12, and three more that map into 14
+ h.insert(12, 12);
+ h.insert(14, 14);
+ h.insert(30, 30);
+ h.insert(46, 46);
+ //// 46 - - - 4 5 6 7 - - - - 12 - 14 30
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ cout << h;
+ ASSERT_EQ(16, h.size()) << "size not 16";
+ ASSERT_EQ(8, h.population()) << "population not 8";
+
+ }
+ {
+ SCOPED_TRACE("remove 4");
+ h.remove(4);
+ //// 46 - - - - 5 6 7 - - - - 12 - 14 30
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ cout << h;
+ ASSERT_EQ(16, h.size()) << "size not 16";
+ ASSERT_EQ(NULL, (int)h.lookupKey(4)) << "key " << 4 << " not NULL";
+ ASSERT_EQ(5, h.table[5]->key) << "[5] not " << 5;
+ ASSERT_EQ(6, h.table[6]->key) << "[6] not " << 6;
+ ASSERT_EQ(7, h.table[7]->key) << "[7] not " << 7;
+ ASSERT_EQ(7, h.population()) << "population not 7";
+ }
+ {
+ SCOPED_TRACE("remove 6");
+ cout << "before removing 6\n" << h;
+ h.remove(6);
+ //// 46 - - - - 5 - 7 - - - - 12 - 14 30
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ ASSERT_EQ(16, h.size()) << "size not 16";
+ ASSERT_EQ(6, h.population()) << "population not 6";
+ cout << "after removing 6\n" << h;
+ ASSERT_EQ(NULL, (int)h.lookupKey(6)) << "key " << 6 << " not NULL";
+ ASSERT_EQ(5, h.table[5]->key) << "key " << 5 << " misplaced";
+ ASSERT_EQ(7, h.table[7]->key) << "key " << 7 << " misplaced";
+ }
+ {
+ SCOPED_TRACE("remove simple 2-item bucket");
+ h.insert(85, 85);
+ //// 46 - - - - 5 85 7 - - - - 12 - 14 30
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ ASSERT_EQ(7, h.population()) << "population not 7";
+ ASSERT_EQ(85, h.table[6]->key) << "[6] not 85";
+ ASSERT_EQ(16, h.size()) << "size not 16";
+ h.remove(5);
+ //// 46 - - - - 85 - 7 - - - - 12 - 14 30
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ ASSERT_EQ(NULL, (int)h.lookupKey(5)) << "key " << 5 << " not NULL";
+ ASSERT_EQ(85, h.table[5]->key) << "key " << 85 << " misplaced";
+ ASSERT_EQ(NULL, (int)h.table[6]) << "[6] not NULL";
+ ASSERT_EQ(7, h.table[7]->key) << "key " << 7 << " misplaced";
+
+ // put 5 back
+ h.insert(5,5);
+ //// 46 - - - - 85 5 7 - - - - 12 - 14 30
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ ASSERT_EQ(5, h.table[6]->key) << "key " << 5 << " misplaced";
+
+ // delete 5
+ h.remove(5);
+ //// 46 - - - - 85 - 7 - - - - 12 - 14 30
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ ASSERT_EQ(16, h.size()) << "size not 16";
+ ASSERT_EQ(NULL, (int)h.lookupKey(5)) << "key " << 5 << " not NULL";
+ ASSERT_EQ(85, h.table[5]->key) << "key " << 85 << " misplaced";
+ ASSERT_EQ(NULL, (int)h.table[6]) << "[6] not NULL";
+ ASSERT_EQ(7, h.table[7]->key) << "key " << 7 << " misplaced";
+
+ // for consistency later on, replace 85 with 5
+ h.remove(85);
+ h.insert(5, 5);
+ //// 46 - - - - 5 - 7 - - - - 12 - 14 30
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ ASSERT_EQ(16, h.size()) << "size not 16";
+ ASSERT_EQ(5, h.table[5]->key) << "[5] not 5";
+ ASSERT_EQ(NULL, (int)h.table[6]) << "[6] not NULL";
+ ASSERT_EQ(7, h.table[7]->key) << "key " << 7 << " misplaced";
+ }
+
+ // add several entries that map into bucket 5
+ h.insert(21, 21);
+ h.insert(37, 37);
+ h.insert(53, 53);
+ //// 46 - - - - 5 21 7 37 53 - - 12 - 14 30
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+
+ // add several entries that map into bucket 7
+ h.insert(23, 23);
+ h.insert(39, 39);
+ //// 46 - - - - 5 21 7 37 53 23 39 12 - 14 30
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+
+ {
+ SCOPED_TRACE("remove key from multi-item bucket");
+
+ {
+ SCOPED_TRACE("remove 5");
+ //// 46 - - - - 5 21 7 37 53 23 39 12 - 14 30
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ // delete 5
+ cout << "before removing 5:\n" << h << endl;
+ h.remove(5);
+ //// 46 - - - - 21 37 7 53 23 39 - 12 - 14 30
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ cout << "after removing 5:\n" << h << endl;
+ ASSERT_EQ(16, h.size()) << "size not 16";
+ ASSERT_EQ(NULL, (int)h.lookupKey(5)) << "key " << 5 << " not NULL";
+ ASSERT_EQ(21, h.table[5]->key) << "[5] not 21";
+ ASSERT_EQ(37, h.table[6]->key) << "[6] not 37";
+ ASSERT_EQ(7, h.table[7]->key) << "[7] not 7";
+ ASSERT_EQ(53, h.table[8]->key) << "[8] not 53";
+ ASSERT_EQ(23, h.table[9]->key) << "[9] not 23";
+ ASSERT_EQ(39, h.table[10]->key) << "[10] not 39";
+ ASSERT_EQ(NULL, (int)h.table[11]) << "[11] not NULL";
+ ASSERT_EQ(12, h.table[12]->key) << "[12] not 12";
+ ASSERT_EQ(NULL, (int)h.table[13]) << "[13] not NULL";
+ ASSERT_EQ(14, h.table[14]->key) << "[14] not 14";
+ }
+ {
+ SCOPED_TRACE("remove 21");
+ //delete 21
+ h.remove(21);
+ //// 46 - - - - 37 53 7 23 39 - - 12 - 14 30
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ ASSERT_EQ(16, h.size()) << "size not 16";
+ ASSERT_EQ(NULL, (int)h.lookupKey(21)) << "key " << 21 << " not NULL";
+ ASSERT_EQ(37, h.table[5]->key) << "[5] not 37";
+ ASSERT_EQ(53, h.table[6]->key) << "[6] not 53";
+ ASSERT_EQ(7, h.table[7]->key) << "[7] not 7";
+ ASSERT_EQ(23, h.table[8]->key) << "[8] not 23";
+ ASSERT_EQ(39, h.table[9]->key) << "[9] not 39";
+ ASSERT_EQ(NULL, (int)h.table[10]) << "[10] not NULL";
+ ASSERT_EQ(NULL, (int)h.table[11]) << "[11] not NULL";
+ ASSERT_EQ(12, h.table[12]->key) << "[12] not 12";
+ ASSERT_EQ(NULL, (int)h.table[13]) << "[13] not NULL";
+ ASSERT_EQ(14, h.table[14]->key) << "[14] not 14";
+ }
+
+ {
+ SCOPED_TRACE("remove 23");
+ //delete 23
+ h.remove(23);
+ //// 46 - - - - 37 53 7 39 - - - 12 - 14 30
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ ASSERT_EQ(16, h.size()) << "size not 16";
+ ASSERT_EQ(NULL, (int)h.lookupKey(23)) << "key " << 23 << " not NULL";
+ ASSERT_EQ(37, h.table[5]->key) << "[5] not 37";
+ ASSERT_EQ(53, h.table[6]->key) << "[6] not 53";
+ ASSERT_EQ(7, h.table[7]->key) << "[7] not 7";
+ ASSERT_EQ(39, h.table[8]->key) << "[8] not 39";
+ ASSERT_EQ(NULL, (int)h.table[9]) << "[9] not NULL";
+ ASSERT_EQ(NULL, (int)h.table[10]) << "[10] not NULL";
+ ASSERT_EQ(NULL, (int)h.table[11]) << "[11] not NULL";
+ ASSERT_EQ(12, h.table[12]->key) << "[12] not 12";
+ ASSERT_EQ(NULL, (int)h.table[13]) << "[13] not NULL";
+ ASSERT_EQ(14, h.table[14]->key) << "[14] not 14";
+ }
+ h.insert(62, 62);
+ h.insert(17, 17);
+ //// 46 62 17 - - 37 53 39 - - - - 12 - 14 30
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ ASSERT_EQ(62, h.table[1]->key) << "[1] not 62";
+
+ {
+ SCOPED_TRACE("remove 46");
+ //delete 46
+ cout << "before removing 46:\n" << h << endl;
+ h.remove(46);
+ //// 62 17 - - - 37 53 39 - - - - 12 - 14 30
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ cout << "after removing 46:\n" << h << endl;
+ ASSERT_EQ(16, h.size()) << "size not 16";
+ ASSERT_EQ(NULL, (int)h.lookupKey(46)) << "key " << 46 << " not NULL";
+ ASSERT_EQ(62, h.table[0]->key) << "[0] not 62";
+ ASSERT_EQ(17, h.table[1]->key) << "[1] not 17";
+ ASSERT_EQ(NULL, (int)h.table[2]) << "[2] not NULL";
+ ASSERT_EQ(14, h.table[14]->key) << "[14] not 14";
+ ASSERT_EQ(30, h.table[15]->key) << "[15] not 30";
+ }
+
+ {
+ SCOPED_TRACE("remove 30");
+ //delete 30
+ h.remove(30);
+ //// - 17 - - - 37 53 39 - - - - 12 - 14 62
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ ASSERT_EQ(16, h.size()) << "size not 16";
+ ASSERT_EQ(NULL, (int)h.lookupKey(30)) << "key " << 30 << " not NULL";
+ ASSERT_EQ(17, h.table[1]->key) << "[1] not 17";
+ ASSERT_EQ(NULL, (int)h.table[0]) << "[0] not NULL";
+ ASSERT_EQ(14, h.table[14]->key) << "[14] not 14";
+ ASSERT_EQ(62, h.table[15]->key) << "[15] not 62";
+ }
+ {
+ SCOPED_TRACE("remove 14");
+ //delete 14
+ h.remove(14);
+ //// - - - - - 37 53 39 - - - - 12 - 62 -
+ //// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ ASSERT_EQ(16, h.size()) << "size not 16";
+ ASSERT_EQ(NULL, (int)h.lookupKey(14)) << "key " << 14 << " not NULL";
+ ASSERT_EQ(62, h.table[14]->key) << "[14] not 62";
+ ASSERT_EQ(NULL, (int)h.table[15]) << "[15] not NULL";
+ }
+
+ }
+ {
+ SCOPED_TRACE("Zero key");
+ ASSERT_EQ(false, h._zeroUsed) << "_zeroUsed true";
+ h.insert(0, 5);
+ ASSERT_EQ(true, h._zeroUsed) << "_zeroUsed false";
+ ASSERT_EQ(0, h._zeroPair.key) << "key of zero element non-zero";
+ ASSERT_EQ(5, h._zeroPair.value) << "key of zero element non-zero";
+
+ // now delete 0
+ h.remove(0);
+ ASSERT_EQ(false, h._zeroUsed) << "_zeroUsed true";
+ ASSERT_EQ(0, h._zeroPair.key) << "key of zero element non-zero";
+ ASSERT_EQ(0, h._zeroPair.value) << "value of zero element non-zero";
+ }
+ {
+ SCOPED_TRACE("remove from an all-filled table");
+ // create a pathological table
+ Pair** badTable = new Pair*[8]; // 8 entries in the table
+ for (int i = 0; i < 8; i++){
+ badTable[i] = new Pair(5, i); // all keys are 5
+ }
+ IntHashTable8 h2(3); // 8 elements to begin
+ h2.forceTable(badTable, 3, 8, h2.hash); // make the table full
+ ASSERT_EQ(8, h2.size()) << "size not 8";
+ ASSERT_EQ(8, h2.population()) << "population not 8";
+ ASSERT_EQ(8, h2.hash.modulus) << "has mod not 8";
+
+ {
+ SCOPED_TRACE("delete from index 5");
+ cout << "before removing 5:\n" << h2;
+ h2.remove(5);
+ cout << "after removing 5:\n" << h2;
+
+ ASSERT_EQ(7, h2.population()) << "population not 7";
+ for (int i = 0; i < 8; i++){
+ if (i == 4){
+ ASSERT_EQ(NULL, (int)h2.table[i]) << "[" << i << "] not NULL";
+ }
+ else
+ ASSERT_EQ(5, h2.table[i]->key) << "[" << i << "] not 5";
+ }
+ }
+
+ // cleanup
+ }
+
+ }
+
+ void growCondition(){
+ typedef LinearProbingHashTable, ModFunction > IntHashTable;
+ typedef IntHashTable::BucketType BucketType;
+ typedef IntHashTable::Pair Pair;
+
+ IntHashTable h1(4);
+ h1.hash.modulus = 16;
+
+ int i = 0;
+ {
+ SCOPED_TRACE("grow");
+ ASSERT_EQ(16, h1.size()) << "h1 size not 16";
+ ASSERT_EQ(0, h1.population()) << "h1 pop not " << 0;
+ int popLimit = h1.maxPopulationWithoutGrow();
+ for (; i < popLimit; i++){
+ h1.insert(i, i);
+ }
+ ASSERT_EQ(16, h1.size()) << "h1 size not 16";
+ ASSERT_EQ(popLimit, h1.population()) << "h1 pop not " << popLimit;
+ cout << h1;
+ // this should trigger grow
+ h1.insert(i, i);
+ i++;
+ ASSERT_EQ(32, h1.size()) << "h1 size not 32";
+ ASSERT_EQ(popLimit + 1, h1.population()) << "h1 pop not " << popLimit + 1;
+ }
+ }
+ void shrinkCondition(){
+ typedef LinearProbingHashTable, ModFunction > IntHashTable;
+ typedef IntHashTable::BucketType BucketType;
+ typedef IntHashTable::Pair Pair;
+
+ IntHashTable h1(4);
+ h1.hash.modulus = 16;
+
+ int i = 0;
+ {
+ SCOPED_TRACE("shrink");
+ // put as many items as we can without invoking grow()
+ int popLimit = h1.maxPopulationWithoutGrow();
+ for (; i < popLimit; i++){
+ h1.insert(i, i);
+ }
+ ASSERT_EQ(16, h1.size()) << "h1 size not 16";
+ ASSERT_EQ(popLimit, h1.population()) << "h1 pop not " << popLimit;
+
+ popLimit = h1.minPopulationWithoutShrink();
+ int removeCount = h1._population - popLimit;
+ int j = 0;
+ for (; j < removeCount; j++){
+ h1.remove(j);
+ }
+ ASSERT_EQ(16, h1.size()) << "h1 size not 16";
+ ASSERT_EQ(popLimit, h1.population()) << "h1 pop not " << popLimit;
+ // this should trigger shrink
+ h1.remove(j);
+ j--;
+ ASSERT_EQ(8, h1.size()) << "h1 size not " << 8;
+ ASSERT_EQ(popLimit - 1, h1.population()) << "h1 pop not " << popLimit - 1;
+ }
+ }
+
+ };
+
+ ADD_TEST_F(LinearProbingHashTableTest, insert);
+ ADD_TEST_F(LinearProbingHashTableTest, copyTable);
+ ADD_TEST_F(LinearProbingHashTableTest, growCondition);
+ ADD_TEST_F(LinearProbingHashTableTest, remove);
+ ADD_TEST_F(LinearProbingHashTableTest, shrinkCondition);
+
+}
+}
\ No newline at end of file
diff --git a/YASI_12/ds.SeparateChainingHashTable.h b/YASI_12/ds.SeparateChainingHashTable.h
new file mode 100644
index 0000000..3d234e1
--- /dev/null
+++ b/YASI_12/ds.SeparateChainingHashTable.h
@@ -0,0 +1,352 @@
+#pragma once
+#include "common.h"
+#include "ds.hashtablebase.h"
+
+using namespace std;
+
+namespace yasi{
+namespace ds{
+
+ // Separate chaining collision resolution strategy
+ // each bucket holds a list of values mapped into that bucket
+ template<
+ class Key,
+ class Value = Key,
+ class HashFunction = IntHashFunction,
+ class Pred = KVPairEqualityPredicate
+ >
+ class SeparateChainingHashTable : public HashTableBase<
+ Key,
+ Value,
+ DoublyLinkedList< KVPair >*,
+ HashFunction>
+ {
+ ///////////////// enable testing ///////////////////
+ friend class SeparateChainingHashTableTest;
+
+ protected:
+ typedef KVPair Pair;
+ typedef DoublyLinkedList < Pair > List;
+ typedef typename List::NodeType Node;
+ public:
+ typedef List* BucketType;
+ typedef List ListType;
+ protected:
+ // returns the pointer to the value if exists, otherwise returns NULL
+ Node* bucketNode(BucketType pList, const Key& k) const{
+ Pred keyEquals;
+ for (List::iterator n = pList->begin(); n != pList->end(); n++){
+ if (keyEquals((*n).key, k)){
+ // found node;
+ return n.pNode;
+ }
+ }
+ return NULL;
+ }
+ Pair* bucketEntry(BucketType pList, const Key& k) const{
+ Pred keyEquals;
+ for (List::iterator n = pList->begin(); n != pList->end(); n++){
+ if (keyEquals((*n).key, k)){
+ // found node;
+ return &(n.pNode->element);
+ }
+ }
+ return NULL;
+ }
+ bool bucketContains(BucketType pList, const Key& k) const{
+ Pred keyEquals;
+ for (List::iterator n = pList->begin(); n != pList->end(); n++){
+ if (keyEquals((*n).key, k)){
+ // found node;
+ return true;
+ }
+ }
+ return false;
+ }
+ virtual void copyTable(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;
+
+ virtual ~SeparateChainingHashTable(){
+ if (table){
+ for (int i = 0; i < _size; i++){
+ // each entry is either NULL or a List*
+ if (table[i]){
+ table[i]->clear();
+ DELETE_SAFE(table[i]);
+ }
+ }
+ }
+ }
+ SeparateChainingHashTable(unsigned int logSize = INIT_LOGSIZE) : HashTableBase(logSize){
+
+ //collisionHandler(this);
+ }
+ // returns true on success, false on failure
+ virtual void put(const Key& k, const Value& v) override{
+ int i = index(k);
+ BucketType pList = table[i];
+ if (pList == NULL){
+ // empty slot; create a new list
+ pList = table[i] = new List();
+ // pushFront for better temporal locality
+ pList->pushFront(Pair(k, v));
+ _population++;
+ }
+ else{
+ // existing bucket
+ Pair* pEntry = bucketEntry(pList, k);
+ if (pEntry){
+ // key already exists; update value
+ pEntry->value = v;
+ }
+ else{
+ pList->pushFront(Pair(k, v));
+ _population++;
+ }
+ }
+ // do we need to grow?
+ if (density() >= DENSITY_MAX) grow();
+ }
+ virtual void put(const Key& k) {
+ put(k, k);
+ }
+ virtual Value* get(const Key& k) const override{
+ int i = index(k);
+ BucketType pList = table[i];
+ if (pList != NULL){
+ // existing bucket
+ Pair* pEntry = bucketEntry(pList, k);
+ if (pEntry)
+ return &pEntry->value;
+ }
+ return NULL;
+ }
+ virtual void remove(const Key& k) override{
+ int i = index(k);
+ BucketType pList = table[i];
+ if (pList == NULL){
+ // the key is absent
+ // nothing to do
+ }
+ else{
+ // existing bucket
+ Node* pNode = bucketNode(pList, k);
+ if (pNode){
+ pList->remove(pNode);
+ _population--;
+ // do we need to shrink?
+ if (density() <= DENSITY_MIN) shrink();
+ }
+ }
+ }
+ virtual bool contains(const Key& k) const override{
+ int i = index(k);
+ BucketType pList = table[i];
+ if (pList != NULL){
+ // existing bucket
+ return bucketContains(pList, k);
+ }
+ return false;
+ }
+
+ };
+
+ class SeparateChainingHashTableTest : public yasi::Test{
+ protected:
+ typedef SeparateChainingHashTable IntHashTable;
+ typedef KVPair Pair;
+ typedef IntHashTable::BucketType BucketType;
+ typedef IntHashTable::ListType ListType;
+
+ IntHashTable h;
+ public:
+
+ void hashCode(){
+ // hashcode must be within range
+ srand((unsigned int)time(NULL));
+ for (int i = 0; i < 1000; i++){
+ int n = rand();
+ int index = h.index(n);
+ ASSERT_LT(index, h.size()) << "index(" << n << ") out of range";
+ ASSERT_GE(index, 0) << "index(" << n << ") out of range";
+ }
+ }
+
+ void all(){
+ srand((unsigned int)time(NULL));
+ int logSize = h._logSize;
+ int size = h._size;
+ ASSERT_EQ(IntHashTable::INIT_LOGSIZE, logSize) << "initial logSize not " << IntHashTable::INIT_LOGSIZE;
+ ASSERT_EQ(1 << logSize, h.size()) << "table size not 2^" << logSize;
+ for (int i = 0; i < h.size(); i++){
+ ASSERT_EQ(0, (int)h.table[i]) << "table[i] not NULL for i=" << i;
+ }
+
+ {
+ SCOPED_TRACE("add items in hashtable");
+ // now add 10 items; should not trigger grow()
+ ASSERT_LT(10 / h.size(), h.DENSITY_MAX) << "10/" << h.size() << " items triggered grow() while DENSITY_MAX=" << h.DENSITY_MAX;
+ for (int i = 0; i < 10; i++){
+ h.put(i, i);
+ }
+ // test contains()
+ for (int i = 0; i < 10; i++){
+ ASSERT_EQ(true, h.contains(i)) << "key " << i << " not found";
+ }
+ ASSERT_EQ(false, h.contains(100)) << "absent key 100 was found";
+ // test get()
+ for (int i = 0; i < 10; i++){
+ int* pValue = h.get(i);
+ ASSERT_NE(NULL, (int)pValue) << "pValue with key " << i << " NULL";
+ ASSERT_EQ(i, *pValue) << "value with key " << i << " mismatch";
+ }
+ ASSERT_EQ(NULL, h.get(100)) << "absent key 100 was found";
+ // test duplicate insert
+ // should result in update
+ h.put(5, -6);
+ int* pValue = h.get(5);
+ ASSERT_NE(NULL, (int)pValue) << "pValue with key " << 5 << " NULL";
+ ASSERT_EQ(-6, *pValue) << "value with key " << 5 << " not -6";
+
+ // now add some more but don't trigger grow()
+ size = h.size();
+ int maxCurrent = ((int)(h.size() * h.DENSITY_MAX)) - 1;
+ int currentPop = h.population();
+ for (int i = currentPop; i < maxCurrent; i++){
+ // this insertion should not trigger grow()
+ h.put(i, i);
+ }
+ ASSERT_EQ(maxCurrent, h.population()) << "population not maxCurrent";
+ ASSERT_EQ(size, h.size()) << "size() not size";
+ // this insertion should trigger grow()
+ int key = rand();
+ while (h.contains(key)){ key = rand(); }
+ h.put(key, key); // should trigger grow()
+ ASSERT_EQ(size * 2, h.size()) << "size() not 2*oldSize";
+ ASSERT_EQ(maxCurrent + 1, h.population()) << "population() not maxCurrent+1";
+ ASSERT_GE(0.375, h.density()) << "density() > 0.375";
+
+ // print the table
+ string str = h.toString();
+ cout << "after grow(): " << endl << str << endl;
+
+ // check that all old entries are still in the table
+ for (int i = 0; i < 10; i++){ // first 10 entries
+ int v = i;
+ if (i == 5){
+ // remember that we had updated an entry
+ v = -6;
+ }
+ ASSERT_EQ(true, h.contains(i)) << "key " << i << " not found in new table";
+ ASSERT_EQ(v, *h.get(i)) << "value with key " << i << " incorrect in new table";
+ }
+ for (int i = currentPop; i < maxCurrent; i++){ // further entries till max capacity before grow
+ ASSERT_EQ(true, h.contains(i)) << "key " << i << " not found in new table";
+ ASSERT_EQ(i, *h.get(i)) << "value with key " << i << " incorrect in new table";
+ }
+ // the entry that triggered grow()
+ ASSERT_EQ(true, h.contains(key)) << "key " << key << " not found in new table";
+ ASSERT_EQ(key, *h.get(key)) << " value with key " << key << " incorrect in new table";
+ }
+
+ {
+ SCOPED_TRACE("remove");
+ // now remove entries but do not trigger shrink()
+ int i = 0;
+ BucketType pCriticalBucket = NULL;
+ DoublyLinkedList survivedKeys;
+ int initPop = h.population();
+ int numRemoved = 0;
+ bool removedEnough = false;
+ for (; i < h.size(); i++){
+ BucketType pList = h.table[i];
+ if (pList){
+ // number of entries touched: either removed or copied
+ int remainingItems = pList->size();
+
+ while (remainingItems > 0){
+ // check if we can remove this node without causing shrink()
+ if (!removedEnough &&
+ ((float)(h._population - 1)) / h._size <= h.DENSITY_MIN){
+ // this deletion will cause shrink()
+ pCriticalBucket = pList;
+ removedEnough = true;
+ }
+ else{
+ Pair kvPair = (*pList->begin());
+ int key = kvPair.key;
+ if (removedEnough == false){
+ // remove an entry
+ int prevPop = h._population;
+ int prevSize = h.size();
+ h.remove(key);
+ ASSERT_EQ(prevPop - 1, h._population) << "remove did not work";
+ ASSERT_EQ(h._size, prevSize) << "size changed by remove";
+ ASSERT_EQ(false, h.contains(key)) << "removed key " << key << " still remains";
+ numRemoved++;
+ remainingItems--;
+ }
+ else{
+ // copy this should-be-surviving entry into a list for further verification
+ survivedKeys.push(kvPair);
+ remainingItems--;
+ }
+ }
+ }
+ }
+ } // for loop through all slots
+
+ ASSERT_EQ(initPop - numRemoved, survivedKeys.size()) << "initSize+numRemoved not survivedKeys.size";
+ if (removedEnough) {
+ // ok; removing next key should cause shrink()
+ int prevPop = h._population;
+ int prevSize = h.size();
+ int removedKey = (*pCriticalBucket->begin()).key;
+ h.remove(removedKey);
+ cout << "shrinked: \n" << h.toString() << endl;
+ ASSERT_EQ(h._population, prevPop - 1) << "remove did not work";
+ ASSERT_EQ(h._size, prevSize >> 1) << "size did not shrink by half";
+ ASSERT_EQ(false, h.contains(removedKey)) << "removed key " << removedKey << " still remains";
+
+ // now check that all should-have-survived keys are still present in the new table
+ for (DoublyLinkedList::iterator it = survivedKeys.begin(); it != survivedKeys.end(); it++){
+ int key = (*it).key;
+ if (key == removedKey) continue; // this is the removed key that made the table shrink
+ int value = (*it).value;
+ ASSERT_EQ(true, h.contains(key)) << "key " << key << " absent in shrinked table";
+ ASSERT_NE(NULL, (int)h.get(key)) << "get(" << key << ") is NULL in shrinked table";
+ ASSERT_EQ(value, *(h.get(key))) << "get(" << key << ") not " << value << "in shrinked table";
+ }
+
+ }
+ }
+
+
+ //ASSERT_EQ(0, 1) << "incomplete test";
+ }
+ };
+
+ ADD_TEST_F(SeparateChainingHashTableTest, hashCode);
+ ADD_TEST_F(SeparateChainingHashTableTest, all);
+
+}
+}
\ No newline at end of file
diff --git a/YASI_12/ds.doublylinkedlist.h b/YASI_12/ds.doublylinkedlist.h
index bdefafe..0e6270b 100644
--- a/YASI_12/ds.doublylinkedlist.h
+++ b/YASI_12/ds.doublylinkedlist.h
@@ -239,6 +239,19 @@ namespace ds{
}
};
+ // override for printing
+ template, class Iter = DLLIterator >
+ std::ostream& operator<< (std::ostream& out, const DoublyLinkedList& list) {
+ out << list.toString();
+ return out;
+ }
+ template, class Iter = DLLIterator >
+ std::ostream& operator<< (std::ostream& out, const DoublyLinkedList* pList) {
+ out << pList->toString();
+ return out;
+ }
+
+
// test
class DoublyLinkedListTest : public yasi::Test {
typedef DoublyLinkedList >::node_t node_t;
diff --git a/YASI_12/ds.hashtable.h b/YASI_12/ds.hashtable.h
deleted file mode 100644
index e91aa88..0000000
--- a/YASI_12/ds.hashtable.h
+++ /dev/null
@@ -1,750 +0,0 @@
-#pragma once
-#include "common.h"
-#include "ds.kvpair.h"
-#include "ds.doublylinkedlist.h"
-#include "ds.iterator.h"
-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;
-};
-
-template
-class IHashFunction{
-public:
- virtual int operator()(Key n) const = 0;
-};
-
-template
-class IntHashFunction : public IHashFunction{
-public:
- virtual int operator()(Key n) const override{
- return n ^ (~n << 11) ^ (n << 3) ^ (~n << 27);
- }
-};
-
-//template
-//class ICollisionStrategy{
-//
-//public:
-// virtual ~ICollisionResolutionStrategy(){}
-// // either returns a ptr to the bucket-index, or NULL if no bucket found
-// virtual int* findBucket(const Key& k, (void*)pHashTable) = 0;
-//};
-
-
-template< class Key,
- class Value,
- class BucketType,
- class HashFunction
->
-class HashTableBase : public IKeyValueStore < Key, Value >{
-protected:
- // size of the table
- // must be a power of two
- unsigned int _size;
- unsigned int _logSize;
- static const unsigned int INIT_LOGSIZE = 5; // 32 elements
- // actual number of keys stored in the table
- unsigned int _population;
- // the buckets array
- BucketType* table;
- // the hash function
- HashFunction hash;
- // load factor for growth and shrinkage
- 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 modSize(hash(k)); // hash(k) % _size
- }
- BucketType& bucket(const Key& k) const{
- return table[index(k)];
- }
- void initTable(BucketType** pTable, const int numElems){
- // initialize to zero
- 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++){
- buf << "[" << i << "] ";
- if (table[i]) buf << table[i]->toString();
- buf << endl;
- }
- return buf.str();
- }
-
-};
-
-
-// Separate chaining collision resolution strategy
-// each bucket holds a list of values mapped into that bucket
-template<
- class Key,
- class Value = Key,
- class HashFunction = IntHashFunction,
- class Pred = KVPairEqualityPredicate
->
-class SeparateChainingHashTable : public HashTableBase<
- Key,
- Value,
- DoublyLinkedList< KVPair >*,
- HashFunction>
-{
- ///////////////// enable testing ///////////////////
- friend class SeparateChainingHashTableTest;
-
-protected:
- typedef KVPair Pair;
- typedef DoublyLinkedList < Pair > List;
- typedef typename List::NodeType Node;
-public:
- typedef List* BucketType;
- typedef List ListType;
-protected:
- // returns the pointer to the value if exists, otherwise returns NULL
- Node* bucketNode(BucketType pList, const Key& k) const{
- Pred keyEquals;
- for (List::iterator n = pList->begin(); n != pList->end(); n++){
- if (keyEquals((*n).key,k)){
- // found node;
- return n.pNode;
- }
- }
- return NULL;
- }
- Pair* bucketEntry(BucketType pList, const Key& k) const{
- Pred keyEquals;
- for (List::iterator n = pList->begin(); n != pList->end(); n++){
- if (keyEquals((*n).key, k)){
- // found node;
- return & (n.pNode->element);
- }
- }
- return NULL;
- }
- bool bucketContains(BucketType pList, const Key& k) const{
- Pred keyEquals;
- for (List::iterator n = pList->begin(); n != pList->end(); n++){
- if (keyEquals((*n).key, k)){
- // found node;
- return true;
- }
- }
- 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;
-
- virtual ~SeparateChainingHashTable(){
- if (table){
- for (int i = 0; i < _size; i++){
- // each entry is either NULL or a List*
- if (table[i]){
- table[i]->clear();
- DELETE_SAFE(table[i]);
- }
- }
- }
- }
- SeparateChainingHashTable(unsigned int logSize = INIT_LOGSIZE) : HashTableBase(logSize){
-
- //collisionHandler(this);
- }
- // returns true on success, false on failure
- virtual void put(const Key& k, const Value& v) override{
- int i = index(k);
- BucketType pList = table[i];
- if (pList == NULL){
- // empty slot; create a new list
- pList = table[i] = new List();
- // pushFront for better temporal locality
- pList->pushFront(Pair(k, v));
- _population++;
- }
- else{
- // existing bucket
- Pair* pEntry = bucketEntry(pList, k);
- if (pEntry){
- // key already exists; update value
- pEntry->value = v;
- }
- else{
- pList->pushFront(Pair(k, v));
- _population++;
- }
- }
- // do we need to grow?
- if (density() >= DENSITY_MAX) grow();
- }
- virtual void put(const Key& k) {
- put(k, k);
- }
- virtual Value* get(const Key& k) const override{
- int i = index(k);
- BucketType pList = table[i];
- if (pList != NULL){
- // existing bucket
- Pair* pEntry = bucketEntry(pList, k);
- if (pEntry)
- return &pEntry->value;
- }
- return NULL;
- }
- virtual void remove(const Key& k) override{
- int i = index(k);
- BucketType pList = table[i];
- if (pList == NULL){
- // the key is absent
- // nothing to do
- }
- else{
- // existing bucket
- Node* pNode = bucketNode(pList, k);
- if (pNode){
- pList->remove(pNode);
- _population--;
- // do we need to shrink?
- if (density() <= DENSITY_MIN) shrink();
- }
- }
- }
- virtual bool contains(const Key& k) const override{
- int i = index(k);
- BucketType pList = table[i];
- if (pList != NULL){
- // existing bucket
- return bucketContains(pList,k);
- }
- return false;
- }
-
-};
-
-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
-
- 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{
- // 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{
- // 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;
- }
- }
-
- Pair* insertKey(const Key& k) {
-
- // 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++){
- Pair* p = oldTable[i];
- if (p){
- insert(p->key, p->value);
- DELETE_SAFE(oldTable[i]);
- }
- }
- }
-
-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;
- typedef KVPair Pair;
- typedef IntHashTable::BucketType BucketType;
- typedef IntHashTable::ListType ListType;
-
- IntHashTable h;
-public:
-
- void hashCode(){
- // hashcode must be within range
- srand((unsigned int)time(NULL));
- for (int i = 0; i < 1000; i++){
- int n = rand();
- int index = h.index(n);
- ASSERT_LT(index, h.size()) << "index(" << n << ") out of range";
- ASSERT_GE(index, 0) << "index(" << n << ") out of range";
- }
- }
-
- void all(){
- srand((unsigned int)time(NULL));
- int logSize = h._logSize;
- int size = h._size;
- ASSERT_EQ(IntHashTable::INIT_LOGSIZE, logSize) << "initial logSize not " << IntHashTable::INIT_LOGSIZE;
- ASSERT_EQ(1 << logSize, h.size()) << "table size not 2^" << logSize;
- for (int i = 0; i < h.size(); i++){
- ASSERT_EQ(0, (int)h.table[i]) << "table[i] not NULL for i=" << i;
- }
-
- {
- SCOPED_TRACE("add items in hashtable");
- // now add 10 items; should not trigger grow()
- ASSERT_LT(10 / h.size(), h.DENSITY_MAX) << "10/" << h.size() << " items triggered grow() while DENSITY_MAX=" << h.DENSITY_MAX;
- for (int i = 0; i < 10; i++){
- h.put(i, i);
- }
- // test contains()
- for (int i = 0; i < 10; i++){
- ASSERT_EQ(true, h.contains(i)) << "key " << i << " not found";
- }
- ASSERT_EQ(false, h.contains(100)) << "absent key 100 was found";
- // test get()
- for (int i = 0; i < 10; i++){
- int* pValue = h.get(i);
- ASSERT_NE(NULL, (int)pValue) << "pValue with key " << i << " NULL";
- ASSERT_EQ(i, *pValue) << "value with key " << i << " mismatch";
- }
- ASSERT_EQ(NULL, h.get(100)) << "absent key 100 was found";
- // test duplicate insert
- // should result in update
- h.put(5, -6);
- int* pValue = h.get(5);
- ASSERT_NE(NULL, (int)pValue) << "pValue with key " << 5 << " NULL";
- ASSERT_EQ(-6, *pValue) << "value with key " << 5 << " not -6";
-
- // now add some more but don't trigger grow()
- size = h.size();
- int maxCurrent = ((int)(h.size() * h.DENSITY_MAX)) - 1;
- int currentPop = h.population();
- for (int i = currentPop; i < maxCurrent; i++){
- // this insertion should not trigger grow()
- h.put(i, i);
- }
- ASSERT_EQ(maxCurrent, h.population()) << "population not maxCurrent";
- ASSERT_EQ(size, h.size()) << "size() not size";
- // this insertion should trigger grow()
- int key = rand();
- while (h.contains(key)){ key = rand(); }
- h.put(key, key); // should trigger grow()
- ASSERT_EQ(size * 2, h.size()) << "size() not 2*oldSize";
- ASSERT_EQ(maxCurrent + 1, h.population()) << "population() not maxCurrent+1";
- ASSERT_GE(0.375, h.density()) << "density() > 0.375";
-
- // print the table
- string str = h.toString();
- cout << "after grow(): " << endl << str << endl;
-
- // check that all old entries are still in the table
- for (int i = 0; i < 10; i++){ // first 10 entries
- int v = i;
- if (i == 5){
- // remember that we had updated an entry
- v = -6;
- }
- ASSERT_EQ(true, h.contains(i)) << "key " << i << " not found in new table";
- ASSERT_EQ(v, *h.get(i)) << "value with key " << i << " incorrect in new table";
- }
- for (int i = currentPop; i < maxCurrent; i++){ // further entries till max capacity before grow
- ASSERT_EQ(true, h.contains(i)) << "key " << i << " not found in new table";
- ASSERT_EQ(i, *h.get(i)) << "value with key " << i << " incorrect in new table";
- }
- // the entry that triggered grow()
- ASSERT_EQ(true, h.contains(key)) << "key " << key << " not found in new table";
- ASSERT_EQ(key, *h.get(key)) << " value with key " << key << " incorrect in new table";
- }
-
- {
- SCOPED_TRACE("remove");
- // now remove entries but do not trigger shrink()
- int i = 0;
- BucketType pCriticalBucket = NULL;
- DoublyLinkedList survivedKeys;
- int initPop = h.population();
- int numRemoved = 0;
- bool removedEnough = false;
- for (; i < h.size(); i++){
- BucketType pList = h.table[i];
- if (pList){
- // number of entries touched: either removed or copied
- int remainingItems = pList->size();
-
- while (remainingItems > 0){
- // check if we can remove this node without causing shrink()
- if (!removedEnough &&
- ((float)(h._population - 1)) / h._size <= h.DENSITY_MIN){
- // this deletion will cause shrink()
- pCriticalBucket = pList;
- removedEnough = true;
- }
- else{
- Pair kvPair = (*pList->begin());
- int key = kvPair.key;
- if (removedEnough == false){
- // remove an entry
- int prevPop = h._population;
- int prevSize = h.size();
- h.remove(key);
- ASSERT_EQ(prevPop - 1, h._population) << "remove did not work";
- ASSERT_EQ(h._size, prevSize) << "size changed by remove";
- ASSERT_EQ(false, h.contains(key)) << "removed key " << key << " still remains";
- numRemoved++;
- remainingItems--;
- }
- else{
- // copy this should-be-surviving entry into a list for further verification
- survivedKeys.push(kvPair);
- remainingItems--;
- }
- }
- }
- }
- } // for loop through all slots
-
- ASSERT_EQ(initPop - numRemoved, survivedKeys.size()) << "initSize+numRemoved not survivedKeys.size";
- if (removedEnough) {
- // ok; removing next key should cause shrink()
- int prevPop = h._population;
- int prevSize = h.size();
- int removedKey = (*pCriticalBucket->begin()).key;
- h.remove(removedKey);
- cout << "shrinked: \n" << h.toString() << endl;
- ASSERT_EQ(h._population, prevPop - 1) << "remove did not work";
- ASSERT_EQ(h._size, prevSize >> 1) << "size did not shrink by half";
- ASSERT_EQ(false, h.contains(removedKey)) << "removed key " << removedKey << " still remains";
-
- // now check that all should-have-survived keys are still present in the new table
- for (DoublyLinkedList::iterator it = survivedKeys.begin(); it != survivedKeys.end(); it++){
- int key = (*it).key;
- if (key == removedKey) continue; // this is the removed key that made the table shrink
- int value = (*it).value;
- ASSERT_EQ(true, h.contains(key)) << "key " << key << " absent in shrinked table";
- ASSERT_NE(NULL, (int)h.get(key)) << "get(" << key << ") is NULL in shrinked table";
- ASSERT_EQ(value, *(h.get(key))) << "get(" << key << ") not " << value << "in shrinked table";
- }
-
- }
- }
-
-
- //ASSERT_EQ(0, 1) << "incomplete test";
- }
-};
-
-ADD_TEST_F(SeparateChainingHashTableTest, hashCode);
-ADD_TEST_F(SeparateChainingHashTableTest, all);
-
-} // namespace ds
-} // namespace yasi
\ No newline at end of file
diff --git a/YASI_12/ds.hashtablebase.h b/YASI_12/ds.hashtablebase.h
new file mode 100644
index 0000000..1a17038
--- /dev/null
+++ b/YASI_12/ds.hashtablebase.h
@@ -0,0 +1,178 @@
+#pragma once
+#include "common.h"
+#include "ds.kvpair.h"
+#include "ds.doublylinkedlist.h"
+#include "ds.iterator.h"
+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;
+};
+
+template
+class IHashFunction{
+public:
+ virtual int operator()(Key n) const = 0;
+};
+
+template
+class IntHashFunction : public IHashFunction{
+public:
+ virtual int operator()(Key n) const override{
+ return n ^ (~n << 11) ^ (n << 3) ^ (~n << 27);
+ }
+};
+
+
+//template
+//class ICollisionStrategy{
+//
+//public:
+// virtual ~ICollisionResolutionStrategy(){}
+// // either returns a ptr to the bucket-index, or NULL if no bucket found
+// virtual int* findBucket(const Key& k, (void*)pHashTable) = 0;
+//};
+
+
+template< class Key,
+ class Value,
+ class BucketType,
+ class HashFunction
+>
+class HashTableBase : public IKeyValueStore < Key, Value >{
+protected:
+
+ // size of the table
+ // must be a power of two
+ unsigned int _size;
+ unsigned int _logSize;
+ static const unsigned int INIT_LOGSIZE = 5; // 32 elements
+ // actual number of keys stored in the table
+ unsigned int _population;
+ // the buckets array
+ BucketType* table;
+ // the hash function
+ HashFunction hash;
+ // load factor for growth and shrinkage
+ 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 modSize(hash(k)); // hash(k) % _size
+ }
+ BucketType& bucket(const Key& k) const{
+ return table[index(k)];
+ }
+ void initTable(BucketType** pTable, const int numElems){
+ // initialize to zero
+ 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; }
+
+ inline unsigned int maxPopulationWithoutGrow() const{
+ return (unsigned int)(_size * DENSITY_MAX) - 1;
+ }
+ inline unsigned int minPopulationWithoutShrink() const{
+ return (unsigned int)(_size * DENSITY_MIN) + 1;
+ }
+
+ virtual void copyTable(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 table elements
+ copyTable(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() const{
+ stringstream buf;
+ for (int i = 0; i < _size; i++){
+ buf << "[" << i << "] ";
+ if (table[i]) buf << table[i];
+ buf << endl;
+ }
+ return buf.str();
+ }
+
+};
+// override for printing
+template< class Key,
+ class Value,
+ class BucketType,
+ class HashFunction
+>
+std::ostream& operator<< (std::ostream& out, const HashTableBase& h) {
+ out << h.toString();
+ return out;
+}
+
+
+} // namespace ds
+} // namespace yasi
\ No newline at end of file
diff --git a/YASI_12/ds.kvpair.h b/YASI_12/ds.kvpair.h
index 63989cc..164e1f1 100644
--- a/YASI_12/ds.kvpair.h
+++ b/YASI_12/ds.kvpair.h
@@ -32,6 +32,50 @@ namespace yasi{
out << kv.key;
return out;
}
+ template
+ std::ostream& operator<< (std::ostream& out, const KVPair* pKv) {
+ out << pKv->key;
+ return out;
+ }
+
+ // class KeyOnly
+ // for stroing keys without values
+ // will save space where values are not important
+ template
+ class KeyOnly : public IComparable>{
+ typedef KeyOnly self;
+ public:
+ // still support the KVPair interface
+ // i.e., this->key and this->value are both meaningful (and the same)
+ union{
+ Key key;
+ Value value;
+ };
+
+ virtual ~KeyOnly(){}
+ KeyOnly(){}
+ KeyOnly(const Key& k, const Value& v) : key(k){} // ignore value
+ // need explicit because otherwise copy-constructor can be used to unwantedly put a KVPair as key
+ explicit KeyOnly(const Key& k) : key(k){}
+ KeyOnly(const self& other) :key(other.key){}
+ self& operator= (const self& other){ key = other.key; return *this; }
+ inline bool operator==(const self& other) const override{ return key == other.key; }
+ inline bool operator< (const self& other) const override{ return key < other.key; }
+ inline bool operator<= (const self& other) const override{ return key <= other.key; }
+ inline bool operator!=(const self& other) const { return !((*this) == other); }
+ };
+ // override for printing
+ template
+ std::ostream& operator<< (std::ostream& out, const KeyOnly& kv) {
+ out << kv.key;
+ return out;
+ }
+ template
+ std::ostream& operator<< (std::ostream& out, const KeyOnly* pKv) {
+ out << pKv->key;
+ return out;
+ }
+
template
class KVPairEqualityPredicate{
diff --git a/YASI_12/ds.list.h b/YASI_12/ds.list.h
index bfdd8f2..d4223e5 100644
--- a/YASI_12/ds.list.h
+++ b/YASI_12/ds.list.h
@@ -2,6 +2,7 @@
#include "common.h"
#include "utils.h"
#include "ds.iterator.h"
+#include
using namespace std;
namespace yasi{
@@ -20,7 +21,10 @@ class IList{
virtual bool empty() const = 0;
virtual Iter begin() const = 0;
virtual Iter end() const = 0;
+
};
+
+
} // namespace ds
} // namespace yasi
\ No newline at end of file
diff --git a/YASI_12/ds.singlylinkedlist.h b/YASI_12/ds.singlylinkedlist.h
index 24a1995..857ae37 100644
--- a/YASI_12/ds.singlylinkedlist.h
+++ b/YASI_12/ds.singlylinkedlist.h
@@ -276,6 +276,17 @@ class SinglyLinkedList : public virtual ISinglyLinkedList < E, Node, Iter > {
};
+// override for printing
+template, class Iter = SLLIterator >
+std::ostream& operator<< (std::ostream& out, const SinglyLinkedList& list) {
+ out << list.toString();
+ return out;
+}
+template, class Iter = SLLIterator >
+std::ostream& operator<< (std::ostream& out, const SinglyLinkedList* pList) {
+ out << pList->toString();
+ return out;
+}
class SinglyLinkedListTest : public yasi::Test{
typedef SinglyLinkedList >::node_t node_t;
diff --git a/YASI_12/main.cpp b/YASI_12/main.cpp
index 286f9ee..1c2fa49 100644
--- a/YASI_12/main.cpp
+++ b/YASI_12/main.cpp
@@ -7,7 +7,8 @@
#include "ds.binarysearchtree.h"
#include "ds.priorityqueue.h"
#include "ds.BSTDictionary.h"
-#include "ds.hashtable.h"
+#include "ds.separatechaininghashtable.h"
+#include "ds.linearprobinghashtable.h"
//#include "Sorter.h"
#include
diff --git a/gtest-1.7.0/msvc/gtest/gtest.tlog/CL.read.1.tlog b/gtest-1.7.0/msvc/gtest/gtest.tlog/CL.read.1.tlog
index b72e1ff..5f26b72 100644
Binary files a/gtest-1.7.0/msvc/gtest/gtest.tlog/CL.read.1.tlog and b/gtest-1.7.0/msvc/gtest/gtest.tlog/CL.read.1.tlog differ
diff --git a/gtest-1.7.0/msvc/gtest/vc120.idb b/gtest-1.7.0/msvc/gtest/vc120.idb
index 4b9636d..56f7d00 100644
Binary files a/gtest-1.7.0/msvc/gtest/vc120.idb and b/gtest-1.7.0/msvc/gtest/vc120.idb differ
diff --git a/gtest-1.7.0/msvc/gtestd.lib b/gtest-1.7.0/msvc/gtestd.lib
index 7b5cd80..8a8264d 100644
Binary files a/gtest-1.7.0/msvc/gtestd.lib and b/gtest-1.7.0/msvc/gtestd.lib differ