diff --git a/YASI_12/ds.LinearProbingHashTable.h b/YASI_12/ds.LinearProbingHashTable.h index 97d26a3..5fef035 100644 --- a/YASI_12/ds.LinearProbingHashTable.h +++ b/YASI_12/ds.LinearProbingHashTable.h @@ -16,22 +16,24 @@ namespace ds{ class LinearProbingHashTable : public HashTableBase < Key, Value, - EntryType*, + EntryType, // storing objects, not pointers HashFunction > { ///////////////// enable testing /////////////////// friend class LinearProbingHashTableTest; public: + // an EntryType object is stored in the table + //typedef KVPair Pair; - typedef EntryType Pair; // technically, this pair may not have a value - typedef Pair* BucketType; + //typedef EntryType Pair; // technically, this pair may not have a value + typedef EntryType BucketType; // each bucket holds an EntryType object + typedef EntryType Pair; // which is actually a key-value pair + typedef EntryType* BucketEntryPtr; // ptr to an entry object typedef Key KeyType; typedef Value ValueType; - typedef BucketType BucketEntryPtr; // need to change 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 + EntryType _zeroKeyEntry; // 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 char* _pZeroValue; // all zeros up to #bytes of a Value; used to set a Value to zero @@ -42,22 +44,6 @@ namespace ds{ return modSize(index - 1); } - // only fundamental key types are checked to be zero - // non-fundamental types are never zero - // we do this because sizeof(Key) includes the padding bytes for non-fundamental types; - // we cannot predict value at those bytes - inline bool isKeyZero(const Key* pKey) const{ - return memcmp(pKey, _pZeroKey, sizeof(Key)) == 0; - } - inline bool isValueZero(const Value* pValue) const{ - return memcmp(pValue, _pZeroValue, sizeof(Value)) == 0; - } - inline void setKeyZero(Key* pKey) const{ - memcpy(pKey, _pZeroKey, sizeof(Key)); - } - inline void setValueZero(Value* pValue) const{ - memcpy(pValue, _pZeroValue, sizeof(Value)); - } // if a key is already there, it is updated void insert(const Key& k, const Value& v){ @@ -75,7 +61,7 @@ namespace ds{ BucketEntryPtr lookupKey(const Key& k) const { // test the zero key if (isKeyZero(&k)){ - if (_zeroUsed) return const_cast(&_zeroPair); + if (_zeroUsed) return const_cast(&_zeroKeyEntry); else return NULL; } else{ @@ -116,12 +102,12 @@ namespace ds{ // we will use a special slot for this entry if (_zeroUsed == false) _zeroUsed = true; - _zeroPair.key = k; + _zeroKeyEntry.key = k; _population++; // see if we need to size up if (needGrow(_population)) grow(); - return &_zeroPair; + return &_zeroKeyEntry; } else{ // key is non-zero @@ -148,7 +134,7 @@ namespace ds{ } else{ // create a new entry - table[cur] = new Pair(k); + makeEntry(cur,k); _population++; // return the pointer to the entry return entryptr(cur); @@ -181,10 +167,11 @@ namespace ds{ _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]); + EntryType p = oldTable[i]; + if ( !isKeyZero(&p.key)){ + insert(p.key, p.value); + // no need to delete because it is not pointer anymore + //DELETE_SAFE(oldTable[i]); } } // count the zero element @@ -202,41 +189,62 @@ namespace ds{ hash = newHashFunction; } void clear(){ - if (table){ - for (unsigned int i = 0; i < _size; i++){ - removeEntry(i); - } - } - DELETE_ARR_SAFE(table); + //if (table){ + // for (unsigned int i = 0; i < _size; i++){ + // removeEntry(i); + // } + //} + // DELETE_ARR_SAFE(table); _zeroUsed = false; _size = _population = _logSize = 0; } - // for test only - inline BucketType entry(const int bucket) const { + //////////////////////////////////////////////// + //// abstracting the data type of the table + //////////////////////////////////////////////// + virtual inline BucketType& entry(const int bucket) const { return table[bucket]; } - inline Key key(const int bucket) const { - return table[bucket]->key; + virtual inline Key key(const int bucket) const { + return table[bucket].key; } - inline Key* keyptr(const int bucket) const { - return &table[bucket]->key; + virtual inline Key* keyptr(const int bucket) const { + return &table[bucket].key; } - inline BucketEntryPtr entryptr(const int bucket) const { - return table[bucket]; + virtual inline BucketEntryPtr entryptr(const int bucket) const { + return &table[bucket]; } - inline Value value(const int bucket) const { - return table[bucket]->value; + virtual inline Value value(const int bucket) const { + return table[bucket].value; } - inline BucketEntryPtr entryPtr(const int bucket) const{ return table[bucket]; } - inline bool isNull(const int bucket) const{ - return table[bucket] == NULL; + virtual inline bool isNull(const int bucket) const{ + return isKeyZero(keyptr(bucket)); } - inline void makeNull(const int bucket) const{ + virtual inline void makeNull(const int bucket) const{ setKeyZero(keyptr(bucket)); } - inline void removeEntry(const int bucket) const{ - DELETE_SAFE(table[bucket]); + virtual inline void removeEntry(const int bucket) const{ + //DELETE_SAFE(table[bucket]); + makeNull(bucket); + } + virtual inline bool isKeyZero(const Key* pKey) const{ + return memcmp(pKey, _pZeroKey, sizeof(Key)) == 0; + } + virtual inline bool isValueZero(const Value* pValue) const{ + return memcmp(pValue, _pZeroValue, sizeof(Value)) == 0; + } + virtual inline void setKeyZero(Key* pKey) const{ + memcpy(pKey, _pZeroKey, sizeof(Key)); + } + virtual inline void setValueZero(Value* pValue) const{ + memcpy(pValue, _pZeroValue, sizeof(Value)); + } + virtual inline void makeEntry(const int bucket, const Key& k){ + table[bucket] = Pair(k); + // if we stored pointers, we would have done new Pair(k) + } + virtual bool isBucketEmpty(const int bucket) const override { + return isNull(bucket); } public: @@ -246,7 +254,7 @@ namespace ds{ memset(_pZeroKey, 0, sizeof(Key)); - setKeyZero(&_zeroPair.key); + setKeyZero(&_zeroKeyEntry.key); } virtual ~LinearProbingHashTable(){ clear(); @@ -312,7 +320,7 @@ namespace ds{ // crossedBoundary = true; //} - unsigned int neighborFirstBucket = index(table[neighbor]->key); + unsigned int neighborFirstBucket = index(key(neighbor)); 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 @@ -442,7 +450,7 @@ namespace ds{ p->value = key; // assign the value cout << str << endl << h; ASSERT_NE(true, h.isNull(bucket)) << "bucket " << bucket << " NULL"; - ASSERT_EQ(p, h.entryPtr(bucket)) << key << " not in bucket " << bucket; + ASSERT_EQ(p, h.entryptr(bucket)) << key << " not in bucket " << bucket; ASSERT_EQ(key, h.key(bucket)) << key << " not in bucket " << bucket; } { @@ -454,7 +462,7 @@ namespace ds{ p->value = key; // assign the value cout << str << endl << h; ASSERT_NE(true, h.isNull(bucket)) << "bucket " << bucket << " NULL"; - ASSERT_EQ(p, h.entryPtr(bucket)) << key << " not in bucket " << bucket; + ASSERT_EQ(p, h.entryptr(bucket)) << key << " not in bucket " << bucket; ASSERT_EQ(key, h.key(bucket)) << key << " not in bucket " << bucket; } { @@ -466,7 +474,7 @@ namespace ds{ BucketEntryPtr p = h.insertKey(key); cout << str << endl << h; ASSERT_NE(true, h.isNull(bucket)) << "bucket " << bucket << " NULL"; - ASSERT_EQ(p, h.entryPtr(bucket)) << key << " not in bucket " << bucket; + ASSERT_EQ(p, h.entryptr(bucket)) << key << " not in bucket " << bucket; ASSERT_EQ(key, h.key(bucket)) << key << " not in bucket " << bucket; ASSERT_EQ(key, h.value(bucket)) << key << " not in bucket " << bucket; } @@ -483,7 +491,7 @@ namespace ds{ p->value = key; // assign the value cout << str << endl << h; ASSERT_NE(true, h.isNull(bucket)) << "bucket " << bucket << " NULL"; - ASSERT_EQ(p, h.entryPtr(bucket)) << key << " not in bucket " << bucket; + ASSERT_EQ(p, h.entryptr(bucket)) << key << " not in bucket " << bucket; ASSERT_EQ(key, h.key(bucket)) << key << " not in bucket " << bucket; } @@ -496,7 +504,7 @@ namespace ds{ p->value = key; // assign the value cout << str << endl << h; ASSERT_NE(true, h.isNull(bucket)) << "bucket " << bucket << " NULL"; - ASSERT_EQ(p, h.entryPtr(bucket)) << key << " not in bucket " << bucket; + ASSERT_EQ(p, h.entryptr(bucket)) << key << " not in bucket " << bucket; ASSERT_EQ(key, h.key(bucket)) << key << " not in bucket " << bucket; } @@ -510,9 +518,9 @@ namespace ds{ 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(p, &h._zeroKeyEntry) << " 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"; + ASSERT_EQ(key, h._zeroKeyEntry.key) << key << " not in zeroPair"; } } @@ -544,7 +552,7 @@ namespace ds{ // check that all items exist // items are rehashed mod 32 for (int i = 0; i < h1._size; i++){ - if (h1.table[i]){ + if (!h1.isNull(i)){ int key = h1.key(i); int value = h1.value(i); BucketEntryPtr p = h2.lookupKey(key); @@ -782,21 +790,21 @@ namespace ds{ 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"; + ASSERT_EQ(0, h._zeroKeyEntry.key) << "key of zero element non-zero"; + ASSERT_EQ(5, h._zeroKeyEntry.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"; + ASSERT_EQ(0, h._zeroKeyEntry.key) << "key of zero element non-zero"; + ASSERT_EQ(0, h._zeroKeyEntry.value) << "value of zero element non-zero"; } { SCOPED_TRACE("remove from an all-filled table"); // create a pathological table - BucketEntryPtr* badTable = new BucketEntryPtr[8]; // 8 entries in the table + BucketType* badTable = new BucketType[8]; // 8 entries in the table for (int i = 0; i < 8; i++){ - badTable[i] = new Pair(5, i); // all keys are 5 + badTable[i] = 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 @@ -878,45 +886,52 @@ namespace ds{ } void isKeyZero(){ - struct MyStruct{ - int a; - float b; - char c; - bool operator==(const MyStruct& other) const { return a == other.a && b == other.b && c == other.c; } - bool operator!=(const MyStruct& other) const { return !(*this == other); } - bool operator<(const MyStruct& other) const { return a < other.a && b < other.b && c < other.c; } - bool operator<=(const MyStruct& other) const { return a <= other.a && b <= other.b && c <= other.c; } - }; - struct MyHashFunction{ - public: - virtual int operator()(const MyStruct& n)const{ return n.a + (int)n.b + (int)n.c; } - }; - ASSERT_EQ(false, std::is_fundamental::value) << "MyStruct fundamental"; - MyStruct z = { 0, 0, 0 }, nz1 = { 0, 5, 0 }, nz2 = { 0, 0, 6 }, nz3 = { 10, 0, 0 }; - ASSERT_EQ(sizeof(int) * 3, sizeof(z)) << "struct size mismatch"; // note the word alignment for char - - // create a hashtable with MyStruct as Key - LinearProbingHashTable,MyHashFunction> h(1); - // now test - ASSERT_EQ(false, h.isKeyZero(&z)) << "z not zero"; // compound-types are always non-zero - ASSERT_EQ(false, h.isKeyZero(&nz1)) << "nz1 zero"; - - int k1 = 0, k11 = 2; - char k2 = 0, k22 = 'c'; - LinearProbingHashTable h1(1); - LinearProbingHashTable h2(1); - ASSERT_EQ(true, h1.isKeyZero(&k1)) << "k1 not zero"; - ASSERT_EQ(false, h1.isKeyZero(&k11)) << "k11 zero"; - ASSERT_EQ(true, h2.isKeyZero(&k2)) << "k2 not zero"; - ASSERT_EQ(false, h2.isKeyZero(&k22)) << "k22 zero"; - - // test setKeyZero and setValueZero - h.setKeyZero(&nz1); - ASSERT_EQ(true, h.isKeyZero(&nz1)) << "nz1 not zero"; - // both key and value of h are of type MyStruct - h.setValueZero(&nz2); - ASSERT_EQ(true, h.isValueZero(&nz2)) << "nz2 not zero"; - + //struct MyStruct{ + // int a; + // float b; + // char c; + // bool operator==(const MyStruct& other) const { return a == other.a && b == other.b && c == other.c; } + // bool operator!=(const MyStruct& other) const { return !(*this == other); } + // bool operator<(const MyStruct& other) const { return a < other.a && b < other.b && c < other.c; } + // bool operator<=(const MyStruct& other) const { return a <= other.a && b <= other.b && c <= other.c; } + //}; + //struct MyHashFunction{ + //public: + // virtual int operator()(const MyStruct& n)const{ return n.a + (int)n.b + (int)n.c; } + //}; + // + //// create a hashtable with MyStruct as Key + //LinearProbingHashTable,MyHashFunction> h(1); + + IntHashTable h; + IntHashTable* pH = new IntHashTable(); + delete pH; + + //h.insert(4, 4); + //h.insert(5, 5); + //h.insert(6, 6); + + //// now test + //ASSERT_EQ(false, h.isNull(4)) << "4 zero"; + //ASSERT_EQ(false, h.isNull(5)) << "5 zero"; + //ASSERT_EQ(false, h.isNull(6)) << "6 zero"; + //ASSERT_EQ(true, h.isNull(3)) << "3 not zero"; + //ASSERT_EQ(true, h.isNull(0)) << "0 not zero"; + // + //h.setKeyZero(h.keyptr(4)); + //ASSERT_EQ(true, h.isNull(4)) << "4 not null"; + //h.makeNull(5); + //ASSERT_EQ(true, h.isNull(5)) << "5 not null"; + //*h.keyptr(6) = 0; + //ASSERT_EQ(true, h.isNull(6)) << "6 not null"; + //h.entryptr(2)->key = 2; + //ASSERT_EQ(2, h.key(2)) << "[2] not 2"; + //h.removeEntry(2); + //ASSERT_EQ(true, h.isNull(2)) << "[2] not NULL"; + //h.makeEntry(4, 44); + //ASSERT_EQ(44, h.key(4)) << "[4] not 44"; + + ASSERT_EQ(0, 1) << "uncomment other tests"; } }; diff --git a/YASI_12/ds.SeparateChainingHashTable.h b/YASI_12/ds.SeparateChainingHashTable.h index 3d234e1..d573f63 100644 --- a/YASI_12/ds.SeparateChainingHashTable.h +++ b/YASI_12/ds.SeparateChainingHashTable.h @@ -81,6 +81,10 @@ namespace ds{ } } + + virtual bool isBucketEmpty(const int bucket) const override { + return table[bucket] == NULL || table[bucket]->empty(); + } public: typedef Key KeyType; typedef Value ValueType; diff --git a/YASI_12/ds.hashtablebase.h b/YASI_12/ds.hashtablebase.h index 33a9d0a..a2f3aaf 100644 --- a/YASI_12/ds.hashtablebase.h +++ b/YASI_12/ds.hashtablebase.h @@ -86,7 +86,8 @@ class HashTableBase : public IKeyValueStore < Key, Value >{ } void initTable(BucketType** pTable, const int numElems){ // initialize to zero - memset(*pTable, 0, numElems * sizeof(BucketType)); + const int sizeOfEntry = sizeof(BucketType); + memset(*pTable, 0, numElems * sizeOfEntry); } inline float density(){ return ((float) _population) / _size; } // true if the specified population would be too dense for current table @@ -101,6 +102,8 @@ class HashTableBase : public IKeyValueStore < Key, Value >{ return (unsigned int)(_size * DENSITY_MIN) + 1; } + virtual bool isBucketEmpty(const int bucket) const = 0; + virtual void copyTable(BucketType* oldTable, const unsigned int oldSize) = 0; // grows/shrinks by specified logFactor // that is, newSize is either oldSize << logFactor (grow) @@ -145,7 +148,10 @@ class HashTableBase : public IKeyValueStore < Key, Value >{ table = new BucketType[_size]; initTable(&table, _size); } - virtual ~HashTableBase(){ DELETE_ARR_SAFE(table); _size = _population = _logSize = 0; } + virtual ~HashTableBase(){ + DELETE_ARR_SAFE(table); + _size = _population = _logSize = 0; + } inline unsigned int size(){ return _size; } inline unsigned int population(){ return _population; } @@ -155,7 +161,7 @@ class HashTableBase : public IKeyValueStore < Key, Value >{ stringstream buf; for (int i = 0; i < _size; i++){ buf << "[" << i << "] "; - if (table[i]) buf << table[i]; + if (!isBucketEmpty(i)) buf << table[i]; buf << endl; } return buf.str();