diff --git a/Containers/public/IntervalTree.hpp b/Containers/public/IntervalTree.hpp new file mode 100644 index 0000000..963b3c4 --- /dev/null +++ b/Containers/public/IntervalTree.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include "Tree.hpp" + +namespace tp { + + template + 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 + 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 + class IntervalTree : public AvlTree, tData> { + typedef AvlTree, tData>::Node Node; + + public: + IntervalTree() = default; + + template + ualni forEachIntersection(tType start, tType end, tFunctor functor) const { + ualni debug = 0; + forEachIntersectionUtil(this->head(), start, end, functor, debug); + return debug; + } + + private: + template + 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); + } + }; +} \ No newline at end of file diff --git a/Containers/public/Tree.hpp b/Containers/public/Tree.hpp index 9506b16..51c2a6b 100644 --- a/Containers/public/Tree.hpp +++ b/Containers/public/Tree.hpp @@ -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 + inline void updateTreeCacheCallBack(const NodeType&) {} }; template @@ -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: diff --git a/Containers/tests/IntervalTreeTests.cpp b/Containers/tests/IntervalTreeTests.cpp new file mode 100644 index 0000000..f83b0a7 --- /dev/null +++ b/Containers/tests/IntervalTreeTests.cpp @@ -0,0 +1,223 @@ +#include "IntervalTree.hpp" +#include "Testing.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); + } +}; + +TEST_DEF_STATIC(FunctionalitySimple) { + + IntervalTree 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 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, [&](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(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_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 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 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_DEF_STATIC(Benchmarks) {} + +TEST_DEF(IntervalTree) { + testFunctionalitySimple(); + testFunctionalityScale(); + testEfficency(); + testBenchmarks(); +} diff --git a/Containers/tests/Tests.cpp b/Containers/tests/Tests.cpp index b051deb..4f15914 100644 --- a/Containers/tests/Tests.cpp +++ b/Containers/tests/Tests.cpp @@ -16,6 +16,7 @@ int main() { return 1; } + testIntervalTree(); testList(); testMap(); testAvl(); diff --git a/Containers/tests/Tests.hpp b/Containers/tests/Tests.hpp index 24a93e3..e93d65c 100644 --- a/Containers/tests/Tests.hpp +++ b/Containers/tests/Tests.hpp @@ -33,4 +33,5 @@ void testList(); void testMap(); void testAvl(); void testBuffer(); -void testBuffer2d(); \ No newline at end of file +void testBuffer2d(); +void testIntervalTree(); \ No newline at end of file diff --git a/Modules/public/Common.hpp b/Modules/public/Common.hpp index 44741d0..71de466 100644 --- a/Modules/public/Common.hpp +++ b/Modules/public/Common.hpp @@ -93,6 +93,7 @@ namespace tp { // power template [[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;