276 lines
No EOL
6.7 KiB
C++
276 lines
No EOL
6.7 KiB
C++
#include "IntervalTree.hpp"
|
|
#include "Tests.hpp"
|
|
|
|
#include "Buffer.hpp"
|
|
|
|
#include <cstdio>
|
|
|
|
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) {
|
|
auto offset = 0;
|
|
|
|
start = ((halnf) randomFloat()) * (span) - offset;
|
|
end = ((halnf) randomFloat()) * (span) - offset;
|
|
|
|
if (start > end) swapV(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<ualni, ualni> 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 = 1000;
|
|
|
|
Buffer<Interval> pool;
|
|
IntervalTree<halnf, ualni> intervalTree;
|
|
|
|
Buffer<Interval> testIntervals;
|
|
|
|
auto test = [&]() {
|
|
for (auto testInterval : testIntervals) {
|
|
|
|
Buffer<ualni> correct;
|
|
Buffer<ualni> 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 : IterRange<ualni>(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 : IterRange<ualni>(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<halnf, ualni> intervalTree;
|
|
Buffer<Interval> testIntervals;
|
|
|
|
auto WOBBLE = SPAN * 0;
|
|
for (auto i : IterRange<ualni>(NUM_TEST_INTERVALS)) {
|
|
auto interval = Interval();
|
|
interval.randomSized(SPAN, SCALE, WOBBLE);
|
|
intervalTree.insert({ interval.start, interval.end }, i);
|
|
// WOBBLE -= 1.f;
|
|
}
|
|
|
|
for (auto i : IterRange(0))
|
|
intervalTree.insert({ (halnf) i * 0.01f, SPAN }, 0);
|
|
|
|
WOBBLE = 0;
|
|
for (auto i : IterRange<ualni>(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<Stat> stats;
|
|
for (auto i : IterRange(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<ualni, ualni> 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));
|
|
}
|
|
} |