mirror of
https://hub.njuu.cf/TheAlgorithms/C-Plus-Plus.git
synced 2023-10-11 13:05:55 +08:00
Tests:
1) Add unit tests for SkipList class with a view to refactoring the code, 2) Added a base class for objects that generate uniformly distributed random numbers between 0 and 1. This means we can mock this interface and choose the sequence of "random" numbers. It allows us to choose the level of a key we want to add to the SkipList for testing purposes.
This commit is contained in:
parent
6376bf46af
commit
48d179adb5
@ -13,18 +13,28 @@
|
||||
* @author [Krishna Vedala](https://github.com/kvedala)
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
template <typename T, typename... Args>
|
||||
std::unique_ptr<T> make_unique(Args&&... args) {
|
||||
return std::unique_ptr<T>(new T(std::forward<Args>(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<float>(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<float> randos;
|
||||
int idx;
|
||||
|
||||
public:
|
||||
MockRandomNumberGenerator(const int level) : idx(0) {
|
||||
randos = std::vector<float>(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<Node> 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<Node>(-1, MAX_LEVEL);
|
||||
}
|
||||
std::unique_ptr<BaseRandomNumberGenerator> uniformRealNumberGenerator;
|
||||
|
||||
/**
|
||||
* Returns random level of the skip list.
|
||||
@ -74,13 +112,30 @@ class SkipList {
|
||||
*/
|
||||
int randomLevel() {
|
||||
int lvl = 0;
|
||||
while (static_cast<float>(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<BaseRandomNumberGenerator> generator) {
|
||||
level = 0;
|
||||
// Header initialization
|
||||
header = std::make_shared<Node>(-1, MAX_LEVEL);
|
||||
uniformRealNumberGenerator = std::move(generator);
|
||||
}
|
||||
|
||||
void setNumberGenerator(
|
||||
std::unique_ptr<BaseRandomNumberGenerator> 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<Node> x = header;
|
||||
std::array<std::shared_ptr<Node>, 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<Node> 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> 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<data_structures::CRandBasedGenerator>());
|
||||
|
||||
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<data_structures::MockRandomNumberGenerator>(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<data_structures::MockRandomNumberGenerator>(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<data_structures::MockRandomNumberGenerator>(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<data_structures::MockRandomNumberGenerator>(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<data_structures::MockRandomNumberGenerator>(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<data_structures::MockRandomNumberGenerator>(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<data_structures::CRandBasedGenerator>());
|
||||
|
||||
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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user