// Copyright 2022 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source // License, and you may not use this file except in compliance with the Business Source License. // // As of the Change Date specified in that file, in accordance with // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. #include #include #include #include "utils/math.hpp" #include "utils/skip_list.hpp" #include "utils/timer.hpp" TEST(SkipList, Int) { memgraph::utils::SkipList list; { auto acc = list.access(); for (int64_t i = -10; i <= 10; ++i) { auto res = acc.insert(i); ASSERT_EQ(*res.first, i); ASSERT_TRUE(res.second); } ASSERT_EQ(acc.size(), 21); } { auto acc = list.access(); for (int64_t i = -10; i <= 10; ++i) { auto res = acc.insert(i); ASSERT_EQ(*res.first, i); ASSERT_FALSE(res.second); } ASSERT_EQ(acc.size(), 21); } { auto acc = list.access(); int64_t val = -10; for (auto &item : acc) { ASSERT_EQ(item, val); ++val; } ASSERT_EQ(val, 11); ASSERT_EQ(acc.size(), 21); } { auto acc = list.access(); for (int64_t i = -10; i <= 10; ++i) { auto it = acc.find(i); ASSERT_NE(it, acc.end()); ASSERT_EQ(*it, i); } ASSERT_EQ(acc.size(), 21); } { auto acc = list.access(); for (int64_t i = -10; i <= 10; ++i) { ASSERT_TRUE(acc.remove(i)); } ASSERT_EQ(acc.size(), 0); } { auto acc = list.access(); for (int64_t i = -10; i <= 10; ++i) { ASSERT_FALSE(acc.remove(i)); } ASSERT_EQ(acc.size(), 0); } } TEST(SkipList, String) { memgraph::utils::SkipList list; { auto acc = list.access(); for (int64_t i = -10; i <= 10; ++i) { std::string str(fmt::format("str{}", i)); auto res = acc.insert(str); ASSERT_EQ(*res.first, str); ASSERT_TRUE(res.second); ASSERT_NE(str, ""); } ASSERT_EQ(acc.size(), 21); } { auto acc = list.access(); for (int64_t i = -10; i <= 10; ++i) { std::string str(fmt::format("str{}", i)); auto res = acc.insert(str); ASSERT_EQ(*res.first, str); ASSERT_FALSE(res.second); ASSERT_NE(str, ""); } ASSERT_EQ(acc.size(), 21); } { auto acc = list.access(); int64_t pos = 0; std::vector order{-1, -10, -2, -3, -4, -5, -6, -7, -8, -9, 0, 1, 10, 2, 3, 4, 5, 6, 7, 8, 9}; for (auto &item : acc) { std::string str(fmt::format("str{}", order[pos])); ASSERT_EQ(item, str); ++pos; } ASSERT_EQ(pos, 21); ASSERT_EQ(acc.size(), 21); } { auto acc = list.access(); for (int64_t i = -10; i <= 10; ++i) { std::string str(fmt::format("str{}", i)); auto it = acc.find(str); ASSERT_NE(it, acc.end()); ASSERT_EQ(*it, str); } ASSERT_EQ(acc.size(), 21); } { auto acc = list.access(); for (int64_t i = -10; i <= 10; ++i) { std::string str(fmt::format("str{}", i)); ASSERT_TRUE(acc.remove(str)); } ASSERT_EQ(acc.size(), 0); } { auto acc = list.access(); for (int64_t i = -10; i <= 10; ++i) { std::string str(fmt::format("str{}", i)); ASSERT_FALSE(acc.remove(str)); } ASSERT_EQ(acc.size(), 0); } } TEST(SkipList, StringMove) { memgraph::utils::SkipList list; { auto acc = list.access(); for (int64_t i = -10; i <= 10; ++i) { std::string str(fmt::format("str{}", i)); std::string copy(str); auto res = acc.insert(std::move(str)); ASSERT_EQ(str, ""); ASSERT_EQ(*res.first, copy); ASSERT_TRUE(res.second); } ASSERT_EQ(acc.size(), 21); } { auto acc = list.access(); for (int64_t i = -10; i <= 10; ++i) { std::string str(fmt::format("str{}", i)); auto res = acc.insert(str); ASSERT_EQ(*res.first, str); ASSERT_FALSE(res.second); ASSERT_NE(str, ""); } ASSERT_EQ(acc.size(), 21); } } TEST(SkipList, Basic) { memgraph::utils::SkipList list; auto acc = list.access(); auto insert_item = [&acc](auto item, bool inserted) { auto ret = acc.insert(item); ASSERT_NE(ret.first, acc.end()); ASSERT_EQ(*ret.first, item); ASSERT_EQ(ret.second, inserted); }; auto find_item = [&acc](auto item, bool found) { auto ret = acc.find(item); if (found) { ASSERT_NE(ret, acc.end()); ASSERT_EQ(*ret, item); } else { ASSERT_EQ(ret, acc.end()); } }; ASSERT_FALSE(acc.contains(5)); insert_item(5, true); insert_item(1, true); insert_item(2, true); insert_item(3, true); insert_item(4, true); insert_item(5, false); find_item(5, true); find_item(6, false); ASSERT_TRUE(acc.remove(5)); ASSERT_FALSE(acc.remove(5)); ASSERT_FALSE(acc.remove(6)); ASSERT_EQ(acc.size(), 4); } struct OnlyCopyable { OnlyCopyable() = default; OnlyCopyable(OnlyCopyable &&) = delete; OnlyCopyable(const OnlyCopyable &) = default; OnlyCopyable &operator=(OnlyCopyable &&) = delete; OnlyCopyable &operator=(const OnlyCopyable &) = default; OnlyCopyable(const uint64_t val) : value{val} {} uint64_t value; }; bool operator==(const OnlyCopyable &a, const OnlyCopyable &b) { return a.value == b.value; } bool operator<(const OnlyCopyable &a, const OnlyCopyable &b) { return a.value < b.value; } TEST(SkipList, OnlyCopyable) { memgraph::utils::SkipList list; std::vector vec{{1}, {2}, {3}, {4}, {5}}; auto acc = list.access(); auto ret = acc.insert(vec[1]); ASSERT_NE(ret.first, acc.end()); ASSERT_EQ(*ret.first, OnlyCopyable{2}); ASSERT_TRUE(ret.second); } struct OnlyMoveable { OnlyMoveable() = default; OnlyMoveable(uint64_t val) : value(val) {} OnlyMoveable(OnlyMoveable &&) = default; OnlyMoveable(const OnlyMoveable &) = delete; OnlyMoveable &operator=(OnlyMoveable &&) = default; OnlyMoveable &operator=(const OnlyMoveable &) = delete; uint64_t value; }; bool operator==(const OnlyMoveable &a, const OnlyMoveable &b) { return a.value == b.value; } bool operator<(const OnlyMoveable &a, const OnlyMoveable &b) { return a.value < b.value; } TEST(SkipList, OnlyMoveable) { memgraph::utils::SkipList list; std::vector vec; vec.push_back({1}); vec.push_back({2}); auto acc = list.access(); auto ret = acc.insert(std::move(vec[1])); ASSERT_NE(ret.first, acc.end()); ASSERT_EQ(*ret.first, OnlyMoveable{2}); ASSERT_TRUE(ret.second); } TEST(SkipList, Const) { memgraph::utils::SkipList list; auto func = [](const memgraph::utils::SkipList &lst) { auto acc = lst.access(); return acc.find(5); }; auto acc = list.access(); MG_ASSERT(func(list) == acc.end()); } struct MapObject { uint64_t key; std::string value; }; bool operator==(const MapObject &a, const MapObject &b) { return a.key == b.key; } bool operator<(const MapObject &a, const MapObject &b) { return a.key < b.key; } bool operator==(const MapObject &a, const uint64_t &b) { return a.key == b; } bool operator<(const MapObject &a, const uint64_t &b) { return a.key < b; } TEST(SkipList, MapExample) { memgraph::utils::SkipList list; { auto accessor = list.access(); // Inserts an object into the list. ASSERT_TRUE(accessor.insert(MapObject{5, "hello world"}).second); // This operation will return an iterator that isn't equal to // `accessor.end()`. This is because the comparison operators only use // the key field for comparison, the value field is ignored. ASSERT_NE(accessor.find(MapObject{5, "this probably isn't desired"}), accessor.end()); // This will also succeed in removing the object. ASSERT_TRUE(accessor.remove(MapObject{5, "not good"})); } { auto accessor = list.access(); // Inserts an object into the list. ASSERT_TRUE(accessor.insert({5, "hello world"}).second); // This successfully finds the inserted object. ASSERT_NE(accessor.find(5), accessor.end()); // This successfully removes the inserted object. ASSERT_TRUE(accessor.remove(5)); } } TEST(SkipList, Move) { memgraph::utils::SkipList list; { auto acc = list.access(); for (int64_t i = -1000; i <= 1000; ++i) { acc.insert(i); } ASSERT_EQ(acc.size(), 2001); } { auto acc = list.access(); int64_t val = -1000; for (auto &item : acc) { ASSERT_EQ(item, val); ++val; } ASSERT_EQ(val, 1001); ASSERT_EQ(acc.size(), 2001); } memgraph::utils::SkipList moved(std::move(list)); { auto acc = moved.access(); int64_t val = -1000; for (auto &item : acc) { ASSERT_EQ(item, val); ++val; } ASSERT_EQ(val, 1001); ASSERT_EQ(acc.size(), 2001); } { auto acc = list.access(); ASSERT_DEATH(acc.insert(5), ""); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(SkipList, Clear) { memgraph::utils::SkipList list; { auto acc = list.access(); for (int64_t i = -1000; i <= 1000; ++i) { acc.insert(i); } ASSERT_EQ(acc.size(), 2001); } { auto acc = list.access(); int64_t val = -1000; for (auto &item : acc) { ASSERT_EQ(item, val); ++val; } ASSERT_EQ(val, 1001); ASSERT_EQ(acc.size(), 2001); } list.clear(); { auto acc = list.access(); uint64_t count = 0; for (auto it = acc.begin(); it != acc.end(); ++it) { ++count; } ASSERT_EQ(count, 0); ASSERT_EQ(acc.size(), 0); } { auto acc = list.access(); for (int64_t i = -1000; i <= 1000; ++i) { acc.insert(i); } ASSERT_EQ(acc.size(), 2001); } { auto acc = list.access(); int64_t val = -1000; for (auto &item : acc) { ASSERT_EQ(item, val); ++val; } ASSERT_EQ(val, 1001); ASSERT_EQ(acc.size(), 2001); } } struct Inception { uint64_t id; memgraph::utils::SkipList data; }; bool operator==(const Inception &a, const Inception &b) { return a.id == b.id; } bool operator<(const Inception &a, const Inception &b) { return a.id < b.id; } bool operator==(const Inception &a, const uint64_t &b) { return a.id == b; } bool operator<(const Inception &a, const uint64_t &b) { return a.id < b; } TEST(SkipList, Inception) { memgraph::utils::SkipList list; { for (uint64_t i = 0; i < 5; ++i) { memgraph::utils::SkipList inner; auto acc_inner = inner.access(); for (uint64_t j = 0; j < 100; ++j) { acc_inner.insert(j + 1000 * i); } ASSERT_EQ(acc_inner.size(), 100); auto acc = list.access(); acc.insert(Inception{i, std::move(inner)}); auto dead = inner.access(); ASSERT_DEATH(dead.insert(5), ""); } } { ASSERT_EQ(list.size(), 5); for (uint64_t i = 0; i < 5; ++i) { auto acc = list.access(); auto it = acc.find(i); ASSERT_NE(it, acc.end()); ASSERT_EQ(it->id, i); auto acc_inner = it->data.access(); ASSERT_EQ(acc_inner.size(), 100); for (uint64_t j = 0; j < 100; ++j) { auto it_inner = acc_inner.find(j + 1000 * i); ASSERT_NE(it_inner, acc_inner.end()); ASSERT_EQ(*it_inner, j + 1000 * i); } } } } TEST(SkipList, FindEqualOrGreater) { memgraph::utils::SkipList list; { auto acc = list.access(); for (uint64_t i = 1000; i < 2000; i += 2) { auto ret = acc.insert(i); ASSERT_NE(ret.first, acc.end()); ASSERT_EQ(*ret.first, i); ASSERT_TRUE(ret.second); } } { auto acc = list.access(); for (uint64_t i = 0; i < 1000; ++i) { auto it = acc.find_equal_or_greater(i); ASSERT_NE(it, acc.end()); ASSERT_EQ(*it, 1000); } for (uint64_t i = 1000; i < 1999; ++i) { auto it = acc.find_equal_or_greater(i); ASSERT_NE(it, acc.end()); ASSERT_EQ(*it, i + (i % 2 == 0 ? 0 : 1)); } for (uint64_t i = 1999; i < 3000; ++i) { auto it = acc.find_equal_or_greater(i); ASSERT_EQ(it, acc.end()); } } } struct Counter { int64_t key; int64_t value; }; bool operator==(const Counter &a, const Counter &b) { return a.key == b.key && a.value == b.value; } bool operator<(const Counter &a, const Counter &b) { if (a.key == b.key) return a.value < b.value; return a.key < b.key; } bool operator==(const Counter &a, int64_t b) { return a.key == b; } bool operator<(const Counter &a, int64_t b) { return a.key < b; } TEST(SkipList, EstimateCount) { memgraph::utils::SkipList list; // 100k elements will yield an expected maximum height of 17 const int kMaxElements = 100; const int kElementMembers = 1000; { auto acc = list.access(); for (int64_t i = 0; i < kMaxElements; ++i) { for (int64_t j = 0; j < kElementMembers; ++j) { auto ret = acc.insert({i, j}); ASSERT_NE(ret.first, acc.end()); ASSERT_EQ(ret.first->key, i); ASSERT_EQ(ret.first->value, j); ASSERT_TRUE(ret.second); } } } { uint64_t delta_min = std::numeric_limits::max(), delta_max = 0, delta_avg = 0; auto acc = list.access(); memgraph::utils::Timer timer; for (int64_t i = 0; i < kMaxElements; ++i) { uint64_t count = acc.estimate_count(i); uint64_t delta = count >= kElementMembers ? count - kElementMembers : kElementMembers - count; delta_min = std::min(delta_min, delta); delta_max = std::max(delta_max, delta); delta_avg += delta; } auto duration = timer.Elapsed().count(); delta_avg /= kMaxElements; std::cout << "Results for estimation from default layer:" << std::endl; std::cout << " min(delta) = " << delta_min << std::endl; std::cout << " avg(delta) = " << delta_avg << std::endl; std::cout << " max(delta) = " << delta_max << std::endl; std::cout << " duration = " << duration << " s" << std::endl; } { auto acc = list.access(); for (int64_t i = 0; i < kMaxElements; ++i) { uint64_t count = acc.estimate_count(i, 1); ASSERT_EQ(count, kElementMembers); } } } #define MAKE_RANGE_BOTH_DEFINED_TEST(lower, upper) \ { \ for (int64_t i = 0; i < 10; ++i) { \ for (int64_t j = 0; j < 10; ++j) { \ auto acc = list.access(); \ uint64_t blocks = 0; \ if (memgraph::utils::BoundType::lower == memgraph::utils::BoundType::EXCLUSIVE && \ memgraph::utils::BoundType::upper == memgraph::utils::BoundType::EXCLUSIVE) { \ if (j > i) { \ blocks = j - i - 1; \ } \ } else { \ if (j >= i) { \ blocks = j - i; \ if (memgraph::utils::BoundType::lower == memgraph::utils::BoundType::INCLUSIVE && \ memgraph::utils::BoundType::upper == memgraph::utils::BoundType::INCLUSIVE) { \ ++blocks; \ } \ } \ } \ uint64_t count = acc.estimate_range_count({{i, memgraph::utils::BoundType::lower}}, \ {{j, memgraph::utils::BoundType::upper}}, 1); \ ASSERT_EQ(count, kElementMembers *blocks); \ } \ } \ } #define MAKE_RANGE_LOWER_INFINITY_TEST(upper_value, upper_type, blocks) \ { \ auto acc = list.access(); \ uint64_t count = \ acc.estimate_range_count(std::nullopt, {{upper_value, memgraph::utils::BoundType::upper_type}}, 1); \ ASSERT_EQ(count, kElementMembers *blocks); \ } #define MAKE_RANGE_UPPER_INFINITY_TEST(lower_value, lower_type, blocks) \ { \ auto acc = list.access(); \ uint64_t count = \ acc.estimate_range_count({{lower_value, memgraph::utils::BoundType::lower_type}}, std::nullopt, 1); \ ASSERT_EQ(count, kElementMembers *blocks); \ } TEST(SkipList, EstimateRangeCount) { memgraph::utils::SkipList list; // 100k elements will yield an expected maximum height of 17 const int kMaxElements = 100; const int kElementMembers = 1000; { auto acc = list.access(); for (int64_t i = 0; i < kMaxElements; ++i) { for (int64_t j = 0; j < kElementMembers; ++j) { auto ret = acc.insert({i, j}); ASSERT_NE(ret.first, acc.end()); ASSERT_EQ(ret.first->key, i); ASSERT_EQ(ret.first->value, j); ASSERT_TRUE(ret.second); } } } { uint64_t delta_min = std::numeric_limits::max(), delta_max = 0, delta_avg = 0; auto acc = list.access(); memgraph::utils::Timer timer; for (int64_t i = 0; i < kMaxElements; ++i) { uint64_t count = acc.estimate_range_count(std::nullopt, {{i, memgraph::utils::BoundType::INCLUSIVE}}); uint64_t must_have = kElementMembers * (i + 1); uint64_t delta = count >= must_have ? count - must_have : must_have - count; delta_min = std::min(delta_min, delta); delta_max = std::max(delta_max, delta); delta_avg += delta; } auto duration = timer.Elapsed().count(); delta_avg /= kMaxElements; std::cout << "Results for estimation from default layer:" << std::endl; std::cout << " min(delta) = " << delta_min << std::endl; std::cout << " avg(delta) = " << delta_avg << std::endl; std::cout << " max(delta) = " << delta_max << std::endl; std::cout << " duration = " << duration << " s" << std::endl; } MAKE_RANGE_BOTH_DEFINED_TEST(INCLUSIVE, INCLUSIVE); MAKE_RANGE_BOTH_DEFINED_TEST(INCLUSIVE, EXCLUSIVE); MAKE_RANGE_BOTH_DEFINED_TEST(EXCLUSIVE, INCLUSIVE); MAKE_RANGE_BOTH_DEFINED_TEST(EXCLUSIVE, EXCLUSIVE); MAKE_RANGE_LOWER_INFINITY_TEST(10, INCLUSIVE, 11); MAKE_RANGE_LOWER_INFINITY_TEST(10, EXCLUSIVE, 10); MAKE_RANGE_LOWER_INFINITY_TEST(0, INCLUSIVE, 1); MAKE_RANGE_LOWER_INFINITY_TEST(0, EXCLUSIVE, 0); MAKE_RANGE_LOWER_INFINITY_TEST(-10, INCLUSIVE, 0); MAKE_RANGE_LOWER_INFINITY_TEST(-10, EXCLUSIVE, 0); MAKE_RANGE_UPPER_INFINITY_TEST(89, INCLUSIVE, 11); MAKE_RANGE_UPPER_INFINITY_TEST(89, EXCLUSIVE, 10); MAKE_RANGE_UPPER_INFINITY_TEST(99, INCLUSIVE, 1); MAKE_RANGE_UPPER_INFINITY_TEST(99, EXCLUSIVE, 0); MAKE_RANGE_UPPER_INFINITY_TEST(109, INCLUSIVE, 0); MAKE_RANGE_UPPER_INFINITY_TEST(109, EXCLUSIVE, 0); { auto acc = list.access(); uint64_t count = acc.estimate_range_count(std::nullopt, std::nullopt, 1); ASSERT_EQ(count, kMaxElements * kElementMembers); } } template void BenchmarkEstimateAverageNumberOfEquals(memgraph::utils::SkipList *list, const TCmp &cmp) { std::cout << "List size: " << list->size() << std::endl; std::cout << "The index will use layer " << memgraph::utils::SkipListLayerForAverageEqualsEstimation(list->size()) << std::endl; auto acc = list->access(); for (int layer = 1; layer <= memgraph::utils::kSkipListMaxHeight; ++layer) { memgraph::utils::Timer timer; auto estimate = acc.estimate_average_number_of_equals(cmp, layer); auto duration = timer.Elapsed().count(); std::cout << "Estimate on layer " << layer << " is " << estimate << " in " << duration << std::endl; } } TEST(SkipList, EstimateAverageNumberOfEquals1) { memgraph::utils::SkipList list; // ~500k elements will yield an expected maximum height of 19. const int kMaxElements = 1000; // Create a list that has 1, then 2, then 3, then 4, ..., up to // `kMaxElements` same keys next to each other. { auto acc = list.access(); for (int64_t i = 1; i <= kMaxElements; ++i) { for (int64_t j = 1; j <= i; ++j) { auto ret = acc.insert({i, j}); ASSERT_NE(ret.first, acc.end()); ASSERT_EQ(ret.first->key, i); ASSERT_EQ(ret.first->value, j); ASSERT_TRUE(ret.second); } } } // There are `kMaxElements * (kMaxElements + 1) / 2` members in the list. ASSERT_EQ(list.size(), kMaxElements * (kMaxElements + 1) / 2); // Benchmark the estimation function. BenchmarkEstimateAverageNumberOfEquals(&list, [](const auto &a, const auto &b) { return a.key == b.key; }); // Verify that the estimate on the lowest layer is correct. { auto acc = list.access(); uint64_t count = acc.estimate_average_number_of_equals([](const auto &a, const auto &b) { return a.key == b.key; }, 1); // There are `kMaxElements` unique elements when observing the data with // the specified equation operator so we divide the number of elements with // `kMaxElements`. ASSERT_EQ(list.size(), kMaxElements * (kMaxElements + 1) / 2); ASSERT_EQ(count, (kMaxElements + 1) / 2); } } TEST(SkipList, EstimateAverageNumberOfEquals2) { memgraph::utils::SkipList list; // 100k elements will yield an expected maximum height of 17. const int kMaxElements = 100000; const int kElementMembers = 1; // Create a list that has `kMaxElements` sets of `kElementMembers` items that // have same keys. { auto acc = list.access(); for (int64_t i = 0; i < kMaxElements; ++i) { for (int64_t j = 0; j < kElementMembers; ++j) { auto ret = acc.insert({i, j}); ASSERT_NE(ret.first, acc.end()); ASSERT_EQ(ret.first->key, i); ASSERT_EQ(ret.first->value, j); ASSERT_TRUE(ret.second); } } } // There are `kMaxElements * kElementMembers` members in the list. ASSERT_EQ(list.size(), kMaxElements * kElementMembers); // Benchmark the estimation function. BenchmarkEstimateAverageNumberOfEquals(&list, [](const auto &a, const auto &b) { return a.key == b.key; }); // Verify that the estimate on the lowest layer is correct. { auto acc = list.access(); uint64_t count = acc.estimate_average_number_of_equals([](const auto &a, const auto &b) { return a.key == b.key; }, 1); ASSERT_EQ(count, kElementMembers); } } TEST(SkipList, EstimateAverageNumberOfEquals3) { memgraph::utils::SkipList list; // 100k elements will yield an expected maximum height of 17 const int kMaxElements = 100; const int kElementMembers = 1000; // Create a list that has `kMaxElements` sets of `kElementMembers` items that // have same keys. { auto acc = list.access(); for (int64_t i = 0; i < kMaxElements; ++i) { for (int64_t j = 0; j < kElementMembers; ++j) { auto ret = acc.insert({i, j}); ASSERT_NE(ret.first, acc.end()); ASSERT_EQ(ret.first->key, i); ASSERT_EQ(ret.first->value, j); ASSERT_TRUE(ret.second); } } } // There are `kMaxElements * kElementMembers` members in the list. ASSERT_EQ(list.size(), kMaxElements * kElementMembers); // Benchmark the estimation function. BenchmarkEstimateAverageNumberOfEquals(&list, [](const auto &a, const auto &b) { return a.key == b.key; }); // Verify that the estimate on the lowest layer is correct. { auto acc = list.access(); uint64_t count = acc.estimate_average_number_of_equals([](const auto &a, const auto &b) { return a.key == b.key; }, 1); ASSERT_EQ(count, kElementMembers); } } TEST(SkipList, EstimateAverageNumberOfEquals4) { memgraph::utils::SkipList list; // ~300k elements will yield an expected maximum height of 18. const int kMaxElements = 100000; // Create a list that has `kMaxElements` sets of 1 or 3 items that have same // keys. The bias is 70% for a set that has 3 items, and 30% for a set that // has 1 items. std::mt19937 gen{std::random_device{}()}; std::uniform_real_distribution<> dis(0.0, 1.0); { auto acc = list.access(); for (int64_t i = 0; i < kMaxElements; ++i) { for (int64_t j = 0; j < (dis(gen) < 0.7 ? 3 : 1); ++j) { auto ret = acc.insert({i, j}); ASSERT_NE(ret.first, acc.end()); ASSERT_EQ(ret.first->key, i); ASSERT_EQ(ret.first->value, j); ASSERT_TRUE(ret.second); } } } // Benchmark the estimation function. BenchmarkEstimateAverageNumberOfEquals(&list, [](const auto &a, const auto &b) { return a.key == b.key; }); // Verify that the estimate on the lowest layer is correct. { auto acc = list.access(); uint64_t count = acc.estimate_average_number_of_equals([](const auto &a, const auto &b) { return a.key == b.key; }, 1); // Because the test is randomized, the exact estimate on the lowest layer // can't be known. But it definitely must be between 1 and 3 because the // clusters of items are of sizes 1 and 3. ASSERT_GE(count, 1); ASSERT_LE(count, 3); } } TEST(SkipList, EstimateAverageNumberOfEquals5) { memgraph::utils::SkipList list; // ~500k elements will yield an expected maximum height of 19. const int kMaxElements = 1000000; // Create a list that has `kMaxElements` items that have same keys. { auto acc = list.access(); for (int64_t i = 1; i <= kMaxElements; ++i) { auto ret = acc.insert({1, i}); ASSERT_NE(ret.first, acc.end()); ASSERT_EQ(ret.first->key, 1); ASSERT_EQ(ret.first->value, i); ASSERT_TRUE(ret.second); } } // There are `kMaxElements` members in the list. ASSERT_EQ(list.size(), kMaxElements); // Benchmark the estimation function. BenchmarkEstimateAverageNumberOfEquals(&list, [](const auto &a, const auto &b) { return a.key == b.key; }); // Verify that the estimate on the lowest layer is correct. { auto acc = list.access(); uint64_t count = acc.estimate_average_number_of_equals([](const auto &a, const auto &b) { return a.key == b.key; }, 1); ASSERT_EQ(count, kMaxElements); } }