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