Interval Tree added
This commit is contained in:
parent
8955a02d05
commit
782a041ba8
6 changed files with 311 additions and 4 deletions
80
Containers/public/IntervalTree.hpp
Normal file
80
Containers/public/IntervalTree.hpp
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#pragma once
|
||||
|
||||
#include "Tree.hpp"
|
||||
|
||||
namespace tp {
|
||||
|
||||
template <typename tType>
|
||||
class IntervalKey {
|
||||
public:
|
||||
IntervalKey() = default;
|
||||
|
||||
IntervalKey(tType start, tType end) {
|
||||
mStart = start;
|
||||
mEnd = end;
|
||||
}
|
||||
|
||||
inline bool descentRight(const IntervalKey& in) const { return in.mStart > mStart; }
|
||||
inline bool descentLeft(const IntervalKey& in) const { return in.mStart <= mStart; }
|
||||
inline bool exactNode(const IntervalKey& in) const { return in.mStart == mStart && in.mEnd == mEnd; }
|
||||
|
||||
inline const IntervalKey& getFindKey() const { return *this; }
|
||||
inline const IntervalKey& keyInRightSubtree(const IntervalKey& in) const { return in; }
|
||||
inline const IntervalKey& keyInLeftSubtree(const IntervalKey& in) const { return in; }
|
||||
|
||||
template <typename tTreeNodeType>
|
||||
inline void updateTreeCacheCallBack(const tTreeNodeType& node) {
|
||||
mMax = 0;
|
||||
if (node.mRight && node.mRight->key.mMax > mMax) mMax = node.mRight->key.mMax;
|
||||
if (node.mLeft && node.mLeft->key.mMax > mMax) mMax = node.mLeft->key.mMax;
|
||||
if (mMax < mEnd) mMax = mEnd;
|
||||
}
|
||||
|
||||
public:
|
||||
tType mStart = tType();
|
||||
tType mEnd = tType();
|
||||
tType mMax = tType();
|
||||
};
|
||||
|
||||
template <typename tType, typename tData>
|
||||
class IntervalTree : public AvlTree<IntervalKey<tType>, tData> {
|
||||
typedef AvlTree<IntervalKey<tType>, tData>::Node Node;
|
||||
|
||||
public:
|
||||
IntervalTree() = default;
|
||||
|
||||
template <typename tFunctor>
|
||||
ualni forEachIntersection(tType start, tType end, tFunctor functor) const {
|
||||
ualni debug = 0;
|
||||
forEachIntersectionUtil(this->head(), start, end, functor, debug);
|
||||
return debug;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename tFunctor>
|
||||
void forEachIntersectionUtil(const Node* node, tType start, tType end, tFunctor functor, ualni& debug) const {
|
||||
if (node == nullptr) return;
|
||||
|
||||
debug++;
|
||||
|
||||
// If 'start' is to the right of the rightmost point of any interval
|
||||
// in this node and all children, there won't be any matches.
|
||||
if (start > node->key.mMax) return;
|
||||
|
||||
// Search left children
|
||||
forEachIntersectionUtil(node->mLeft, start, end, functor, debug);
|
||||
|
||||
// Check this node
|
||||
if (start < node->key.mEnd && end > node->key.mStart) {
|
||||
functor(node->data);
|
||||
}
|
||||
|
||||
// If end is to the left of the start of this interval,
|
||||
// then it can't be in any child to the right.
|
||||
if (end < node->key.mStart) return;
|
||||
|
||||
// Otherwise, search right children
|
||||
forEachIntersectionUtil(node->mRight, start, end, functor, debug);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -21,7 +21,8 @@ namespace tp {
|
|||
inline AvlNumericKey keyInRightSubtree(AvlNumericKey in) const { return in; }
|
||||
inline AvlNumericKey keyInLeftSubtree(AvlNumericKey in) const { return in; }
|
||||
|
||||
inline void updateTreeCacheCallBack() {}
|
||||
template <typename NodeType>
|
||||
inline void updateTreeCacheCallBack(const NodeType&) {}
|
||||
};
|
||||
|
||||
template <typename Key, typename Data, class Allocator = DefaultAllocator>
|
||||
|
|
@ -42,7 +43,7 @@ namespace tp {
|
|||
Data data;
|
||||
Key key;
|
||||
|
||||
private:
|
||||
public:
|
||||
Node* mLeft = nullptr;
|
||||
Node* mRight = nullptr;
|
||||
Node* mParent = nullptr;
|
||||
|
|
@ -57,7 +58,7 @@ namespace tp {
|
|||
inline KeyArg keyInRightSubtree(KeyArg aKey) const { return key.keyInRightSubtree(aKey); }
|
||||
inline KeyArg keyInLeftSubtree(KeyArg aKey) const { return key.keyInLeftSubtree(aKey); }
|
||||
|
||||
inline void updateTreeCacheCallBack() { key.updateTreeCacheCallBack(); }
|
||||
inline void updateTreeCacheCallBack() { key.updateTreeCacheCallBack(*this); }
|
||||
};
|
||||
|
||||
private:
|
||||
|
|
|
|||
223
Containers/tests/IntervalTreeTests.cpp
Normal file
223
Containers/tests/IntervalTreeTests.cpp
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
#include "IntervalTree.hpp"
|
||||
#include "Testing.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) {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_DEF_STATIC(FunctionalitySimple) {
|
||||
|
||||
IntervalTree<ualni, ualni> intervalTree;
|
||||
|
||||
intervalTree.insert({ 0, 10 }, 1);
|
||||
intervalTree.insert({ 3, 6 }, 2);
|
||||
intervalTree.insert({ 8, 12 }, 3);
|
||||
|
||||
intervalTree.forEachIntersection(4, 5, [](ualni data) {
|
||||
printf("%i", int(data));
|
||||
printf("\n");
|
||||
});
|
||||
}
|
||||
|
||||
TEST_DEF_STATIC(FunctionalityScale) {
|
||||
|
||||
const int NUM_TEST_INTERVALS = 1000;
|
||||
const halnf SPAN = 100;
|
||||
|
||||
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, [&](ualni data) {
|
||||
result.append(data);
|
||||
});
|
||||
|
||||
TEST(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<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 : Range<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_DEF_STATIC(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)) * 0 + 1000;
|
||||
const auto SCALE = (halnf) (2.f);
|
||||
|
||||
IntervalTree<halnf, ualni> intervalTree;
|
||||
Buffer<Interval> testIntervals;
|
||||
|
||||
auto WOBBLE = SPAN * 0;
|
||||
for (auto i : Range<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 : Range(0))
|
||||
intervalTree.insert({ (halnf) i * 0.01f, SPAN }, 0);
|
||||
|
||||
WOBBLE = 0;
|
||||
for (auto i : Range<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 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 : 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_DEF_STATIC(Benchmarks) {}
|
||||
|
||||
TEST_DEF(IntervalTree) {
|
||||
testFunctionalitySimple();
|
||||
testFunctionalityScale();
|
||||
testEfficency();
|
||||
testBenchmarks();
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ int main() {
|
|||
return 1;
|
||||
}
|
||||
|
||||
testIntervalTree();
|
||||
testList();
|
||||
testMap();
|
||||
testAvl();
|
||||
|
|
|
|||
|
|
@ -33,4 +33,5 @@ void testList();
|
|||
void testMap();
|
||||
void testAvl();
|
||||
void testBuffer();
|
||||
void testBuffer2d();
|
||||
void testBuffer2d();
|
||||
void testIntervalTree();
|
||||
|
|
@ -93,6 +93,7 @@ namespace tp {
|
|||
// power
|
||||
template <typename T>
|
||||
[[nodiscard]] T pow(T x, uhalni n) {
|
||||
if (n == 0) return 1;
|
||||
T out = x;
|
||||
for (uhalni i = 0; i < n - 1; i++) {
|
||||
out = out * x;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue