#include "IntervalTree.hpp" #include "Tests.hpp" #include "Buffer.hpp" #include using namespace tp; struct Interval { [[nodiscard]] bool overlaps(const Interval& in) const { return in.start <= end && in.end >= start; } halnf start{}; halnf end{}; bool ignore = false; void random(halnf span, halnf scale = 1.f) { start = ((halnf) randomFloat()) * (span); end = ((halnf) randomFloat()) * (span); if (start > end) swap(start, end); auto len = (end - start) * scale * 0.5f; auto mid = (start + end) / 2.f; start = mid - len; end = mid + len; } void randomSized(halnf span, halnf size, halnf wobble) { start = ((halnf) randomFloat()) * (span); end = start + size + (halnf) (randomFloat() * wobble); } }; SUITE(IntervalTree) { TEST(FunctionalitySimple) { IntervalTree intervalTree; intervalTree.insert({ 0, 10 }, 1); intervalTree.insert({ 3, 6 }, 2); intervalTree.insert({ 8, 12 }, 3); intervalTree.forEachIntersection(4, 5, [](alni start, ualni end, ualni data) { printf("%i", int(data)); printf("\n"); }); } TEST(FunctionalityScale) { const int NUM_TEST_INTERVALS = 1000; const halnf SPAN = 100; Buffer pool; IntervalTree intervalTree; Buffer testIntervals; auto test = [&]() { for (auto testInterval : testIntervals) { Buffer correct; Buffer result; ualni idx = 0; for (auto interval : pool) { if (!interval->ignore && interval->overlaps(testInterval.data())) { correct.append(idx); } idx++; } intervalTree.forEachIntersection(testInterval->start, testInterval->end, [&](alni start, ualni end, ualni data) { result.append(data); }); CHECK(correct.size() == result.size()); if (!(correct.size() == result.size())) { printf("intersections - \n"); for (auto i : correct) { printf(" %i", (int) i.data()); } printf("\n"); for (auto i : result) { printf(" %i", (int) i.data()); } printf("\n\n"); } // todo compare containers } }; // initialize for (auto i : Range(NUM_TEST_INTERVALS)) { auto interval = Interval(); interval.random(SPAN); pool.append(interval); intervalTree.insert({ interval.start, interval.end }, i); interval.random(SPAN * 2.f, (halnf) randomFloat()); testIntervals.append(interval); } test(); // remove some for (auto i : Range(NUM_TEST_INTERVALS / 2)) { auto idx = ualni(randomFloat() * (alnf) pool.size()); pool[idx].ignore = true; intervalTree.remove({ pool[idx].start, pool[idx].end }); } test(); } TEST(Efficency) { struct Stat { halnf numItems = 0; halnf avgFound = 0; halnf avgChecks = 0; }; auto test = [&](ualni NUM_TEST_INTERVALS, ualni NUM_CHECKS) { const auto SPAN = (halnf) (halnf(NUM_TEST_INTERVALS)); const auto SCALE = (halnf) (2.f); IntervalTree intervalTree; Buffer testIntervals; auto WOBBLE = SPAN * 0; for (auto i : Range(NUM_TEST_INTERVALS)) { auto interval = Interval(); interval.randomSized(SPAN, SCALE, WOBBLE); intervalTree.insert({ interval.start, interval.end }, i); // WOBBLE -= 1.f; } for (auto i : Range(0)) intervalTree.insert({ (halnf) i * 0.01f, SPAN }, 0); WOBBLE = 0; for (auto i : Range(NUM_CHECKS)) { auto interval = Interval(); interval.randomSized(SPAN, SCALE, WOBBLE); testIntervals.append(interval); } ualni debugMaxChecks = 0; ualni debugMaxFound = 0; halnf debugAvgChecks = 0; halnf debugAvgFound = 0; for (auto testInterval : testIntervals) { ualni debugFound = 0; ualni debug = intervalTree.forEachIntersection( testInterval->start, testInterval->end, [&](ualni start, ualni end, ualni data) { debugFound++; // } ); if (debug > debugMaxChecks) { debugMaxChecks = debug; debugMaxFound = debugFound; } debugMaxChecks = max(debug, debugMaxChecks); debugAvgChecks += (halnf) debug; debugAvgFound += (halnf) debugFound; } debugAvgChecks /= (halnf) testIntervals.size(); debugAvgFound /= (halnf) testIntervals.size(); printf("\nItems : %llu\n", NUM_TEST_INTERVALS); printf("Avg(checks) : %f\n", debugAvgChecks); printf("Avg(found) : %f\n", debugAvgFound); printf("Max checks : %llu\n", debugMaxChecks); printf("Max checks found : %llu\n", debugMaxFound); printf("N(Avg(checks) / Avg(N(items)) : %f\n", debugAvgChecks / (halnf) intervalTree.size()); printf("N(Avg(found)) / Avg(N(items)) : %f\n", debugAvgFound / (halnf) intervalTree.size()); printf("Avg(found) / Avg(checks) : %f\n", debugAvgFound / debugAvgChecks); return Stat{ (halnf) NUM_TEST_INTERVALS, debugAvgFound / (halnf) intervalTree.size(), debugAvgChecks / (halnf) intervalTree.size() }; }; Buffer stats; for (auto i : Range(2, 5)) { Stat stat = test(pow(10, i), 100); stats.append(stat); } printf("\nChecks: "); for (auto stat : stats) printf("%e ", stat->avgChecks); printf("\nHits: "); for (auto stat : stats) printf("%e ", stat->avgFound); printf("\nItems: "); for (auto stat : stats) printf("%e ", stat->numItems); printf("\n\n"); } TEST(FunctionalityComplex) { IntervalTree intervals; struct QueryResult { ualni numFound = 0; ualni lastDataFound = 0; [[nodiscard]] bool isSingleData(ualni aData) const { return numFound == 1 && lastDataFound == aData; } [[nodiscard]] bool notFound() const { return numFound == 0; } [[nodiscard]] bool found(ualni num) const { return numFound == num; } }; auto makeQuery = [&](ualni aStart, ualni aEnd) { QueryResult out; intervals.forEachIntersection(aStart, aEnd, [&](alni start, ualni end, ualni data) { out.numFound++; out.lastDataFound = data; }); return out; }; intervals.insert({ 2, 5 }, 1); intervals.insert({ 12, 15 }, 2); intervals.insert({ 22, 25 }, 3); CHECK(makeQuery(1, 6).isSingleData(1)); CHECK(makeQuery(1, 3).isSingleData(1)); CHECK(makeQuery(3, 6).isSingleData(1)); CHECK(makeQuery(0, 1).notFound()); CHECK(makeQuery(7, 8).notFound()); CHECK(makeQuery(3, 4).isSingleData(1)); CHECK(makeQuery(13, 14).isSingleData(2)); CHECK(makeQuery(1, 35).found(3)); CHECK(makeQuery(11, 35).found(2)); // check opened CHECK(makeQuery(5, 12).found(2)); CHECK(makeQuery(15, 22).found(2)); CHECK(makeQuery(1, 2).isSingleData(1)); CHECK(makeQuery(25, 35).isSingleData(3)); intervals.removeAll(); intervals.insert({ 0, 3 }, 1); intervals.insert({ 0, 13 }, 2); CHECK(makeQuery(0, 1).found(2)); } }