-
-
Save eXCoreX/e3dcb3cc60fa13272b0626c3ffb19844 to your computer and use it in GitHub Desktop.
Linked List + 2 Hash Maps vs Tree Map
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// { | |
#include <unordered_map> | |
#include <map> | |
#include <unordered_set> | |
#include <list> | |
#include <iostream> | |
#include <chrono> | |
using namespace std; | |
// } | |
class Constant { | |
private: | |
// Appart from value we also store the iterator to the corresponding current bucket. | |
struct Value { | |
unsigned int value; | |
list<pair<unsigned int, unordered_set<string>>>::iterator bucket; | |
}; | |
// List of buckets: value + set of keys with this value. | |
// We keep this list sorted by value. | |
list<pair<unsigned int, unordered_set<string>>> _buckets; | |
// Map from value to count of keys with that value. | |
unordered_map<unsigned int, unsigned int> _counts; | |
// Map from key to Value. | |
unordered_map<string, Value> _values; | |
public: | |
void create_element(const string& key) { | |
if (_buckets.size() == 0 || _buckets.front().first != 1) { | |
_buckets.push_front(make_pair(1, unordered_set<string>{key})); | |
} | |
else { | |
_buckets.front().second.insert(key); | |
} | |
auto it = _buckets.begin(); | |
_values.insert(make_pair(key, Value{ 1, it })); | |
_counts[1]++; | |
} | |
void delete_element(unordered_map<string, Value>::iterator it) { | |
_counts[1]--; | |
if (_counts[1] == 0) { | |
_buckets.pop_front(); | |
} | |
else { | |
_buckets.front().second.erase(it->first); | |
} | |
_values.erase(it); | |
} | |
// Adjust element by +1/-1 | |
void adjust_element(unordered_map<string, Value>::iterator it, int adj) { | |
unsigned current = it->second.value; | |
unsigned next = current + adj; | |
_counts[current]--; | |
_counts[next]++; | |
// remove the key from the current bucket | |
it->second.bucket->second.erase(it->first); | |
// no bucket change, just adjust the number in the bucket | |
if (_counts[current] == 0 && _counts[next] == 1) { | |
it->second.bucket->first += adj; | |
} | |
// no bucket change, we need to adjust iterator | |
if (_counts[current] > 0 && _counts[next] > 1) { | |
advance(it->second.bucket, adj); | |
} | |
// create a new next bucket | |
if (_counts[current] > 0 && _counts[next] == 1) { | |
if (adj > 0) // insert inserts before the element, if we are increasing value, we want first advance once | |
advance(it->second.bucket, adj); | |
it->second.bucket = _buckets.insert(it->second.bucket, make_pair(next, unordered_set<string>{it->first})); | |
} | |
// delete the current bucket | |
if (_counts[current] == 0 && _counts[next] > 1) { | |
it->second.bucket = _buckets.erase(it->second.bucket); | |
if (adj < 0) // erase returns the iterator after the removed element, if we decreasing the value, we need to go one back | |
advance(it->second.bucket, adj); | |
} | |
// add the bucket to the next bucket | |
it->second.bucket->second.insert(it->first); | |
it->second.value = next; | |
} | |
/** Inserts a new key <Key> with value 1. Or increments an existing key by 1. */ | |
void inc(const string& key) { | |
auto it = _values.find(key); | |
if (it == _values.end()) { | |
create_element(key); | |
} | |
else { | |
adjust_element(it, 1); | |
} | |
} | |
/** Decrements an existing key by 1. If Key's value is 1, remove it from the data structure. */ | |
void dec(const string& key) { | |
auto it = _values.find(key); | |
if (it == _values.end()) | |
return; | |
if (it->second.value == 1) { | |
delete_element(it); | |
} | |
else { | |
adjust_element(it, -1); | |
} | |
} | |
/** Returns one of the keys with maximal value. */ | |
string getMaxKey() const { | |
if (_buckets.empty()) | |
return string(""); | |
return *_buckets.back().second.begin(); | |
} | |
/** Returns one of the keys with Minimal value. */ | |
string getMinKey() const { | |
if (_buckets.empty()) | |
return string(""); | |
return *_buckets.front().second.begin(); | |
} | |
}; | |
string gen_string(size_t len) | |
{ | |
static const char alphanum[] = | |
"0123456789" | |
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" | |
"abcdefghijklmnopqrstuvwxyz"; | |
string res; | |
for (size_t i = 0; i < len; i++) | |
{ | |
res += alphanum[rand() % (sizeof(alphanum) - 1)]; | |
} | |
return res; | |
} | |
using namespace std::chrono; | |
// { | |
int main() { | |
const size_t TEST_NUM = 10; | |
const size_t CASE_SIZE = 100000; | |
const size_t KEYS_NUM = 100000; | |
Constant c; | |
map<string, int> m; | |
vector<string> keys = { "a", "b", "c", "d", "e" }; | |
srand(time(nullptr)); | |
cout << "5 known keys:\n"; | |
{ | |
auto inc = [&c](const string& key) { | |
c.inc(key); | |
c.getMinKey(); | |
c.getMaxKey(); | |
// cout << "inc(\"" << key << "\") min = \"" << c.getMinKey() << "\" max = \"" << c.getMaxKey() << "\"" << endl; | |
}; | |
auto dec = [&c](const string& key) { | |
c.dec(key); | |
c.getMinKey(); | |
c.getMaxKey(); | |
// cout << "dec(\"" << key << "\") min = \"" << c.getMinKey() << "\" max = \"" << c.getMaxKey() << "\"" << endl; | |
}; | |
high_resolution_clock::time_point t1 = high_resolution_clock::now(); | |
high_resolution_clock::time_point t2 = high_resolution_clock::now(); | |
auto total = duration_cast<microseconds>(t1 - t1); | |
for (int test = 0; test < TEST_NUM; test++) | |
{ | |
t1 = high_resolution_clock::now(); | |
for (int i = 0; i < CASE_SIZE; i++) | |
{ | |
inc(keys[rand() % 5]); | |
dec(keys[rand() % 5]); | |
} | |
t2 = high_resolution_clock::now(); | |
total += duration_cast<microseconds>(t2 - t1); | |
} | |
total /= TEST_NUM; | |
cout << "Average O(1): " << total.count() << " us\n"; | |
auto inc_m = [&m](const string& key) | |
{ | |
++m[key]; | |
m.begin(); | |
m.rbegin(); | |
}; | |
auto dec_m = [&m](const string& key) | |
{ | |
auto it = m.find(key); | |
if (it == m.end()) | |
{ | |
return; | |
} | |
--(it->second); | |
if (it->second == 0) | |
{ | |
m.erase(it); | |
} | |
m.begin(); | |
m.rbegin(); | |
}; | |
auto total_m = duration_cast<microseconds>(t1 - t1); | |
for (int test = 0; test < TEST_NUM; test++) | |
{ | |
t1 = high_resolution_clock::now(); | |
for (int i = 0; i < CASE_SIZE; i++) | |
{ | |
inc_m(keys[rand() % 5]); | |
dec_m(keys[rand() % 5]); | |
} | |
t2 = high_resolution_clock::now(); | |
total_m += duration_cast<microseconds>(t2 - t1); | |
} | |
total_m /= TEST_NUM; | |
cout << "Average O(log(n)): " << total_m.count() << " us\n"; | |
} | |
cout << KEYS_NUM << " random keys:\n"; | |
keys = vector<string>(KEYS_NUM); | |
for (size_t i = 0; i < KEYS_NUM; i++) | |
{ | |
keys[i] = gen_string(static_cast<size_t>(rand() % 10) + 1); | |
} | |
{ | |
auto inc = [&c](const string& key) { | |
c.inc(key); | |
c.getMinKey(); | |
c.getMaxKey(); | |
// cout << "inc(\"" << key << "\") min = \"" << c.getMinKey() << "\" max = \"" << c.getMaxKey() << "\"" << endl; | |
}; | |
auto dec = [&c](const string& key) { | |
c.dec(key); | |
c.getMinKey(); | |
c.getMaxKey(); | |
// cout << "dec(\"" << key << "\") min = \"" << c.getMinKey() << "\" max = \"" << c.getMaxKey() << "\"" << endl; | |
}; | |
high_resolution_clock::time_point t1 = high_resolution_clock::now(); | |
high_resolution_clock::time_point t2 = high_resolution_clock::now(); | |
auto total = duration_cast<microseconds>(t1 - t1); | |
for (int test = 0; test < TEST_NUM; test++) | |
{ | |
t1 = high_resolution_clock::now(); | |
for (int i = 0; i < CASE_SIZE; i++) | |
{ | |
inc(keys[rand() % KEYS_NUM]); | |
dec(keys[rand() % KEYS_NUM]); | |
} | |
t2 = high_resolution_clock::now(); | |
total += duration_cast<microseconds>(t2 - t1); | |
} | |
total /= TEST_NUM; | |
cout << "Average O(1): " << total.count() << " us\n"; | |
auto inc_m = [&m](const string& key) | |
{ | |
++m[key]; | |
m.begin(); | |
m.rbegin(); | |
}; | |
auto dec_m = [&m](const string& key) | |
{ | |
auto it = m.find(key); | |
if (it == m.end()) | |
{ | |
return; | |
} | |
--(it->second); | |
if (it->second == 0) | |
{ | |
m.erase(it); | |
} | |
m.begin(); | |
m.rbegin(); | |
}; | |
auto total_m = duration_cast<microseconds>(t1 - t1); | |
for (int test = 0; test < TEST_NUM; test++) | |
{ | |
t1 = high_resolution_clock::now(); | |
for (int i = 0; i < CASE_SIZE; i++) | |
{ | |
inc_m(keys[rand() % KEYS_NUM]); | |
dec_m(keys[rand() % KEYS_NUM]); | |
} | |
t2 = high_resolution_clock::now(); | |
total_m += duration_cast<microseconds>(t2 - t1); | |
} | |
total_m /= TEST_NUM; | |
cout << "Average O(log(n)): " << total_m.count() << " us\n"; | |
} | |
} | |
// } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment