/* * Unit tests for data structures. * * Depends on the Boost.Test unit test framework. * Tested with Boost v1.74, available from https://www.boost.org/ */ #include #include #include #include #include #include #include #include #include #define BOOST_TEST_MODULE datastruct #include #include "datastruct.h" /* ********** Test DisjointSetNode ********** */ BOOST_AUTO_TEST_SUITE(test_disjoint_set) BOOST_AUTO_TEST_CASE(test_single) { using Node = DisjointSetNode; Node a(1); BOOST_TEST(a.find() == 1); a.set_label(2); BOOST_TEST(a.find() == 2); } BOOST_AUTO_TEST_CASE(test_simple) { using Node = DisjointSetNode; Node a(1); Node b(2); Node c(3); Node* m = a.merge(&b); m->set_label(10); BOOST_TEST(a.find() == 10); BOOST_TEST(b.find() == 10); BOOST_TEST(c.find() == 3); m = m->merge(&c); m->set_label(11); BOOST_TEST(a.find() == 11); BOOST_TEST(c.find() == 11); a.detach(1); b.detach(2); c.detach(3); BOOST_TEST(a.find() == 1); BOOST_TEST(b.find() == 2); BOOST_TEST(c.find() == 3); } BOOST_AUTO_TEST_CASE(test_multilevel) { using Node = DisjointSetNode; std::unique_ptr nodes[27]; for (int i = 0; i < 27; ++i) { nodes[i].reset(new Node(i)); } std::vector level1; for (int i = 0; i < 9; ++i) { Node* m = nodes[3*i].get(); for (int k = 1; k < 3; ++k) { m = m->merge(nodes[3*i+k].get()); } m->set_label(100 + i); level1.push_back(m); } std::vector level2; for (int i = 0; i < 3; ++i) { Node* m = level1[3*i]; for (int k = 1; k < 3; ++k) { m = m->merge(level1[3*i+k]); } m->set_label(200 + i); level2.push_back(m); } Node* m = level2[0]; for (int k = 1; k < 3; ++k) { m = m->merge(level2[k]); } m->set_label(300); for (int i = 0; i < 27; ++i) { BOOST_TEST(nodes[i]->find() == 300); } for (int i = 0; i < 3; ++i) { level2[i]->detach(200 + i); } for (int i = 0; i < 27; ++i) { BOOST_TEST(nodes[i]->find() == 200 + i / 9); } for (int i = 0; i < 6; ++i) { level1[i]->detach(100 + i); } for (int i = 0; i < 18; ++i) { BOOST_TEST(nodes[i]->find() == 100 + i / 3); } for (int i = 18; i < 27; ++i) { BOOST_TEST(nodes[i]->find() == 202); } for (int i = 6; i < 9; ++i) { level1[i]->detach(100 + i); } for (int i = 0; i < 27; ++i) { BOOST_TEST(nodes[i]->find() == 100 + i / 3); } for (int i = 6; i < 27; ++i) { nodes[i]->detach(i); } for (int i = 0; i < 6; ++i) { BOOST_TEST(nodes[i]->find() == 100 + i / 3); } for (int i = 6; i < 27; ++i) { BOOST_TEST(nodes[i]->find() == i); } for (int i = 0; i < 6; i++) { nodes[i]->detach(i); } for (int i = 0; i < 27; ++i) { BOOST_TEST(nodes[i]->find() == i); } } BOOST_AUTO_TEST_CASE(test_random) { using Node = DisjointSetNode; std::mt19937 rng(12345); const int num_nodes = 1000; std::unique_ptr nodes[num_nodes]; for (int i = 0; i < num_nodes; ++i) { nodes[i].reset(new Node(i)); } std::unordered_map blossoms; std::unordered_map> sub_blossoms; std::vector top_blossoms; for (int i = 0; i < num_nodes; ++i) { blossoms[i] = nodes[i].get(); top_blossoms.push_back(i); } int next_blossom = num_nodes; auto make_blossom = [&]() { int b = next_blossom; ++next_blossom; int nsub = 1 + 2 * std::uniform_int_distribution<>(1, 4)(rng); std::vector subs(nsub); for (int i = 0; i < nsub; ++i) { int p = std::uniform_int_distribution<>(0, top_blossoms.size() - 1)(rng); subs[i] = top_blossoms[p]; top_blossoms.erase(top_blossoms.begin() + p); } Node *m = blossoms[subs[0]]; for (int i = 1; i < nsub; ++i) { m = m->merge(blossoms[subs[i]]); } m->set_label(b); blossoms[b] = m; sub_blossoms[b] = std::move(subs); top_blossoms.push_back(b); }; auto expand_blossom = [&](int b) { top_blossoms.erase( std::find(top_blossoms.begin(), top_blossoms.end(), b)); for (int t : sub_blossoms[b]) { blossoms[t]->detach(t); top_blossoms.push_back(t); } blossoms.erase(b); sub_blossoms.erase(b); }; auto check_membership = [&](int b, int label) { std::vector q; q.push_back(b); while (! q.empty()) { b = q.back(); q.pop_back(); if (b < num_nodes) { BOOST_TEST(nodes[b]->find() == label); } else { for (int t : sub_blossoms[b]) { q.push_back(t); } } } }; for (int k = 0; k < 100; ++k) { make_blossom(); } for (int b : top_blossoms) { check_membership(b, b); } std::vector top_groups; for (int b : top_blossoms) { if (b >= num_nodes) { top_groups.push_back(b); } } std::shuffle(top_groups.begin(), top_groups.end(), rng); for (int k = 0; k < 50; ++k) { expand_blossom(top_groups[k]); } top_groups.clear(); for (int b : top_blossoms) { check_membership(b, b); } for (int k = 0; k < 50; ++k) { make_blossom(); } for (int b : top_blossoms) { check_membership(b, b); } for (int b : top_blossoms) { if (b >= num_nodes) { top_groups.push_back(b); } } std::shuffle(top_groups.begin(), top_groups.end(), rng); for (int b : top_groups) { expand_blossom(b); } for (int b : top_blossoms) { check_membership(b, b); } } BOOST_AUTO_TEST_SUITE_END() /* ********** Test SplittablePriorityQueue ********** */ template static void check_min_elem(const std::pair& pair, PrioType prio, const DataType& data) { BOOST_TEST(pair.first == prio); BOOST_TEST(pair.second == data); } BOOST_AUTO_TEST_SUITE(test_splittable_queue) BOOST_AUTO_TEST_CASE(test_single) { using Queue = SplittablePriorityQueue; Queue q; BOOST_TEST(q.empty() == true); Queue::Node n; q.insert(&n, 3, 4, 101); BOOST_TEST(q.empty() == false); check_min_elem(q.find_min(), 4, 101); q.update(&n, 5, 102); check_min_elem(q.find_min(), 4, 101); q.update(&n, 3, 103); check_min_elem(q.find_min(), 3, 103); q.clear(); BOOST_TEST(q.empty() == true); } BOOST_AUTO_TEST_CASE(test_simple) { using Queue = SplittablePriorityQueue; Queue q; Queue::Node n1, n2, n3, n4, n5, nx; q.insert(&n1, 1, 5, "a"); q.insert(&n2, 2, 6, "b"); q.insert(&n3, 3, 7, "c"); q.insert(&n4, 4, 4, "d"); q.insert(&n5, 5, 3, "e"); check_min_elem(q.find_min(), 3, std::string("e")); q.update(&n1, 4, "f"); check_min_elem(q.find_min(), 3, std::string("e")); q.update(&n3, 2, "h"); check_min_elem(q.find_min(), 2, std::string("h")); Queue q2 = q.split(3); check_min_elem(q.find_min(), 4, std::string("f")); check_min_elem(q2.find_min(), 2, std::string("h")); q.insert(&nx, 3, 1, "x"); check_min_elem(q.find_min(), 1, std::string("x")); check_min_elem(q2.find_min(), 2, std::string("h")); q.clear(); q2.clear(); } BOOST_AUTO_TEST_CASE(test_split_empty) { using Queue = SplittablePriorityQueue; Queue q; Queue q2 = q.split(10); BOOST_TEST(q.empty() == true); BOOST_TEST(q2.empty() == true); } BOOST_AUTO_TEST_CASE(test_split_oneway) { using Queue = SplittablePriorityQueue; Queue q; Queue::Node n4, n5, n6; q.insert(&n4, 4, 3, "a"); q.insert(&n5, 5, 4, "b"); q.insert(&n6, 6, 2, "c"); Queue q2 = q.split(7); BOOST_TEST(q.empty() == false); BOOST_TEST(q2.empty() == true); check_min_elem(q.find_min(), 2, std::string("c")); q.clear(); q.insert(&n4, 4, 3, "a"); q.insert(&n5, 5, 4, "b"); q.insert(&n6, 6, 2, "c"); q2 = q.split(4); BOOST_TEST(q.empty() == true); BOOST_TEST(q2.empty() == false); check_min_elem(q2.find_min(), 2, std::string("c")); q2.clear(); } BOOST_AUTO_TEST_CASE(test_larger) { using Queue = SplittablePriorityQueue; Queue q; Queue::Node nodes[15]; q.insert(&nodes[7], 7, 5, "h"); q.insert(&nodes[6], 6, 4, "g"); q.insert(&nodes[8], 8, 2, "i"); q.insert(&nodes[5], 5, 4, "f"); q.insert(&nodes[9], 9, 6, "j"); q.insert(&nodes[4], 4, 8, "e"); q.insert(&nodes[10], 10, 4, "k"); q.insert(&nodes[3], 3, 5, "d"); q.insert(&nodes[11], 11, 6, "l"); q.insert(&nodes[2], 2, 7, "c"); q.insert(&nodes[12], 12, 8, "m"); q.insert(&nodes[1], 1, 3, "b"); q.insert(&nodes[13], 13, 1, "n"); q.insert(&nodes[0], 0, 9, "a"); q.insert(&nodes[14], 14, 7, "o"); check_min_elem(q.find_min(), 1, std::string("n")); Queue q2 = q.split(10); check_min_elem(q.find_min(), 2, std::string("i")); check_min_elem(q2.find_min(), 1, std::string("n")); Queue q1 = q.split(5); check_min_elem(q.find_min(), 3, std::string("b")); check_min_elem(q1.find_min(), 2, std::string("i")); check_min_elem(q2.find_min(), 1, std::string("n")); q.clear(); q1.clear(); q2.clear(); } BOOST_AUTO_TEST_CASE(test_random) { using Queue = SplittablePriorityQueue; std::mt19937 rng(23456); std::uniform_int_distribution<> index_distribution(0, 1000000); std::uniform_int_distribution<> prio_distribution(0, 1000000); Queue q; std::map, int, int>> elems; int next_data = 1; for (int i = 0; i < 200; ++i) { // Insert stuff into the queue. for (int k = 0; k < 1000; ++k) { int idx = index_distribution(rng); int prio = prio_distribution(rng); int data = next_data; ++next_data; auto it = elems.find(idx); if (it != elems.end()) { auto& v = it->second; Queue::Node* nptr = std::get<0>(v).get(); int pprio = std::get<1>(v); q.update(nptr, prio, data); if (prio < pprio) { std::get<1>(v) = prio; std::get<2>(v) = data; } } else { std::unique_ptr nptr(new Queue::Node); q.insert(nptr.get(), idx, prio, data); elems[idx] = std::make_tuple(std::move(nptr), prio, data); } } // Check min element. int min_prio = INT_MAX; int min_data = 0; for (const auto& v : elems) { int prio = std::get<1>(v.second); int data = std::get<2>(v.second); if (prio < min_prio) { min_prio = prio; min_data = data; } } check_min_elem(q.find_min(), min_prio, min_data); // Split the queue. int threshold = index_distribution(rng); Queue q2 = q.split(threshold); // Keep one queue and discard the other. if (rng() % 2 == 0) { q.clear(); q = std::move(q2); elems.erase(elems.begin(), elems.lower_bound(threshold)); } else { q2.clear(); elems.erase(elems.lower_bound(threshold), elems.end()); } // Check min element. min_prio = INT_MAX; min_data = 0; for (const auto& v : elems) { int prio = std::get<1>(v.second); int data = std::get<2>(v.second); if (prio < min_prio) { min_prio = prio; min_data = data; } } if (min_prio < INT_MAX) { check_min_elem(q.find_min(), min_prio, min_data); } } q.clear(); } BOOST_AUTO_TEST_SUITE_END() /* ********** Test PriorityQueue ********** */ BOOST_AUTO_TEST_SUITE(test_priority_queue) BOOST_AUTO_TEST_CASE(test_empty) { using Queue = PriorityQueue; Queue q; BOOST_TEST(q.empty() == true); } BOOST_AUTO_TEST_CASE(test_single) { using Queue = PriorityQueue; Queue q; Queue::Node n1; q.insert(&n1, 5, "a"); BOOST_TEST(q.empty() == false); check_min_elem(q.find_min(), 5, std::string("a")); q.update(&n1, 3, "a"); check_min_elem(q.find_min(), 3, std::string("a")); q.remove(&n1); BOOST_TEST(q.empty() == true); } BOOST_AUTO_TEST_CASE(test_simple) { using Queue = PriorityQueue; Queue q; Queue::Node nodes[10]; q.insert(&nodes[0], 9, 'a'); check_min_elem(q.find_min(), 9, 'a'); q.insert(&nodes[1], 4, 'b'); check_min_elem(q.find_min(), 4, 'b'); q.insert(&nodes[2], 7, 'c'); check_min_elem(q.find_min(), 4, 'b'); q.insert(&nodes[3], 5, 'd'); check_min_elem(q.find_min(), 4, 'b'); q.insert(&nodes[4], 8, 'e'); check_min_elem(q.find_min(), 4, 'b'); q.insert(&nodes[5], 6, 'f'); check_min_elem(q.find_min(), 4, 'b'); q.insert(&nodes[6], 4, 'g'); q.insert(&nodes[7], 5, 'h'); q.insert(&nodes[8], 2, 'i'); check_min_elem(q.find_min(), 2, 'i'); q.insert(&nodes[9], 6, 'j'); check_min_elem(q.find_min(), 2, 'i'); q.update(&nodes[2], 1, 'c'); check_min_elem(q.find_min(), 1, 'c'); q.update(&nodes[4], 3, 'e'); check_min_elem(q.find_min(), 1, 'c'); q.remove(&nodes[2]); check_min_elem(q.find_min(), 2, 'i'); q.remove(&nodes[8]); check_min_elem(q.find_min(), 3, 'e'); q.remove(&nodes[4]); q.remove(&nodes[1]); check_min_elem(q.find_min(), 4, 'g'); q.remove(&nodes[3]); q.remove(&nodes[9]); check_min_elem(q.find_min(), 4, 'g'); q.remove(&nodes[6]); check_min_elem(q.find_min(), 5, 'h'); BOOST_TEST(q.empty() == false); q.clear(); BOOST_TEST(q.empty() == true); } BOOST_AUTO_TEST_CASE(test_random) { using Queue = PriorityQueue; Queue q; const int num_elem = 1000; std::vector, int, int>> elems; int next_data = 0; std::mt19937 rng(34567); auto check = [&q,&elems]() { int min_prio, min_data; std::tie(min_prio, min_data) = q.find_min(); int best_prio = INT_MAX; bool found = false; for (const auto& v : elems) { int this_prio = std::get<1>(v); int this_data = std::get<2>(v); best_prio = std::min(best_prio, this_prio); if ((this_prio == min_prio) && (this_data == min_data)) { found = true; } } BOOST_TEST(found == true); BOOST_TEST(min_prio == best_prio); }; for (int i = 0; i < num_elem; ++i) { ++next_data; int prio = std::uniform_int_distribution<>(0, 1000000)(rng); std::unique_ptr nptr(new Queue::Node); q.insert(nptr.get(), prio, next_data); elems.push_back(std::make_tuple(std::move(nptr), prio, next_data)); check(); } for (int i = 0; i < 10000; ++i) { int p = std::uniform_int_distribution<>(0, num_elem - 1)(rng); Queue::Node* node = std::get<0>(elems[p]).get(); int prio = std::get<1>(elems[p]); int data = std::get<2>(elems[p]); prio = std::uniform_int_distribution<>(0, prio)(rng); q.update(node, prio, data); std::get<1>(elems[p]) = prio; check(); p = std::uniform_int_distribution<>(0, num_elem - 1)(rng); node = std::get<0>(elems[p]).get(); q.remove(node); elems.erase(elems.begin() + p); check(); ++next_data; prio = std::uniform_int_distribution<>(0, 1000000)(rng); std::unique_ptr nptr(new Queue::Node); q.insert(nptr.get(), prio, next_data); elems.push_back(std::make_tuple(std::move(nptr), prio, next_data)); check(); } for (int i = 0; i < num_elem; ++i) { int p = std::uniform_int_distribution<>(0, num_elem - 1 - i)(rng); Queue::Node* node = std::get<0>(elems[p]).get(); q.remove(node); elems.erase(elems.begin() + p); if (! elems.empty()) { check(); } } BOOST_TEST(q.empty() == true); } BOOST_AUTO_TEST_SUITE_END()