diff --git a/data_structures/skip_list.cpp b/data_structures/skip_list.cpp index 9cf8c430f..7eeb426d0 100644 --- a/data_structures/skip_list.cpp +++ b/data_structures/skip_list.cpp @@ -13,18 +13,28 @@ * @author [Krishna Vedala](https://github.com/kvedala) */ +#include #include +#include #include #include #include #include +#include +#include +#include #include +template +std::unique_ptr make_unique(Args&&... args) { + return std::unique_ptr(new T(std::forward(args)...)); +} + /** \namespace data_structures * \brief Data-structure algorithms */ namespace data_structures { -constexpr int MAX_LEVEL = 2; ///< Maximum level of skip list +constexpr int MAX_LEVEL = 6; ///< Maximum level of skip list constexpr float PROBABILITY = 0.5; ///< Current probability for "coin toss" /** @@ -49,23 +59,51 @@ struct Node { } }; +/* +A base class for generator that generate uniformly distributed random numbers in +[0, 1). +This is introduced mainly because we want to mock insertion of items. +*/ +class BaseRandomNumberGenerator { + public: + virtual float operator()() = 0; +}; + +class CRandBasedGenerator : public BaseRandomNumberGenerator { + public: + float operator()() { return static_cast(std::rand()) / RAND_MAX; } +}; + +/* + This mock allows us to control the random numbers generated so we can + ensure the new key is added to the required number of levels. +*/ +class MockRandomNumberGenerator : public BaseRandomNumberGenerator { + std::vector randos; + int idx; + + public: + MockRandomNumberGenerator(const int level) : idx(0) { + randos = std::vector(level - 1, 0.1); + randos.push_back(0.65); + } + float operator()() { + // We want to control the skip list level for an item we are adding. + if (idx < randos.size()) + return randos[idx++]; + else + return randos[randos.size() - 1]; + } +}; + /** * SkipList class implementation with basic methods */ class SkipList { + // const int MAX_LEVEL; ///< Maximum level of skip list int level; ///< Maximum level of the skiplist std::shared_ptr header; ///< Pointer to the header node - - public: - /** - * Skip List constructor. Initializes header, start - * Node for searching in the list - */ - SkipList() { - level = 0; - // Header initialization - header = std::make_shared(-1, MAX_LEVEL); - } + std::unique_ptr uniformRealNumberGenerator; /** * Returns random level of the skip list. @@ -74,13 +112,30 @@ class SkipList { */ int randomLevel() { int lvl = 0; - while (static_cast(std::rand()) / RAND_MAX < PROBABILITY && + while ((*uniformRealNumberGenerator)() < PROBABILITY && lvl < MAX_LEVEL) { lvl++; } return lvl; } + public: + /** + * Skip List constructor. Initializes header, start + * Node for searching in the list + */ + SkipList(std::unique_ptr generator) { + level = 0; + // Header initialization + header = std::make_shared(-1, MAX_LEVEL); + uniformRealNumberGenerator = std::move(generator); + } + + void setNumberGenerator( + std::unique_ptr generator) { + uniformRealNumberGenerator = std::move(generator); + } + /** * Inserts elements with given key and value; * It's level is computed by randomLevel() function. @@ -88,7 +143,6 @@ class SkipList { * @param value pointer to a value, that can be any type */ void insertElement(int key, void* value) { - std::cout << "Inserting" << key << "..."; std::shared_ptr x = header; std::array, MAX_LEVEL + 1> update; update.fill(nullptr); @@ -119,10 +173,6 @@ class SkipList { n->forward[i] = update[i]->forward[i]; update[i]->forward[i] = n; } - std::cout << "Inserted" << std::endl; - - } else { - std::cout << "Exists" << std::endl; } } @@ -156,9 +206,6 @@ class SkipList { } /* Remove empty levels*/ while (level > 0 && header->forward[level] == nullptr) level--; - std::cout << "Deleted" << std::endl; - } else { - std::cout << "Doesn't exist" << std::endl; } } @@ -169,7 +216,6 @@ class SkipList { */ void* searchElement(int key) { std::shared_ptr x = header; - std::cout << "Searching for " << key << std::endl; for (int i = level; i >= 0; i--) { while (x->forward[i] && x->forward[i]->key < key) x = x->forward[i]; @@ -177,10 +223,8 @@ class SkipList { x = x->forward[0]; if (x && x->key == key) { - std::cout << "Found" << std::endl; return x->value; } else { - std::cout << "Not Found" << std::endl; return nullptr; } } @@ -188,38 +232,308 @@ class SkipList { /** * Display skip list level */ - void displayList() { - std::cout << "Displaying list:\n"; + void displayList(std::ostream& out, std::string end = " ") { for (int i = 0; i <= level; i++) { std::shared_ptr node = header->forward[i]; - std::cout << "Level " << (i) << ": "; + // out << /*"Level " << (i) << ": "; */ while (node != nullptr) { - std::cout << node->key << " "; + out << node->key << " "; node = node->forward[i]; } - std::cout << std::endl; + out << end; } } }; } // namespace data_structures +// Testing helper functions +std::string removeItem(std::string input, int key) { + std::stringstream ss(input); + std::stringstream out; + + int val; + while (ss >> val) { + if (val != key) { + out << val << " "; + } + } + + std::string out_str = out.str(); + + // Replace consecutive occurance of 2 or more spaces with one space. + std::regex regex("(\\s{2,})"); + + return std::regex_replace(out.str(), regex, std::string(" ")); +} + +std::string insertItem(const std::string input, int key, const int level) { + std::stringstream out; + int val; + + int count = 0; + size_t start = 0; + size_t end = 0; + // Split levels as marked with ; + do { + end = input.find(';', start); + std::stringstream in(input.substr(start, end - start)); + + bool keyInserted = false; + bool foundFirstLarger = false; + while (in >> val) { + if (val == key) { + return input; + } else if (foundFirstLarger == false && key < val) { + foundFirstLarger = true; + keyInserted = true; + out << key << " " << val << " "; + } else { + out << val << " "; + } + } + if (keyInserted == false) { + out << key << " "; + } + + start = end + 1; + count++; + } while (end != std::string::npos && count < level); + + while (count < level) { + out << key << " "; + count++; + } + if (end != std::string::npos) { + std::regex regex("(;)"); + out << std::regex_replace(input.substr(start), regex, std::string(" ")); + } + + // Replace consecutive occurance of 2 or more spaces with one space. + std::regex regex("(\\s{2,})"); + + return std::regex_replace(out.str(), regex, std::string(" ")); +} + +data_structures::SkipList createSkipListTestInstance() { + data_structures::SkipList lst( + make_unique()); + + int count = 0; + for (int j = 0; j < 100; j++) { + int k = (std::rand() % 512 + 1); + lst.insertElement(k, &j); + count++; + } + + return lst; +} + +void isTrue(bool isEqual, std::string failure_msg, std::string success_msg) { + if (isEqual) + std::cout << success_msg << std::endl; + else + std::cout << failure_msg << std::endl; +} + +void testInsertNewKey() { + data_structures::SkipList lst = createSkipListTestInstance(); + + // Add 200 to the skip list + int key = 200; + int level = 4; + + // Generate ground truth for the insertion + std::stringstream ss_insertion_target; + lst.displayList(ss_insertion_target, " ; "); + + std::string target = insertItem(ss_insertion_target.str(), key, level); + + // This mock allows us to control the random numbers generated so we can + // ensure the new key is added to the required number of levels. + lst.setNumberGenerator( + make_unique(level)); + + // Actually insert into the skip list + lst.insertElement(key, NULL); + + std::stringstream ss_insertion; + lst.displayList(ss_insertion, ""); + + isTrue(ss_insertion.str() == target, "FAILURE: test_insert_key", + "SUCCESS: test_insert_key"); +} + +// TEST: Insert a key that is already in the skip list +void testInsertExistingKey() { + data_structures::SkipList lst = createSkipListTestInstance(); + + // Add 200 to the skip list + int key = 390; + int level = 5; + + // Inserting a key that is already there should leave the skip list + // unchanged. Record the current state of the skip list + std::stringstream ss_insertion_gt; + lst.displayList(ss_insertion_gt, ""); + + // Actually insert into the skip list + lst.setNumberGenerator( + make_unique(level)); + lst.insertElement(key, NULL); + + std::stringstream ss_insertion; + lst.displayList(ss_insertion, ""); + + isTrue(ss_insertion.str() == ss_insertion_gt.str(), + "FAILURE: test_insert_existing_key", + "SUCCESS: test_insert_existing_key"); +} + +void testInsertLargestKey() { // TEST: Insert the largest key + data_structures::SkipList lst = createSkipListTestInstance(); + + int key = 10000; + int level = data_structures::MAX_LEVEL; + + // We will use string processing to insert a key into this string and + // use the result as a target + std::stringstream ss_insertion_target; + lst.displayList(ss_insertion_target, " ; "); + std::string target = insertItem(ss_insertion_target.str(), key, level); + + // Actually insert into the skip list + // This mock allows us to control the random numbers generated so we can + // ensure the new key is added to the required number of levels. + lst.setNumberGenerator( + make_unique(level)); + + lst.insertElement(key, NULL); + + std::stringstream ss_insertion; + lst.displayList(ss_insertion, ""); + + isTrue(ss_insertion.str() == target, "FAILURE: test_insert_largest_key", + "SUCCESS: test_insert_largest_key"); +} + +// TEST: Insert what would be the smallest key in the skip list +void testInsertSmallestKey() { + data_structures::SkipList lst = createSkipListTestInstance(); + + int key = -10000; + int level = 1; + + // We will use string processing to insert a key into this string and + // use the result as a target + std::stringstream ss_insertion_target; + lst.displayList(ss_insertion_target, " ; "); + std::string target = insertItem(ss_insertion_target.str(), key, level); + + // Actually insert into the skip list + // This mock allows us to control the random numbers generated so we can + // ensure the new key is added to the required number of levels. + lst.setNumberGenerator( + make_unique(level)); + + lst.insertElement(key, NULL); + + std::stringstream ss_insertion; + lst.displayList(ss_insertion, ""); + + isTrue(ss_insertion.str() == target, "FAILURE: test_insert_smallest_key", + "SUCCESS: test_insert_smallest_key"); +} + +void testDeleteExistingKey() { // TEST: Delete an existing key + data_structures::SkipList lst = createSkipListTestInstance(); + + int key = 119; + int level = 1; + + // We will use string processing to insert a key into this string and + // use the result as a target + std::stringstream ss_deletion_target; + lst.displayList(ss_deletion_target, " "); + std::string target = removeItem(ss_deletion_target.str(), key); + + // Actually insert into the skip list + // This mock allows us to control the random numbers generated so we can + // ensure the new key is added to the required number of levels. + lst.setNumberGenerator( + make_unique(level)); + + lst.deleteElement(key); + + std::stringstream ss_deletion; + lst.displayList(ss_deletion, ""); + + isTrue(ss_deletion.str() == target, "FAILURE: test_delete_existing_key", + "SUCCESS: test_delete_existing_key"); +} + +// TEST: Delete a key that is not in the skip list +void testDeleteKeyNotInSkipList() { + data_structures::SkipList lst = createSkipListTestInstance(); + + int key = 299; + int level = 1; + + // We will use string processing to insert a key into this string and + // use the result as a target + std::stringstream ss_deletion_target; + lst.displayList(ss_deletion_target, " "); + std::string target = removeItem(ss_deletion_target.str(), key); + + // Actually insert into the skip list + // This mock allows us to control the random numbers generated so we can + // ensure the new key is added to the required number of levels. + lst.setNumberGenerator( + make_unique(level)); + + lst.deleteElement(key); + + std::stringstream ss_deletion; + lst.displayList(ss_deletion, ""); + + isTrue(ss_deletion.str() == target, + "FAILURE: test_delete_key_not_in_skip_list", + "SUCCESS: test_delete_key_not_in_skip_list"); +} + /** * Main function: * Creates and inserts random 2^[number of levels] * elements into the skip lists and than displays it */ int main() { - std::srand(std::time(nullptr)); + std::srand(0); - data_structures::SkipList lst; + data_structures::SkipList lst( + make_unique()); - for (int j = 0; j < (1 << (data_structures::MAX_LEVEL + 1)); j++) { - int k = (std::rand() % (1 << (data_structures::MAX_LEVEL + 2)) + 1); + int count = 0; + for (int j = 0; j < 100; j++) { + int k = (std::rand() % 512 + 1); lst.insertElement(k, &j); + count++; } + std::cout << "Number of nodes: " << count << " " + << (2 << (data_structures::MAX_LEVEL + 2)) << "\n"; - lst.displayList(); + lst.displayList(std::cout, "\n"); + + testInsertNewKey(); + + testInsertExistingKey(); + + testInsertLargestKey(); + + testInsertSmallestKey(); + + testDeleteExistingKey(); + + testDeleteKeyNotInSkipList(); return 0; }