From b5719e3cfdb8c68f7f00a16d6d7e9ee83d147181 Mon Sep 17 00:00:00 2001 From: saq10002 Date: Fri, 10 Oct 2014 20:21:22 -0400 Subject: [PATCH] Separated test code from hash tables. Signed-off-by: unknown --- YASI_12/YASI_12.vcxproj | 4 + YASI_12/YASI_12.vcxproj.filters | 12 + YASI_12/ds.HopScotchHashTable.h | 639 +------------------ YASI_12/ds.HopScotchHashTable.test.h | 640 ++++++++++++++++++++ YASI_12/ds.IntLinearProbingHashTable.h | 30 +- YASI_12/ds.IntLinearProbingHashTable.test.h | 39 ++ YASI_12/ds.LinearProbingHashTable.h | 625 +------------------ YASI_12/ds.LinearProbingHashTable.test.h | 586 ++++++++++++++++++ YASI_12/ds.SeparateChainingHashTable.h | 183 +----- YASI_12/ds.SeparateChainingHashTable.test.h | 189 ++++++ YASI_12/ds.hashtablebase.h | 46 ++ YASI_12/main.cpp | 6 +- 12 files changed, 1541 insertions(+), 1458 deletions(-) create mode 100644 YASI_12/ds.HopScotchHashTable.test.h create mode 100644 YASI_12/ds.IntLinearProbingHashTable.test.h create mode 100644 YASI_12/ds.LinearProbingHashTable.test.h create mode 100644 YASI_12/ds.SeparateChainingHashTable.test.h diff --git a/YASI_12/YASI_12.vcxproj b/YASI_12/YASI_12.vcxproj index aa309cc..3d9ae4b 100644 --- a/YASI_12/YASI_12.vcxproj +++ b/YASI_12/YASI_12.vcxproj @@ -93,13 +93,16 @@ + + false + @@ -109,6 +112,7 @@ + diff --git a/YASI_12/YASI_12.vcxproj.filters b/YASI_12/YASI_12.vcxproj.filters index c8be0f4..ccbf8b5 100644 --- a/YASI_12/YASI_12.vcxproj.filters +++ b/YASI_12/YASI_12.vcxproj.filters @@ -188,5 +188,17 @@ Header Files\Data Structures\Dictionary + + Header Files\Data Structures\Hash Table + + + Header Files\Data Structures\Hash Table + + + Header Files\Data Structures\Hash Table + + + Header Files\Data Structures\Hash Table + \ No newline at end of file diff --git a/YASI_12/ds.HopScotchHashTable.h b/YASI_12/ds.HopScotchHashTable.h index dfdec01..faf3f1b 100644 --- a/YASI_12/ds.HopScotchHashTable.h +++ b/YASI_12/ds.HopScotchHashTable.h @@ -3,9 +3,12 @@ #include "ds.IntLinearProbingHashTable.h" #include +// enable-disable testing classes in this file +#include "test.this.module.h" +using namespace std; + namespace yasi{ namespace ds{ -using namespace std; template struct HopScotchEntry{ @@ -50,9 +53,10 @@ class HopScotchHashTable Value, HashFunction, EntryType > { ///////////////// enable testing /////////////// - friend class HopScotchHashTableTest; + FRIEND_TEST_CLASS( HopScotchHashTableTest); + template - friend class LinearProbingHashTableTestBase; + FRIEND_TEST_CLASS( LinearProbingHashTableTestBase); public: typedef size_t Key; @@ -424,635 +428,6 @@ class HopScotchHashTable }; -////////// test IntLinearProbingHashTable -// inherit tests from base class, just redefine the types - -class HopScotchHashTableTest : public yasi::Test -// : public LinearProbingHashTableTestBase< -// HopScotchHashTable >, -// HopScotchHashTable >, -// HopScotchHashTable > -//> -{ - // -protected: - typedef HopScotchHashTable > IntHashTable8; - typedef HopScotchHashTable > IntHashTable16; - typedef IntHashTable16 IntHashTable; // default - typedef HopScotchHashTable > IntHashTable32; - typedef IntHashTable::BucketType BucketType; - typedef IntHashTable::BucketEntryPtr BucketEntryPtr; - typedef IntHashTable::Pair Pair; - -public: - // inherit all public tests - - //void pushToRight(){ - // IntHashTable h(3, 4); // size 8, H=4 - // ASSERT_EQ(8, h.size()) << "size not 8"; - // ASSERT_EQ(4, h.H) << "H not 4"; - // for (int i = 0; i < h.size(); i++){ - // ASSERT_EQ(0, h.table[i].key) << "key[" << i << "] not zero"; - // } - - // h.table[2] = BucketType(2, 2, 0); - // /// - - 2 - - - - - - // /// 0 1 2 3 4 5 6 7 - - // bool ok; - // int b, k; - // { - // SCOPED_TRACE("no bumps"); - // /// - - 2 - - - - - - // /// 0 1 2 3 4 5 6 7 - // ok = h.pushToRight(2); h.makeNull(2); - // /// - - - 2 - - - - - // /// 0 1 2 3 4 5 6 7 - // ASSERT_EQ(true, ok) << "push[2] failed"; - // // [2] will be 2 because - // b = 2; k = 0; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; - // b = 3; k = 2; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; - // // put it back - // /// - - 2 - - - - - - // /// 0 1 2 3 4 5 6 7 - // h.swap(2, 3); - // b = 2; k = 2; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; - // b = 3; k = 0; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; - - // h.table[3] = BucketType(3, 3, 0); - // h.table[4] = BucketType(4, 4, 0); - // h.table[5] = BucketType(5, 5, 0); - // /// - - 2 3 4 5 - - - // /// 0 1 2 3 4 5 6 7 - // ok = h.pushToRight(3); h.makeNull(3); - // /// - - 2 - 4 5 3 - - // /// 0 1 2 3 4 5 6 7 - // ASSERT_EQ(true, ok) << "push[3] failed"; - // b = 3; k = 0; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; - // b = 6; k = 3; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; - // } - // { - // SCOPED_TRACE("one bump"); - // h.table[7] = BucketType(7, 7, 0); - // /// - - 2 - 4 5 3 7 - // /// 0 1 2 3 4 5 6 7 - // // cout << "before push(4)" << h; - // ok = h.pushToRight(4); h.makeNull(4); - // //cout << "after push(4)" << h; - - // /// 7 - 2 - - 5 3 4 - // /// 0 1 2 3 4 5 6 7 - // ASSERT_EQ(true, ok) << "push[4] failed"; - // b = 4; k = 0; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; - // b = 7; k = 4; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; - // b = 0; k = 7; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; - // } - // { - // SCOPED_TRACE("two bumps"); - // h.table[1] = BucketType(6, 7, 0); - // h.table[3] = BucketType(1, 7, 0); - // b = 1; k = 6; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; - // b = 3; k = 1; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; - // /// 7 6 2 1 - 5 3 4 - // /// 0 1 2 3 4 5 6 7 - // ok = h.pushToRight(5); h.makeNull(5); - // /// 5 6 7 1 2 - 3 4 - // /// 0 1 2 3 4 5 6 7 - // ASSERT_EQ(true, ok) << "push[5] failed"; - // b = 5; k = 0; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; - // b = 0; k = 5; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; - // b = 1; k = 6; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; - // b = 2; k = 7; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; - // b = 3; k = 1; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; - // b = 4; k = 2; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; - // } - // { - // SCOPED_TRACE("no more space"); - // h.table[5] = BucketType(17); // just fill the place with something - // /// 5 6 7 1 2 17 3 4 - // /// 0 1 2 3 4 5 6 7 - // ok = h.pushToRight(1); - // ASSERT_EQ(false, ok) << "push[1] succeeded"; - - // } - //} - - // tests whether it is ok to move emptySlot to cur for a given firstBucket - // hash(cur) should lie between firstBucket and emptySlot, and - // hash(cur) should be withing H-1 of emptySlot - void canSwapCurWithEmpty(){ - IntHashTable h(4, 4); // size 16, H=4 - int f, c, hc, e; - { - string str = "non-circular. boundary involving f,c,e"; - SCOPED_TRACE(str); - f = 4, c = 5, hc = 5, e = 4; - - // f == e - f = e; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - f = 4; - - // f == c - c = f; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - // c < f - c = f - 1; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - // c = e - c = e; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - // c > e - c = e + 1; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - } - { - string str = "circular. boundary involving f,c,e"; - SCOPED_TRACE(str); - f = 14, c = 14, hc = 15, e = 1; - - // f == c - c = f; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - // c < f - c = f - 1; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - // c == e - c = e; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - // c > e - c = e + 1; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - } - - { - string str = "non-circular. hash(cur) not in range"; - SCOPED_TRACE(str); - f = 4, c = 5, e = 8; - - // hc < f - hc = f - 1; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - // hc == f - hc = f; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - // hc == e - hc = e; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - // hc > e - hc = e + 1; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - // hc < e - H - hc = e - h.H - 1; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - // hc == e - H - hc = e - h.H; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - } - { - string str = "circular. hash(cur) not in range"; - SCOPED_TRACE(str); - f = 12, c = 2, e = 3; - - // hc < f - hc = f - 1; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - // hc == f - hc = 4; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - // hc == e - hc = e; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - // hc > e - hc = e + 1; - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - // hc < e - H - hc = h.modSize(e - h.H - 1); - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - // hc == e - H - hc = h.modSize(e - h.H); - ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - } - { - string str = "hash(cur) in range"; - SCOPED_TRACE(str); - - // non-circular - f = 4, c = 8, e = 10; - - // hc > e-H - hc = e - h.H + 1; - ASSERT_EQ(true, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - - // circular - f = 11, c = 15, e = 2; - - // hc > e-H - hc = h.modSize(e - h.H + 1); - ASSERT_EQ(true, h.canSwapCurWithEmpty(f, c, hc, e)) - << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; - } - - } - - void pullFromLeft(){ - IntHashTable h(4, 4); // size 16, H=4 - ASSERT_EQ(16, h.size()); - ASSERT_EQ(0, h.population()); - ASSERT_EQ(4, h.H); - - int k, b, f; - BucketType* p; - - // test scenarios: - // - empty slot is within H-1 of firstBucket - // - the replacement item j is found, which is within H-1 of firstBucket - // - the replacement item j is found, which is not within H-1 of firstBucket - // - the replacement item j could not be found within H-1 of emptySlot - // - above, circular - { - SCOPED_TRACE("case #1"); - h.insert(4, 4); - h.insert(5, 5); - h.insert(6, 6); - ASSERT_EQ(3, h.population()); - - // 20 is hashed in 4, should be placed in 7 - k = 20; b = 7; - ASSERT_EQ(true, h.isNull(b)); - p = h.pullFromLeft(4, b); - ASSERT_NE(NULL, (int)p); - ASSERT_EQ(&h.table[b], p); - h.table[b] = BucketType(k, k, 0); - - // circular - h.clear(); - ASSERT_EQ(16, h.size()); - ASSERT_EQ(0, h.population()); - ASSERT_EQ(4, h.H); - - h.insert(14, 14); - h.insert(15, 15); - h.insert(31, 31); // hash value 15 - // 17 is hashed in 4, should be placed in 7 - k = 17; b = 1; - ASSERT_EQ(true, h.isNull(b)); - p = h.pullFromLeft(14, b); - ASSERT_NE(NULL, (int)p); // already within H - ASSERT_EQ(&h.table[b], p); - - } - { - SCOPED_TRACE("the replacement item j is found, which is within H-1 of firstBucket"); - h.clear(); - ASSERT_EQ(16, h.size()); - ASSERT_EQ(0, h.population()); - ASSERT_EQ(4, h.H); - - h.table[3] = BucketType(3); - h.table[4] = BucketType(4); - h.table[5] = BucketType(21); // hash value = 6 - h.table[6] = BucketType(22); // hash value = 5 - h.table[7] = BucketType(19); // hash value = 3 - - cout << "before pulling 8\n" << h; - p = h.pullFromLeft(3, 8); // empty slot should go to 5 - cout << "after pulling 8\n" << h; - ASSERT_EQ(&h.table[5], p); - ASSERT_EQ(3, h.table[3].key); - ASSERT_EQ(4, h.table[4].key); - ASSERT_EQ(0, h.table[5].key); - ASSERT_EQ(22, h.table[6].key); - ASSERT_EQ(19, h.table[7].key); - ASSERT_EQ(21, h.table[8].key); - - // now try a case where key hashes prohibit their move - h.clear(); - h.table[3] = BucketType(3); - h.table[4] = BucketType(4); - h.table[5] = BucketType(18); // hash value = 2 - h.table[6] = BucketType(17); // hash value = 1 - h.table[7] = BucketType(21); // hash value = 5 - - cout << "before pulling 8\n" << h; - p = h.pullFromLeft(3, 8); // empty slot should go to 4 - cout << "after pulling 8\n" << h; - ASSERT_EQ(&h.table[4], p); - ASSERT_EQ(3, h.table[3].key); - ASSERT_EQ(0, h.table[4].key); - ASSERT_EQ(18, h.table[5].key); - ASSERT_EQ(17, h.table[6].key); - ASSERT_EQ(4, h.table[7].key); - ASSERT_EQ(21, h.table[8].key); - - /// circular - h.clear(); - cout << "after clear\n" << h; - ASSERT_EQ(16, h.size()); - ASSERT_EQ(0, h.population()); - ASSERT_EQ(4, h.H); - - k = 14; h.table[k] = BucketType(k); - k = 15; h.table[k] = BucketType(k); - k = 0; h.table[k] = BucketType(159); // hash value 15 - k = 1; h.table[k] = BucketType(30); // hash value 14 - k = 2; h.table[k] = BucketType(k); - k = 3; h.table[k] = BucketType(k); - // for firstBucket 15, empty slot [4] should move to [2] - // because [1]'s hash value is 14 - b = 2; f = 15; - cout << "before moving from [4]\n" << h; - ASSERT_EQ(true, h.isNull(4)); - p = h.pullFromLeft(f, 4); - cout << "after moving from [4]\n" << h; - ASSERT_EQ(&h.table[2], p); - ASSERT_EQ(14, h.table[14].key); - ASSERT_EQ(15, h.table[15].key); - ASSERT_EQ(159, h.table[0].key); - ASSERT_EQ(30, h.table[1].key); - ASSERT_EQ(true, h.isNull(2)); - ASSERT_EQ(3, h.table[3].key); - ASSERT_EQ(2, h.table[4].key); - } - { - SCOPED_TRACE("the replacement item j is found, which is NOT within H-1 of firstBucket"); - h.clear(); - ASSERT_EQ(16, h.size()); - ASSERT_EQ(0, h.population()); - ASSERT_EQ(4, h.H); - - h.table[0] = BucketType(31); // hash to 15 - h.table[1] = BucketType(1); - h.table[2] = BucketType(17); // hash 1 - h.table[3] = BucketType(33); // hash 1 - for (int i = 4; i < 16; i++){ - h.table[i] = BucketType(i); - } - h.table[13] = BucketType(0); - /// keys: 31 1 17 33 4 5 6 7 8 9 10 11 12 0 14 15 - /// index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - cout << "before moving from [13]\n" << h; - p = h.pullFromLeft(1, 13); // empty slot should go to 4 - cout << "after moving from [13]\n" << h; - /// keys: 31 1 17 33 0 5 6 4 8 9 7 11 12 10 14 15 - /// index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - for (int i = 1; i < 16; i++){ - if (i == 4) - ASSERT_EQ(true, h.isNull(i)); - else if (i == 13) - ASSERT_EQ(10, h.table[i].key); - else if (i == 10) - ASSERT_EQ(7, h.table[i].key); - else if (i == 7) - ASSERT_EQ(4, h.table[i].key); - else if (i == 2) - ASSERT_EQ(17, h.table[i].key); - else if (i == 3) - ASSERT_EQ(33, h.table[i].key); - else - ASSERT_EQ(i, h.table[i].key); - } - - /// circular - h.clear(); - f = 4; - - for (int i = 0; i < 16; i++) h.table[i] = BucketType(i); - h.table[3].key = 0; // make [3] null - h.table[0].key = 0; // make [0] null - h.table[15].key = 17; // make [15].key hash to 1, cannot move - h.table[13].key = 0; // make [13] null - h.table[10].key = 25; // make [10].key hash to 25 - h.table[11].key = 0; // make [11] null - h.table[9].key = 19; // make [9].key hash to 3, cannot move - h.table[5].key = 2; // make [5].key hash to 2, cannot move - - /// keys: 0 1 2 0 4 2 6 7 8 19 25 0 12 0 14 17 - /// index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - cout << "before moving from [3]\n" << h; - p = h.pullFromLeft(4, 3); // empty slot should settle at [7] - cout << "after moving from [3]\n" << h; - /// keys: 0 14 2 1 4 2 6 0 8 19 7 0 25 0 12 17 - /// index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - - ASSERT_EQ(&h.table[7], p); - ASSERT_EQ(1, h.table[3].key); // first swap with 1 - ASSERT_EQ(14, h.table[1].key); // then swap with 14 - ASSERT_EQ(12, h.table[14].key); // then swap with 12 - ASSERT_EQ(25, h.table[12].key); // then swap with 10 - ASSERT_EQ(7, h.table[10].key); // then swap with 7 - ASSERT_EQ(0, h.table[7].key); // 7 is now empty slot - } - { - SCOPED_TRACE("the replacement item j could not be found within H-1 of emptySlot"); - h.clear(); - ASSERT_EQ(16, h.size()); - ASSERT_EQ(0, h.population()); - ASSERT_EQ(4, h.H); - - for (int i = 0; i < 15; i++){ - h.table[i] = BucketType(i * 16 + 2 ); // all hash to 2 - } - - p = h.pullFromLeft(3, 9); // should fail - ASSERT_EQ(NULL, (int)p); - p = h.pullFromLeft(8, 2); // should fail - ASSERT_EQ(NULL, (int)p); - } - } - - void circularDiff(){ - IntHashTable8 h(3,4); - ASSERT_EQ(8, h.size()); - ASSERT_EQ(0, h.circularDiff(4, 4)); - ASSERT_EQ(1, h.circularDiff(4, 5)); - ASSERT_EQ(3, h.circularDiff(4, 7)); - ASSERT_EQ(4, h.circularDiff(4, 0)); - ASSERT_EQ(6, h.circularDiff(4, 2)); - ASSERT_EQ(7, h.circularDiff(4, 3)); - } - - void insertRemoveLookup(){ - IntHashTable h(4, 4); // size 16, H=4 - ASSERT_EQ(16, h.size()); - ASSERT_EQ(0, h.population()); - ASSERT_EQ(4, h.H); - - // try to find an empty key - ASSERT_EQ(NULL, (int)h.lookupKey(7)); - ASSERT_EQ(NULL, (int)h.lookupKey(0)); - - // insert one key, find it, and remove it - h.put(5, 50); - ASSERT_EQ(true, h.contains(5)); - ASSERT_EQ(5, h.lookupKey(5)->key); - ASSERT_EQ(50, *h.get(5)); - ASSERT_EQ(1, h.population()); - ASSERT_EQ(16, h.size()); - h.remove(5); - ASSERT_EQ(false, h.contains(5)); - ASSERT_EQ(NULL, (int)h.lookupKey(5)); - ASSERT_EQ(NULL, (int)h.get(5)); - ASSERT_EQ(0, h.population()); - ASSERT_EQ(16, h.size()); - - // insert many keys that should not cause resize - h.clear(); - ASSERT_EQ(16, h.size()); - int numItemsWithoutResize = h.maxPopulationWithoutGrow(); - for (int i = 0; i < numItemsWithoutResize; i++){ - h.insert(16+i, 16+i); - } - ASSERT_EQ(16, h.size()); - ASSERT_EQ(numItemsWithoutResize, h.population()); - - // test remove - ASSERT_EQ(true, h.contains(21)); - h.remove(21); - ASSERT_EQ(false, h.contains(21)); - ASSERT_EQ(16, h.size()); - ASSERT_EQ(numItemsWithoutResize - 1, h.population()); - // put it back - h.insert(21, 21); - - // now add one more item, should cause grow() - h.insert(15, 15); - ASSERT_EQ(true, h.contains(15)); - ASSERT_EQ(32, h.size()); - ASSERT_EQ(numItemsWithoutResize + 1, h.population()); - - ///////// test resizing when a bucket gets more than H items - IntHashTable32 h2(4, 4); // keep the hash func mod 32 so that we can grow and rehash - for (int i = 0; i < h2.H; i++){ - h2.insert(16 * i + 2, i); // all hashed to 2 - } - ASSERT_EQ(16, h2.size()); - ASSERT_EQ(h2.H, h2.population()); - // this insert should cause grow - cout << "before inserting " << 16 * h2.H + 2 << endl << h2; - h2.insert(16 * h2.H + 2, h2.H); - cout << "after inserting " << 16 * h2.H + 2 << endl << h2; - ASSERT_EQ(32, h2.size()); - ASSERT_EQ(h2.H+1, h2.population()); - } - void nextPosInBucket(){ - IntHashTable h(4, 4); - int cur, bitmap, next; - - bitmap = 0xC0000000; cur = -1; next = -1; - ASSERT_EQ(next, h.nextPosInBucket(bitmap, cur)) - << "next(" << cur << ") not " << next << " in bitmap " << std::bitset<32>(bitmap); - - bitmap = 0xC0000000; cur = h.H; next = -1; - ASSERT_EQ(next, h.nextPosInBucket(bitmap, cur)) - << "next(" << cur << ") not " << next << " in bitmap " << std::bitset<32>(bitmap); - - bitmap = 0xC0000000; cur = h.H - 1; next = -1; - ASSERT_EQ(next, h.nextPosInBucket(bitmap, cur)) - << "next(" << cur << ") not " << next << " in bitmap " << std::bitset<32>(bitmap); - - bitmap = 0xC0000000; cur = 1; next = -1; - ASSERT_EQ(next, h.nextPosInBucket(bitmap, cur)) - << "next(" << cur << ") not " << next << " in bitmap " << std::bitset<32>(bitmap); - - bitmap = 0xC0000000; cur = 0; next = 1; - ASSERT_EQ(next, h.nextPosInBucket(bitmap, cur)) - << "next(" << cur << ") not " << next << " in bitmap " << std::bitset<32>(bitmap); - - bitmap = 0xC8800000; cur = 4; next = -1; // cur >= H - ASSERT_EQ(next, h.nextPosInBucket(bitmap, cur)) - << "next(" << cur << ") not " << next << " in bitmap " << std::bitset<32>(bitmap); - - bitmap = 0xC8000000; cur = 1; next = 4; - ASSERT_EQ(next, h.nextPosInBucket(bitmap, cur)) - << "next(" << cur << ") not " << next << " in bitmap " << std::bitset<32>(bitmap); - - bitmap = 0xC0000001; cur = 1; next = 31; - ASSERT_EQ(next, h.nextPosInBucket(bitmap, cur)) - << "next(" << cur << ") not " << next << " in bitmap " << std::bitset<32>(bitmap); - } - - void prevPosInBucket(){ - IntHashTable h(4, 4); - int cur, bitmap, prev; - - bitmap = 0xC0000000; cur = -1; prev = -1; - ASSERT_EQ(prev, h.prevPosInBucket(bitmap, cur)) - << "prev(" << cur << ") not " << prev << " in bitmap " << std::bitset<32>(bitmap); - - bitmap = 0xC0000000; cur = 0; prev = -1; - ASSERT_EQ(prev, h.prevPosInBucket(bitmap, cur)) - << "prev(" << cur << ") not " << prev << " in bitmap " << std::bitset<32>(bitmap); - - bitmap = 0xC0000000; cur = h.H; prev = -1; - ASSERT_EQ(prev, h.prevPosInBucket(bitmap, cur)) - << "prev(" << cur << ") not " << prev << " in bitmap " << std::bitset<32>(bitmap); - - bitmap = 0xC0000000; cur = 1; prev = 0; - ASSERT_EQ(prev, h.prevPosInBucket(bitmap, cur)) - << "prev(" << cur << ") not " << prev << " in bitmap " << std::bitset<32>(bitmap); - - bitmap = 0xA0000000; cur = 2; prev = 0; - ASSERT_EQ(prev, h.prevPosInBucket(bitmap, cur)) - << "prev(" << cur << ") not " << prev << " in bitmap " << std::bitset<32>(bitmap); - - bitmap = 0xC8000000; cur = 4; prev = -1; // cur >= H - ASSERT_EQ(prev, h.prevPosInBucket(bitmap, cur)) - << "prev(" << cur << ") not " << prev << " in bitmap " << std::bitset<32>(bitmap); - - bitmap = 0xD8000000; cur = 3; prev = 1; - ASSERT_EQ(prev, h.prevPosInBucket(bitmap, cur)) - << "prev(" << cur << ") not " << prev << " in bitmap " << std::bitset<32>(bitmap); - } - -}; -ADD_TEST_F(HopScotchHashTableTest, circularDiff); -ADD_TEST_F(HopScotchHashTableTest, nextPosInBucket); -ADD_TEST_F(HopScotchHashTableTest, prevPosInBucket); -ADD_TEST_F(HopScotchHashTableTest, canSwapCurWithEmpty); -ADD_TEST_F(HopScotchHashTableTest, pullFromLeft); -ADD_TEST_F(HopScotchHashTableTest, insertRemoveLookup); - } } \ No newline at end of file diff --git a/YASI_12/ds.HopScotchHashTable.test.h b/YASI_12/ds.HopScotchHashTable.test.h new file mode 100644 index 0000000..765b98a --- /dev/null +++ b/YASI_12/ds.HopScotchHashTable.test.h @@ -0,0 +1,640 @@ +#pragma once + +#if YASI_TEST_THIS_MODULE != 1 +#define YASI_TEST_THIS_MODULE 1 +#endif +#include "ds.HopScotchHashTable.h" +#include "ds.LinearProbingHashTable.test.h" + +namespace yasi{ + namespace ds{ + ////////// test HopScotchHashTable + // inherit tests from base class, just redefine the types + + class HopScotchHashTableTest : public yasi::Test + // : public LinearProbingHashTableTestBase< + // HopScotchHashTable >, + // HopScotchHashTable >, + // HopScotchHashTable > + //> + { + // + protected: + typedef HopScotchHashTable > IntHashTable8; + typedef HopScotchHashTable > IntHashTable16; + typedef IntHashTable16 IntHashTable; // default + typedef HopScotchHashTable > IntHashTable32; + typedef IntHashTable::BucketType BucketType; + typedef IntHashTable::BucketEntryPtr BucketEntryPtr; + typedef IntHashTable::Pair Pair; + + public: + // inherit all public tests + + //void pushToRight(){ + // IntHashTable h(3, 4); // size 8, H=4 + // ASSERT_EQ(8, h.size()) << "size not 8"; + // ASSERT_EQ(4, h.H) << "H not 4"; + // for (int i = 0; i < h.size(); i++){ + // ASSERT_EQ(0, h.table[i].key) << "key[" << i << "] not zero"; + // } + + // h.table[2] = BucketType(2, 2, 0); + // /// - - 2 - - - - - + // /// 0 1 2 3 4 5 6 7 + + // bool ok; + // int b, k; + // { + // SCOPED_TRACE("no bumps"); + // /// - - 2 - - - - - + // /// 0 1 2 3 4 5 6 7 + // ok = h.pushToRight(2); h.makeNull(2); + // /// - - - 2 - - - - + // /// 0 1 2 3 4 5 6 7 + // ASSERT_EQ(true, ok) << "push[2] failed"; + // // [2] will be 2 because + // b = 2; k = 0; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; + // b = 3; k = 2; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; + // // put it back + // /// - - 2 - - - - - + // /// 0 1 2 3 4 5 6 7 + // h.swap(2, 3); + // b = 2; k = 2; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; + // b = 3; k = 0; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; + + // h.table[3] = BucketType(3, 3, 0); + // h.table[4] = BucketType(4, 4, 0); + // h.table[5] = BucketType(5, 5, 0); + // /// - - 2 3 4 5 - - + // /// 0 1 2 3 4 5 6 7 + // ok = h.pushToRight(3); h.makeNull(3); + // /// - - 2 - 4 5 3 - + // /// 0 1 2 3 4 5 6 7 + // ASSERT_EQ(true, ok) << "push[3] failed"; + // b = 3; k = 0; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; + // b = 6; k = 3; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; + // } + // { + // SCOPED_TRACE("one bump"); + // h.table[7] = BucketType(7, 7, 0); + // /// - - 2 - 4 5 3 7 + // /// 0 1 2 3 4 5 6 7 + // // cout << "before push(4)" << h; + // ok = h.pushToRight(4); h.makeNull(4); + // //cout << "after push(4)" << h; + + // /// 7 - 2 - - 5 3 4 + // /// 0 1 2 3 4 5 6 7 + // ASSERT_EQ(true, ok) << "push[4] failed"; + // b = 4; k = 0; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; + // b = 7; k = 4; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; + // b = 0; k = 7; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; + // } + // { + // SCOPED_TRACE("two bumps"); + // h.table[1] = BucketType(6, 7, 0); + // h.table[3] = BucketType(1, 7, 0); + // b = 1; k = 6; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; + // b = 3; k = 1; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; + // /// 7 6 2 1 - 5 3 4 + // /// 0 1 2 3 4 5 6 7 + // ok = h.pushToRight(5); h.makeNull(5); + // /// 5 6 7 1 2 - 3 4 + // /// 0 1 2 3 4 5 6 7 + // ASSERT_EQ(true, ok) << "push[5] failed"; + // b = 5; k = 0; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; + // b = 0; k = 5; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; + // b = 1; k = 6; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; + // b = 2; k = 7; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; + // b = 3; k = 1; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; + // b = 4; k = 2; ASSERT_EQ(k, h.table[b].key) << "[" << b << "] not " << k; + // } + // { + // SCOPED_TRACE("no more space"); + // h.table[5] = BucketType(17); // just fill the place with something + // /// 5 6 7 1 2 17 3 4 + // /// 0 1 2 3 4 5 6 7 + // ok = h.pushToRight(1); + // ASSERT_EQ(false, ok) << "push[1] succeeded"; + + // } + //} + + // tests whether it is ok to move emptySlot to cur for a given firstBucket + // hash(cur) should lie between firstBucket and emptySlot, and + // hash(cur) should be withing H-1 of emptySlot + void canSwapCurWithEmpty(){ + IntHashTable h(4, 4); // size 16, H=4 + int f, c, hc, e; + { + string str = "non-circular. boundary involving f,c,e"; + SCOPED_TRACE(str); + f = 4, c = 5, hc = 5, e = 4; + + // f == e + f = e; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + f = 4; + + // f == c + c = f; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + // c < f + c = f - 1; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + // c = e + c = e; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + // c > e + c = e + 1; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + } + { + string str = "circular. boundary involving f,c,e"; + SCOPED_TRACE(str); + f = 14, c = 14, hc = 15, e = 1; + + // f == c + c = f; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + // c < f + c = f - 1; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + // c == e + c = e; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + // c > e + c = e + 1; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + } + + { + string str = "non-circular. hash(cur) not in range"; + SCOPED_TRACE(str); + f = 4, c = 5, e = 8; + + // hc < f + hc = f - 1; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + // hc == f + hc = f; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + // hc == e + hc = e; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + // hc > e + hc = e + 1; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + // hc < e - H + hc = e - h.H - 1; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + // hc == e - H + hc = e - h.H; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + } + { + string str = "circular. hash(cur) not in range"; + SCOPED_TRACE(str); + f = 12, c = 2, e = 3; + + // hc < f + hc = f - 1; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + // hc == f + hc = 4; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + // hc == e + hc = e; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + // hc > e + hc = e + 1; + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + // hc < e - H + hc = h.modSize(e - h.H - 1); + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + // hc == e - H + hc = h.modSize(e - h.H); + ASSERT_EQ(false, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + } + { + string str = "hash(cur) in range"; + SCOPED_TRACE(str); + + // non-circular + f = 4, c = 8, e = 10; + + // hc > e-H + hc = e - h.H + 1; + ASSERT_EQ(true, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + + // circular + f = 11, c = 15, e = 2; + + // hc > e-H + hc = h.modSize(e - h.H + 1); + ASSERT_EQ(true, h.canSwapCurWithEmpty(f, c, hc, e)) + << "canSwapCurWithEmpty(" << f << ", " << c << ", " << hc << ", " << e << ")"; + } + + } + + void pullFromLeft(){ + IntHashTable h(4, 4); // size 16, H=4 + ASSERT_EQ(16, h.size()); + ASSERT_EQ(0, h.population()); + ASSERT_EQ(4, h.H); + + int k, b, f; + BucketType* p; + + // test scenarios: + // - empty slot is within H-1 of firstBucket + // - the replacement item j is found, which is within H-1 of firstBucket + // - the replacement item j is found, which is not within H-1 of firstBucket + // - the replacement item j could not be found within H-1 of emptySlot + // - above, circular + { + SCOPED_TRACE("case #1"); + h.insert(4, 4); + h.insert(5, 5); + h.insert(6, 6); + ASSERT_EQ(3, h.population()); + + // 20 is hashed in 4, should be placed in 7 + k = 20; b = 7; + ASSERT_EQ(true, h.isNull(b)); + p = h.pullFromLeft(4, b); + ASSERT_NE(NULL, (int)p); + ASSERT_EQ(&h.table[b], p); + h.table[b] = BucketType(k, k, 0); + + // circular + h.clear(); + ASSERT_EQ(16, h.size()); + ASSERT_EQ(0, h.population()); + ASSERT_EQ(4, h.H); + + h.insert(14, 14); + h.insert(15, 15); + h.insert(31, 31); // hash value 15 + // 17 is hashed in 4, should be placed in 7 + k = 17; b = 1; + ASSERT_EQ(true, h.isNull(b)); + p = h.pullFromLeft(14, b); + ASSERT_NE(NULL, (int)p); // already within H + ASSERT_EQ(&h.table[b], p); + + } + { + SCOPED_TRACE("the replacement item j is found, which is within H-1 of firstBucket"); + h.clear(); + ASSERT_EQ(16, h.size()); + ASSERT_EQ(0, h.population()); + ASSERT_EQ(4, h.H); + + h.table[3] = BucketType(3); + h.table[4] = BucketType(4); + h.table[5] = BucketType(21); // hash value = 6 + h.table[6] = BucketType(22); // hash value = 5 + h.table[7] = BucketType(19); // hash value = 3 + + cout << "before pulling 8\n" << h; + p = h.pullFromLeft(3, 8); // empty slot should go to 5 + cout << "after pulling 8\n" << h; + ASSERT_EQ(&h.table[5], p); + ASSERT_EQ(3, h.table[3].key); + ASSERT_EQ(4, h.table[4].key); + ASSERT_EQ(0, h.table[5].key); + ASSERT_EQ(22, h.table[6].key); + ASSERT_EQ(19, h.table[7].key); + ASSERT_EQ(21, h.table[8].key); + + // now try a case where key hashes prohibit their move + h.clear(); + h.table[3] = BucketType(3); + h.table[4] = BucketType(4); + h.table[5] = BucketType(18); // hash value = 2 + h.table[6] = BucketType(17); // hash value = 1 + h.table[7] = BucketType(21); // hash value = 5 + + cout << "before pulling 8\n" << h; + p = h.pullFromLeft(3, 8); // empty slot should go to 4 + cout << "after pulling 8\n" << h; + ASSERT_EQ(&h.table[4], p); + ASSERT_EQ(3, h.table[3].key); + ASSERT_EQ(0, h.table[4].key); + ASSERT_EQ(18, h.table[5].key); + ASSERT_EQ(17, h.table[6].key); + ASSERT_EQ(4, h.table[7].key); + ASSERT_EQ(21, h.table[8].key); + + /// circular + h.clear(); + cout << "after clear\n" << h; + ASSERT_EQ(16, h.size()); + ASSERT_EQ(0, h.population()); + ASSERT_EQ(4, h.H); + + k = 14; h.table[k] = BucketType(k); + k = 15; h.table[k] = BucketType(k); + k = 0; h.table[k] = BucketType(159); // hash value 15 + k = 1; h.table[k] = BucketType(30); // hash value 14 + k = 2; h.table[k] = BucketType(k); + k = 3; h.table[k] = BucketType(k); + // for firstBucket 15, empty slot [4] should move to [2] + // because [1]'s hash value is 14 + b = 2; f = 15; + cout << "before moving from [4]\n" << h; + ASSERT_EQ(true, h.isNull(4)); + p = h.pullFromLeft(f, 4); + cout << "after moving from [4]\n" << h; + ASSERT_EQ(&h.table[2], p); + ASSERT_EQ(14, h.table[14].key); + ASSERT_EQ(15, h.table[15].key); + ASSERT_EQ(159, h.table[0].key); + ASSERT_EQ(30, h.table[1].key); + ASSERT_EQ(true, h.isNull(2)); + ASSERT_EQ(3, h.table[3].key); + ASSERT_EQ(2, h.table[4].key); + } + { + SCOPED_TRACE("the replacement item j is found, which is NOT within H-1 of firstBucket"); + h.clear(); + ASSERT_EQ(16, h.size()); + ASSERT_EQ(0, h.population()); + ASSERT_EQ(4, h.H); + + h.table[0] = BucketType(31); // hash to 15 + h.table[1] = BucketType(1); + h.table[2] = BucketType(17); // hash 1 + h.table[3] = BucketType(33); // hash 1 + for (int i = 4; i < 16; i++){ + h.table[i] = BucketType(i); + } + h.table[13] = BucketType(0); + /// keys: 31 1 17 33 4 5 6 7 8 9 10 11 12 0 14 15 + /// index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + cout << "before moving from [13]\n" << h; + p = h.pullFromLeft(1, 13); // empty slot should go to 4 + cout << "after moving from [13]\n" << h; + /// keys: 31 1 17 33 0 5 6 4 8 9 7 11 12 10 14 15 + /// index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + for (int i = 1; i < 16; i++){ + if (i == 4) + ASSERT_EQ(true, h.isNull(i)); + else if (i == 13) + ASSERT_EQ(10, h.table[i].key); + else if (i == 10) + ASSERT_EQ(7, h.table[i].key); + else if (i == 7) + ASSERT_EQ(4, h.table[i].key); + else if (i == 2) + ASSERT_EQ(17, h.table[i].key); + else if (i == 3) + ASSERT_EQ(33, h.table[i].key); + else + ASSERT_EQ(i, h.table[i].key); + } + + /// circular + h.clear(); + f = 4; + + for (int i = 0; i < 16; i++) h.table[i] = BucketType(i); + h.table[3].key = 0; // make [3] null + h.table[0].key = 0; // make [0] null + h.table[15].key = 17; // make [15].key hash to 1, cannot move + h.table[13].key = 0; // make [13] null + h.table[10].key = 25; // make [10].key hash to 25 + h.table[11].key = 0; // make [11] null + h.table[9].key = 19; // make [9].key hash to 3, cannot move + h.table[5].key = 2; // make [5].key hash to 2, cannot move + + /// keys: 0 1 2 0 4 2 6 7 8 19 25 0 12 0 14 17 + /// index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + cout << "before moving from [3]\n" << h; + p = h.pullFromLeft(4, 3); // empty slot should settle at [7] + cout << "after moving from [3]\n" << h; + /// keys: 0 14 2 1 4 2 6 0 8 19 7 0 25 0 12 17 + /// index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + + ASSERT_EQ(&h.table[7], p); + ASSERT_EQ(1, h.table[3].key); // first swap with 1 + ASSERT_EQ(14, h.table[1].key); // then swap with 14 + ASSERT_EQ(12, h.table[14].key); // then swap with 12 + ASSERT_EQ(25, h.table[12].key); // then swap with 10 + ASSERT_EQ(7, h.table[10].key); // then swap with 7 + ASSERT_EQ(0, h.table[7].key); // 7 is now empty slot + } + { + SCOPED_TRACE("the replacement item j could not be found within H-1 of emptySlot"); + h.clear(); + ASSERT_EQ(16, h.size()); + ASSERT_EQ(0, h.population()); + ASSERT_EQ(4, h.H); + + for (int i = 0; i < 15; i++){ + h.table[i] = BucketType(i * 16 + 2); // all hash to 2 + } + + p = h.pullFromLeft(3, 9); // should fail + ASSERT_EQ(NULL, (int)p); + p = h.pullFromLeft(8, 2); // should fail + ASSERT_EQ(NULL, (int)p); + } + } + + void circularDiff(){ + IntHashTable8 h(3, 4); + ASSERT_EQ(8, h.size()); + ASSERT_EQ(0, h.circularDiff(4, 4)); + ASSERT_EQ(1, h.circularDiff(4, 5)); + ASSERT_EQ(3, h.circularDiff(4, 7)); + ASSERT_EQ(4, h.circularDiff(4, 0)); + ASSERT_EQ(6, h.circularDiff(4, 2)); + ASSERT_EQ(7, h.circularDiff(4, 3)); + } + + void insertRemoveLookup(){ + IntHashTable h(4, 4); // size 16, H=4 + ASSERT_EQ(16, h.size()); + ASSERT_EQ(0, h.population()); + ASSERT_EQ(4, h.H); + + // try to find an empty key + ASSERT_EQ(NULL, (int)h.lookupKey(7)); + ASSERT_EQ(NULL, (int)h.lookupKey(0)); + + // insert one key, find it, and remove it + h.put(5, 50); + ASSERT_EQ(true, h.contains(5)); + ASSERT_EQ(5, h.lookupKey(5)->key); + ASSERT_EQ(50, *h.get(5)); + ASSERT_EQ(1, h.population()); + ASSERT_EQ(16, h.size()); + h.remove(5); + ASSERT_EQ(false, h.contains(5)); + ASSERT_EQ(NULL, (int)h.lookupKey(5)); + ASSERT_EQ(NULL, (int)h.get(5)); + ASSERT_EQ(0, h.population()); + ASSERT_EQ(16, h.size()); + + // insert many keys that should not cause resize + h.clear(); + ASSERT_EQ(16, h.size()); + int numItemsWithoutResize = h.maxPopulationWithoutGrow(); + for (int i = 0; i < numItemsWithoutResize; i++){ + h.insert(16 + i, 16 + i); + } + ASSERT_EQ(16, h.size()); + ASSERT_EQ(numItemsWithoutResize, h.population()); + + // test remove + ASSERT_EQ(true, h.contains(21)); + h.remove(21); + ASSERT_EQ(false, h.contains(21)); + ASSERT_EQ(16, h.size()); + ASSERT_EQ(numItemsWithoutResize - 1, h.population()); + // put it back + h.insert(21, 21); + + // now add one more item, should cause grow() + h.insert(15, 15); + ASSERT_EQ(true, h.contains(15)); + ASSERT_EQ(32, h.size()); + ASSERT_EQ(numItemsWithoutResize + 1, h.population()); + + ///////// test resizing when a bucket gets more than H items + IntHashTable32 h2(4, 4); // keep the hash func mod 32 so that we can grow and rehash + for (int i = 0; i < h2.H; i++){ + h2.insert(16 * i + 2, i); // all hashed to 2 + } + ASSERT_EQ(16, h2.size()); + ASSERT_EQ(h2.H, h2.population()); + // this insert should cause grow + cout << "before inserting " << 16 * h2.H + 2 << endl << h2; + h2.insert(16 * h2.H + 2, h2.H); + cout << "after inserting " << 16 * h2.H + 2 << endl << h2; + ASSERT_EQ(32, h2.size()); + ASSERT_EQ(h2.H + 1, h2.population()); + } + void nextPosInBucket(){ + IntHashTable h(4, 4); + int cur, bitmap, next; + + bitmap = 0xC0000000; cur = -1; next = -1; + ASSERT_EQ(next, h.nextPosInBucket(bitmap, cur)) + << "next(" << cur << ") not " << next << " in bitmap " << std::bitset<32>(bitmap); + + bitmap = 0xC0000000; cur = h.H; next = -1; + ASSERT_EQ(next, h.nextPosInBucket(bitmap, cur)) + << "next(" << cur << ") not " << next << " in bitmap " << std::bitset<32>(bitmap); + + bitmap = 0xC0000000; cur = h.H - 1; next = -1; + ASSERT_EQ(next, h.nextPosInBucket(bitmap, cur)) + << "next(" << cur << ") not " << next << " in bitmap " << std::bitset<32>(bitmap); + + bitmap = 0xC0000000; cur = 1; next = -1; + ASSERT_EQ(next, h.nextPosInBucket(bitmap, cur)) + << "next(" << cur << ") not " << next << " in bitmap " << std::bitset<32>(bitmap); + + bitmap = 0xC0000000; cur = 0; next = 1; + ASSERT_EQ(next, h.nextPosInBucket(bitmap, cur)) + << "next(" << cur << ") not " << next << " in bitmap " << std::bitset<32>(bitmap); + + bitmap = 0xC8800000; cur = 4; next = -1; // cur >= H + ASSERT_EQ(next, h.nextPosInBucket(bitmap, cur)) + << "next(" << cur << ") not " << next << " in bitmap " << std::bitset<32>(bitmap); + + bitmap = 0xC8000000; cur = 1; next = 4; + ASSERT_EQ(next, h.nextPosInBucket(bitmap, cur)) + << "next(" << cur << ") not " << next << " in bitmap " << std::bitset<32>(bitmap); + + bitmap = 0xC0000001; cur = 1; next = 31; + ASSERT_EQ(next, h.nextPosInBucket(bitmap, cur)) + << "next(" << cur << ") not " << next << " in bitmap " << std::bitset<32>(bitmap); + } + + void prevPosInBucket(){ + IntHashTable h(4, 4); + int cur, bitmap, prev; + + bitmap = 0xC0000000; cur = -1; prev = -1; + ASSERT_EQ(prev, h.prevPosInBucket(bitmap, cur)) + << "prev(" << cur << ") not " << prev << " in bitmap " << std::bitset<32>(bitmap); + + bitmap = 0xC0000000; cur = 0; prev = -1; + ASSERT_EQ(prev, h.prevPosInBucket(bitmap, cur)) + << "prev(" << cur << ") not " << prev << " in bitmap " << std::bitset<32>(bitmap); + + bitmap = 0xC0000000; cur = h.H; prev = -1; + ASSERT_EQ(prev, h.prevPosInBucket(bitmap, cur)) + << "prev(" << cur << ") not " << prev << " in bitmap " << std::bitset<32>(bitmap); + + bitmap = 0xC0000000; cur = 1; prev = 0; + ASSERT_EQ(prev, h.prevPosInBucket(bitmap, cur)) + << "prev(" << cur << ") not " << prev << " in bitmap " << std::bitset<32>(bitmap); + + bitmap = 0xA0000000; cur = 2; prev = 0; + ASSERT_EQ(prev, h.prevPosInBucket(bitmap, cur)) + << "prev(" << cur << ") not " << prev << " in bitmap " << std::bitset<32>(bitmap); + + bitmap = 0xC8000000; cur = 4; prev = -1; // cur >= H + ASSERT_EQ(prev, h.prevPosInBucket(bitmap, cur)) + << "prev(" << cur << ") not " << prev << " in bitmap " << std::bitset<32>(bitmap); + + bitmap = 0xD8000000; cur = 3; prev = 1; + ASSERT_EQ(prev, h.prevPosInBucket(bitmap, cur)) + << "prev(" << cur << ") not " << prev << " in bitmap " << std::bitset<32>(bitmap); + } + + }; + ADD_TEST_F(HopScotchHashTableTest, circularDiff); + ADD_TEST_F(HopScotchHashTableTest, nextPosInBucket); + ADD_TEST_F(HopScotchHashTableTest, prevPosInBucket); + ADD_TEST_F(HopScotchHashTableTest, canSwapCurWithEmpty); + ADD_TEST_F(HopScotchHashTableTest, pullFromLeft); + ADD_TEST_F(HopScotchHashTableTest, insertRemoveLookup); + } +} \ No newline at end of file diff --git a/YASI_12/ds.IntLinearProbingHashTable.h b/YASI_12/ds.IntLinearProbingHashTable.h index 7d0f1ff..7a15ce6 100644 --- a/YASI_12/ds.IntLinearProbingHashTable.h +++ b/YASI_12/ds.IntLinearProbingHashTable.h @@ -1,6 +1,10 @@ #pragma once #include "common.h" #include "ds.LinearProbingHashTable.h" + +// enable-disable testing classes in this file +#include "test.this.module.h" + using namespace std; namespace yasi{ @@ -58,33 +62,7 @@ namespace yasi{ }; - ////////// test IntLinearProbingHashTable - // inherit tests from base class, just redefine the types - - class IntLinearProbingHashTableTest : public LinearProbingHashTableTestBase< - IntLinearProbingHashTable >, - IntLinearProbingHashTable >, - IntLinearProbingHashTable > - >{ - // - protected: - typedef IntLinearProbingHashTable > IntHashTable8; - typedef IntLinearProbingHashTable > IntHashTable16; - typedef IntHashTable16 IntHashTable; // default - typedef IntLinearProbingHashTable > IntHashTable32; - typedef IntHashTable::BucketType BucketType; - typedef IntHashTable::BucketEntryPtr BucketEntryPtr; - typedef IntHashTable::Pair Pair; - public: - // inherit all public tests - }; - ADD_TEST_F(IntLinearProbingHashTableTest, insert); - ADD_TEST_F(IntLinearProbingHashTableTest, copyTable); - ADD_TEST_F(IntLinearProbingHashTableTest, growCondition); - ADD_TEST_F(IntLinearProbingHashTableTest, shrinkCondition); - ADD_TEST_F(IntLinearProbingHashTableTest, isKeyZero); - ADD_TEST_F(IntLinearProbingHashTableTest, remove); } } \ No newline at end of file diff --git a/YASI_12/ds.IntLinearProbingHashTable.test.h b/YASI_12/ds.IntLinearProbingHashTable.test.h new file mode 100644 index 0000000..e59b4ec --- /dev/null +++ b/YASI_12/ds.IntLinearProbingHashTable.test.h @@ -0,0 +1,39 @@ +#pragma once + +#if YASI_TEST_THIS_MODULE != 1 +#define YASI_TEST_THIS_MODULE 1 +#endif +#include "ds.IntLinearProbingHashTable.h" +#include "ds.LinearProbingHashTable.test.h" + +namespace yasi{ + namespace ds{ + ////////// test IntLinearProbingHashTable + // inherit tests from base class, just redefine the types + + class IntLinearProbingHashTableTest : public LinearProbingHashTableTestBase< + IntLinearProbingHashTable >, + IntLinearProbingHashTable >, + IntLinearProbingHashTable > + >{ + // + protected: + typedef IntLinearProbingHashTable > IntHashTable8; + typedef IntLinearProbingHashTable > IntHashTable16; + typedef IntHashTable16 IntHashTable; // default + typedef IntLinearProbingHashTable > IntHashTable32; + typedef IntHashTable::BucketType BucketType; + typedef IntHashTable::BucketEntryPtr BucketEntryPtr; + typedef IntHashTable::Pair Pair; + public: + // inherit all public tests + + }; + ADD_TEST_F(IntLinearProbingHashTableTest, insert); + ADD_TEST_F(IntLinearProbingHashTableTest, copyTable); + ADD_TEST_F(IntLinearProbingHashTableTest, growCondition); + ADD_TEST_F(IntLinearProbingHashTableTest, shrinkCondition); + ADD_TEST_F(IntLinearProbingHashTableTest, isKeyZero); + ADD_TEST_F(IntLinearProbingHashTableTest, remove); + } +} \ No newline at end of file diff --git a/YASI_12/ds.LinearProbingHashTable.h b/YASI_12/ds.LinearProbingHashTable.h index fcecaa5..4fe1f48 100644 --- a/YASI_12/ds.LinearProbingHashTable.h +++ b/YASI_12/ds.LinearProbingHashTable.h @@ -4,55 +4,16 @@ #include #include #include + +// enable-disable testing classes in this file +#include "test.this.module.h" + using namespace std; namespace yasi{ namespace ds{ - namespace testhash{ - template - class IdentityFunction : public IHashFunction < Key > { - public: - virtual int operator()(const Key& n) const override{ - return n; - } - }; - template - class Mod8Function : public IHashFunction < Key > { - public: - static const unsigned int modulus = 8; - virtual int operator()(const Key& n) const override{ - return n % 8; - } - }; - template - class Mod16Function : public IHashFunction < Key > { - public: - static const unsigned int modulus = 16; - virtual int operator()(const Key& n) const override{ - return n % 16; - } - }; - template - class Mod32Function : public IHashFunction < Key > { - public: - static const unsigned int modulus = 32; - virtual int operator()(const Key& n) const override{ - return n % 32; - } - }; - // mod 16 by default - template - class ModFunction : public IHashFunction < Key > { - public: - unsigned int modulus; - ModFunction() : modulus(16){} - virtual int operator()(const Key& n) const override{ - return n % modulus; - } - }; - } // namespace testhash using namespace testhash; @@ -70,8 +31,8 @@ namespace ds{ HashFunction > { ///////////////// enable testing /////////////////// template - friend class LinearProbingHashTableTestBase; - friend class LinearProbingHashTableTest; + FRIEND_TEST_CLASS(LinearProbingHashTableTestBase); + FRIEND_TEST_CLASS(LinearProbingHashTableTest); public: // an EntryType object is stored in the table @@ -450,581 +411,7 @@ namespace ds{ }; - ///////////////////////// testing //////////////////////// - - typedef LinearProbingHashTable > _LinProbHT8; - typedef LinearProbingHashTable > _LinProbHT16; - typedef LinearProbingHashTable > _LinProbHT32; - - template< - class IntHashTable8 = _LinProbHT8, - class IntHashTable16 = _LinProbHT16, - class IntHashTable32 = _LinProbHT32 - > - class LinearProbingHashTableTestBase : public yasi::Test{ - protected: - //typedef LinearProbingHashTable > IntHashTable8; - //typedef LinearProbingHashTable > IntHashTable16; - //typedef LinearProbingHashTable > IntHashTable32; - typedef IntHashTable16 IntHashTable; // default - typedef typename IntHashTable::BucketType BucketType; - typedef typename IntHashTable::BucketEntryPtr BucketEntryPtr; - typedef typename IntHashTable::Pair Pair; - - - public: - - virtual void insert(){ - - 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"); - 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); - BucketEntryPtr p = h.insertKey(key); - 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(key, h.key(bucket)) << key << " not in bucket " << bucket; - } - { - string str = "insert 23, which should go to bucket 8"; - int key = 23; - int bucket = 8; - SCOPED_TRACE(str); - BucketEntryPtr p = h.insertKey(key); - 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(key, h.key(bucket)) << 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); - 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(key, h.key(bucket)) << key << " not in bucket " << bucket; - ASSERT_EQ(key, h.value(bucket)) << 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); - BucketEntryPtr p = h.insertKey(key); - 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(key, h.key(bucket)) << 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); - BucketEntryPtr p = h.insertKey(key); - 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(key, h.key(bucket)) << 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(); - BucketEntryPtr 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._zeroKeyEntry) << " zero key not in zeroPair"; - ASSERT_EQ(prevPop + 1, h.population()) << "population did not increase"; - ASSERT_EQ(key, h._zeroKeyEntry.key) << key << " not in zeroPair"; - } - - } - - virtual void copyTable(){ - - IntHashTable16 h1(4); - IntHashTable32 h2(5); - - 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.isNull(i)){ - int key = h1.key(i); - int value = h1.value(i); - BucketEntryPtr 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); - } - - virtual void remove(){ - - 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.key(5)) << "[5] not " << 5; - ASSERT_EQ(6, h.key(6)) << "[6] not " << 6; - ASSERT_EQ(7, h.key(7)) << "[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.key(5)) << "key " << 5 << " misplaced"; - ASSERT_EQ(7, h.key(7)) << "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.key(6)) << "[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.key(5)) << "key " << 85 << " misplaced"; - ASSERT_EQ(true, h.isNull(6)) << "[6] not NULL"; - ASSERT_EQ(7, h.key(7)) << "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.key(6)) << "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.key(5)) << "key " << 85 << " misplaced"; - ASSERT_EQ(true, h.isNull(6)) << "[6) not NULL"; - ASSERT_EQ(7, h.key(7)) << "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.key(5)) << "[5] not 5"; - ASSERT_EQ(true, h.isNull(6)) << "[6] not NULL"; - ASSERT_EQ(7, h.key(7)) << "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.key(5)) << "[5] not 21"; - ASSERT_EQ(37, h.key(6)) << "[6] not 37"; - ASSERT_EQ(7, h.key(7)) << "[7] not 7"; - ASSERT_EQ(53, h.key(8)) << "[8] not 53"; - ASSERT_EQ(23, h.key(9)) << "[9] not 23"; - ASSERT_EQ(39, h.key(10)) << "[10] not 39"; - ASSERT_EQ(true, h.isNull(11)) << "[11] not NULL"; - ASSERT_EQ(12, h.key(12)) << "[12] not 12"; - ASSERT_EQ(true, h.isNull(13)) << "[13] not NULL"; - ASSERT_EQ(14, h.key(14)) << "[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.key(5)) << "[5] not 37"; - ASSERT_EQ(53, h.key(6)) << "[6] not 53"; - ASSERT_EQ(7, h.key(7)) << "[7] not 7"; - ASSERT_EQ(23, h.key(8)) << "[8] not 23"; - ASSERT_EQ(39, h.key(9)) << "[9] not 39"; - ASSERT_EQ(true, h.isNull(10)) << "[10] not NULL"; - ASSERT_EQ(true, h.isNull(11)) << "[11] not NULL"; - ASSERT_EQ(12, h.key(12)) << "[12] not 12"; - ASSERT_EQ(true, h.isNull(13)) << "[13] not NULL"; - ASSERT_EQ(14, h.key(14)) << "[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.key(5)) << "[5] not 37"; - ASSERT_EQ(53, h.key(6)) << "[6] not 53"; - ASSERT_EQ(7, h.key(7)) << "[7] not 7"; - ASSERT_EQ(39, h.key(8)) << "[8] not 39"; - ASSERT_EQ(true, h.isNull(9)) << "[9] not NULL"; - ASSERT_EQ(true, h.isNull(10)) << "[10] not NULL"; - ASSERT_EQ(true, h.isNull(11)) << "[11] not NULL"; - ASSERT_EQ(12, h.key(12)) << "[12] not 12"; - ASSERT_EQ(true, h.isNull(13)) << "[13] not NULL"; - ASSERT_EQ(14, h.key(14)) << "[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.key(1)) << "[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.key(0)) << "[0] not 62"; - ASSERT_EQ(17, h.key(1)) << "[1] not 17"; - ASSERT_EQ(true, h.isNull(2)) << "[2] not NULL"; - ASSERT_EQ(14, h.key(14)) << "[14] not 14"; - ASSERT_EQ(30, h.key(15)) << "[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.key(1)) << "[1] not 17"; - ASSERT_EQ(true, h.isNull(0)) << "[0] not NULL"; - ASSERT_EQ(14, h.key(14)) << "[14] not 14"; - ASSERT_EQ(62, h.key(15)) << "[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.key(14)) << "[14] not 62"; - ASSERT_EQ(true, h.isNull(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._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._zeroKeyEntry.key) << "key of zero element non-zero"; - } - { - SCOPED_TRACE("remove from an all-filled table"); - // create a pathological table - BucketType* badTable = h.allocTable(8); // 8 entries in the table - for (int i = 0; i < 8; i++){ - 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 - 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(true, h2.isNull(i)) << "[" << i << "] not NULL"; - } - else - ASSERT_EQ(5, h2.key(i)) << "[" << i << "] not 5"; - } - } - - // cleanup - } - - } - - virtual void growCondition(){ - IntHashTable16 h1(4); - - 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; - } - } - virtual void shrinkCondition(){ - IntHashTable16 h1(4); - - 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; - } - } - - virtual 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; } - //}; - // - //// 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"; - - - { - class A{ - public: - virtual void x() = 0; - }; - - class BB :public A{ - public: - int key; - int value; - void x() override {} - }; - - //typedef BB B; - //typedef KVPairSimple B; - typedef KVPair B; - int n = 1; - int s = sizeof(B); - B* pArr = (B*)malloc(n * s); - //B* pArr = new B[n]; - memset(pArr, 0, s * n); - //std::fill((char*)pArr, (char*)(pArr+n), 0); - //for (int i = 0; i < n; i++){ - // pArr[i].key = 0xAAAAAAAA; - // pArr[i].value = 0xBBBBBBBB; - //} - //delete[] pArr; - free(pArr); - } - - //ASSERT_EQ(0, 1) << "uncomment other tests"; - } - }; - - class LinearProbingHashTableTest : public LinearProbingHashTableTestBase < _LinProbHT8, _LinProbHT16, _LinProbHT32> { - public: - // inherit all tests from base - }; - ADD_TEST_F(LinearProbingHashTableTest, insert); - ADD_TEST_F(LinearProbingHashTableTest, copyTable); - ADD_TEST_F(LinearProbingHashTableTest, growCondition); - ADD_TEST_F(LinearProbingHashTableTest, shrinkCondition); - ADD_TEST_F(LinearProbingHashTableTest, isKeyZero); - ADD_TEST_F(LinearProbingHashTableTest, remove); diff --git a/YASI_12/ds.LinearProbingHashTable.test.h b/YASI_12/ds.LinearProbingHashTable.test.h new file mode 100644 index 0000000..6536ce6 --- /dev/null +++ b/YASI_12/ds.LinearProbingHashTable.test.h @@ -0,0 +1,586 @@ +#pragma once + +#if YASI_TEST_THIS_MODULE != 1 +#define YASI_TEST_THIS_MODULE 1 +#endif +#include "ds.LinearProbingHashTable.h" + +namespace yasi{ + namespace ds{ + ///////////////////////// testing //////////////////////// + + typedef LinearProbingHashTable > _LinProbHT8; + typedef LinearProbingHashTable > _LinProbHT16; + typedef LinearProbingHashTable > _LinProbHT32; + + template< + class IntHashTable8 = _LinProbHT8, + class IntHashTable16 = _LinProbHT16, + class IntHashTable32 = _LinProbHT32 + > + class LinearProbingHashTableTestBase : public yasi::Test{ + protected: + //typedef LinearProbingHashTable > IntHashTable8; + //typedef LinearProbingHashTable > IntHashTable16; + //typedef LinearProbingHashTable > IntHashTable32; + typedef IntHashTable16 IntHashTable; // default + typedef typename IntHashTable::BucketType BucketType; + typedef typename IntHashTable::BucketEntryPtr BucketEntryPtr; + typedef typename IntHashTable::Pair Pair; + + + public: + + virtual void insert(){ + + 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"); + 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); + BucketEntryPtr p = h.insertKey(key); + 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(key, h.key(bucket)) << key << " not in bucket " << bucket; + } + { + string str = "insert 23, which should go to bucket 8"; + int key = 23; + int bucket = 8; + SCOPED_TRACE(str); + BucketEntryPtr p = h.insertKey(key); + 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(key, h.key(bucket)) << 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); + 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(key, h.key(bucket)) << key << " not in bucket " << bucket; + ASSERT_EQ(key, h.value(bucket)) << 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); + BucketEntryPtr p = h.insertKey(key); + 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(key, h.key(bucket)) << 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); + BucketEntryPtr p = h.insertKey(key); + 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(key, h.key(bucket)) << 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(); + BucketEntryPtr 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._zeroKeyEntry) << " zero key not in zeroPair"; + ASSERT_EQ(prevPop + 1, h.population()) << "population did not increase"; + ASSERT_EQ(key, h._zeroKeyEntry.key) << key << " not in zeroPair"; + } + + } + + virtual void copyTable(){ + + IntHashTable16 h1(4); + IntHashTable32 h2(5); + + 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.isNull(i)){ + int key = h1.key(i); + int value = h1.value(i); + BucketEntryPtr 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); + } + + virtual void remove(){ + + 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.key(5)) << "[5] not " << 5; + ASSERT_EQ(6, h.key(6)) << "[6] not " << 6; + ASSERT_EQ(7, h.key(7)) << "[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.key(5)) << "key " << 5 << " misplaced"; + ASSERT_EQ(7, h.key(7)) << "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.key(6)) << "[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.key(5)) << "key " << 85 << " misplaced"; + ASSERT_EQ(true, h.isNull(6)) << "[6] not NULL"; + ASSERT_EQ(7, h.key(7)) << "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.key(6)) << "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.key(5)) << "key " << 85 << " misplaced"; + ASSERT_EQ(true, h.isNull(6)) << "[6) not NULL"; + ASSERT_EQ(7, h.key(7)) << "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.key(5)) << "[5] not 5"; + ASSERT_EQ(true, h.isNull(6)) << "[6] not NULL"; + ASSERT_EQ(7, h.key(7)) << "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.key(5)) << "[5] not 21"; + ASSERT_EQ(37, h.key(6)) << "[6] not 37"; + ASSERT_EQ(7, h.key(7)) << "[7] not 7"; + ASSERT_EQ(53, h.key(8)) << "[8] not 53"; + ASSERT_EQ(23, h.key(9)) << "[9] not 23"; + ASSERT_EQ(39, h.key(10)) << "[10] not 39"; + ASSERT_EQ(true, h.isNull(11)) << "[11] not NULL"; + ASSERT_EQ(12, h.key(12)) << "[12] not 12"; + ASSERT_EQ(true, h.isNull(13)) << "[13] not NULL"; + ASSERT_EQ(14, h.key(14)) << "[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.key(5)) << "[5] not 37"; + ASSERT_EQ(53, h.key(6)) << "[6] not 53"; + ASSERT_EQ(7, h.key(7)) << "[7] not 7"; + ASSERT_EQ(23, h.key(8)) << "[8] not 23"; + ASSERT_EQ(39, h.key(9)) << "[9] not 39"; + ASSERT_EQ(true, h.isNull(10)) << "[10] not NULL"; + ASSERT_EQ(true, h.isNull(11)) << "[11] not NULL"; + ASSERT_EQ(12, h.key(12)) << "[12] not 12"; + ASSERT_EQ(true, h.isNull(13)) << "[13] not NULL"; + ASSERT_EQ(14, h.key(14)) << "[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.key(5)) << "[5] not 37"; + ASSERT_EQ(53, h.key(6)) << "[6] not 53"; + ASSERT_EQ(7, h.key(7)) << "[7] not 7"; + ASSERT_EQ(39, h.key(8)) << "[8] not 39"; + ASSERT_EQ(true, h.isNull(9)) << "[9] not NULL"; + ASSERT_EQ(true, h.isNull(10)) << "[10] not NULL"; + ASSERT_EQ(true, h.isNull(11)) << "[11] not NULL"; + ASSERT_EQ(12, h.key(12)) << "[12] not 12"; + ASSERT_EQ(true, h.isNull(13)) << "[13] not NULL"; + ASSERT_EQ(14, h.key(14)) << "[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.key(1)) << "[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.key(0)) << "[0] not 62"; + ASSERT_EQ(17, h.key(1)) << "[1] not 17"; + ASSERT_EQ(true, h.isNull(2)) << "[2] not NULL"; + ASSERT_EQ(14, h.key(14)) << "[14] not 14"; + ASSERT_EQ(30, h.key(15)) << "[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.key(1)) << "[1] not 17"; + ASSERT_EQ(true, h.isNull(0)) << "[0] not NULL"; + ASSERT_EQ(14, h.key(14)) << "[14] not 14"; + ASSERT_EQ(62, h.key(15)) << "[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.key(14)) << "[14] not 62"; + ASSERT_EQ(true, h.isNull(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._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._zeroKeyEntry.key) << "key of zero element non-zero"; + } + { + SCOPED_TRACE("remove from an all-filled table"); + // create a pathological table + BucketType* badTable = h.allocTable(8); // 8 entries in the table + for (int i = 0; i < 8; i++){ + 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 + 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(true, h2.isNull(i)) << "[" << i << "] not NULL"; + } + else + ASSERT_EQ(5, h2.key(i)) << "[" << i << "] not 5"; + } + } + + // cleanup + } + + } + + virtual void growCondition(){ + IntHashTable16 h1(4); + + 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; + } + } + virtual void shrinkCondition(){ + IntHashTable16 h1(4); + + 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; + } + } + + virtual 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; } + //}; + // + //// 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"; + + + { + class A{ + public: + virtual void x() = 0; + }; + + class BB :public A{ + public: + int key; + int value; + void x() override {} + }; + + //typedef BB B; + //typedef KVPairSimple B; + typedef KVPair B; + int n = 1; + int s = sizeof(B); + B* pArr = (B*)malloc(n * s); + //B* pArr = new B[n]; + memset(pArr, 0, s * n); + //std::fill((char*)pArr, (char*)(pArr+n), 0); + //for (int i = 0; i < n; i++){ + // pArr[i].key = 0xAAAAAAAA; + // pArr[i].value = 0xBBBBBBBB; + //} + //delete[] pArr; + free(pArr); + } + + //ASSERT_EQ(0, 1) << "uncomment other tests"; + } + }; + + class LinearProbingHashTableTest : public LinearProbingHashTableTestBase < _LinProbHT8, _LinProbHT16, _LinProbHT32> { + public: + // inherit all tests from base + }; + + ADD_TEST_F(LinearProbingHashTableTest, insert); + ADD_TEST_F(LinearProbingHashTableTest, copyTable); + ADD_TEST_F(LinearProbingHashTableTest, growCondition); + ADD_TEST_F(LinearProbingHashTableTest, shrinkCondition); + ADD_TEST_F(LinearProbingHashTableTest, isKeyZero); + ADD_TEST_F(LinearProbingHashTableTest, remove); + } +} \ No newline at end of file diff --git a/YASI_12/ds.SeparateChainingHashTable.h b/YASI_12/ds.SeparateChainingHashTable.h index d573f63..d425ce1 100644 --- a/YASI_12/ds.SeparateChainingHashTable.h +++ b/YASI_12/ds.SeparateChainingHashTable.h @@ -1,7 +1,9 @@ #pragma once -#include "common.h" #include "ds.hashtablebase.h" +// enable-disable testing classes in this file +#include "test.this.module.h" + using namespace std; namespace yasi{ @@ -22,7 +24,7 @@ namespace ds{ HashFunction> { ///////////////// enable testing /////////////////// - friend class SeparateChainingHashTableTest; + FRIEND_TEST_CLASS( SeparateChainingHashTableTest); protected: typedef KVPair Pair; @@ -174,183 +176,6 @@ namespace ds{ }; - 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.SeparateChainingHashTable.test.h b/YASI_12/ds.SeparateChainingHashTable.test.h new file mode 100644 index 0000000..9ab1b97 --- /dev/null +++ b/YASI_12/ds.SeparateChainingHashTable.test.h @@ -0,0 +1,189 @@ +#pragma once + +#if YASI_TEST_THIS_MODULE != 1 +#define YASI_TEST_THIS_MODULE 1 +#endif +#include "ds.SeparateChainingHashTable.h" + +namespace yasi{ + namespace ds{ + 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.hashtablebase.h b/YASI_12/ds.hashtablebase.h index 5b9f13a..b6a77df 100644 --- a/YASI_12/ds.hashtablebase.h +++ b/YASI_12/ds.hashtablebase.h @@ -41,6 +41,52 @@ class IntHashFunction : public IHashFunction{ } }; +// various simplistic hash functions for testing +namespace testhash{ + template + class IdentityFunction : public IHashFunction < Key > { + public: + virtual int operator()(const Key& n) const override{ + return n; + } + }; + template + class Mod8Function : public IHashFunction < Key > { + public: + static const unsigned int modulus = 8; + virtual int operator()(const Key& n) const override{ + return n % 8; + } + }; + template + class Mod16Function : public IHashFunction < Key > { + public: + static const unsigned int modulus = 16; + virtual int operator()(const Key& n) const override{ + return n % 16; + } + }; + template + class Mod32Function : public IHashFunction < Key > { + public: + static const unsigned int modulus = 32; + virtual int operator()(const Key& n) const override{ + return n % 32; + } + }; + // mod 16 by default + template + class ModFunction : public IHashFunction < Key > { + public: + unsigned int modulus; + ModFunction() : modulus(16){} + virtual int operator()(const Key& n) const override{ + return n % modulus; + } + }; + +} // namespace testhash + //template //class ICollisionStrategy{ diff --git a/YASI_12/main.cpp b/YASI_12/main.cpp index e3628ed..163949a 100644 --- a/YASI_12/main.cpp +++ b/YASI_12/main.cpp @@ -1,7 +1,5 @@ #include "common.h" -//#include "ds.BSTDictionary.h" -//#include "ds.separatechaininghashtable.h" //#include "ds.intlinearprobinghashtable.h" //#include "ds.linearprobinghashtable.h" //#include "ds.hopscotchhashtable.h" @@ -31,6 +29,10 @@ using namespace std; #include "ds.priorityqueue.test.h" #include "ds.mutablepriorityqueue.test.h" #include "ds.bstdictionary.test.h" +#include "ds.separatechaininghashtable.test.h" +#include "ds.linearprobinghashtable.test.h" +#include "ds.intlinearprobinghashtable.test.h" +#include "ds.HopScotchHashTable.test.h" int testAll(int* argc, char** argv){