From 35acb75f24eb2c196aeae4d419cd40f17361fbc9 Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Sat, 26 Jul 2025 15:50:54 +0200 Subject: [PATCH 01/11] Added optional type. --- CMakeLists.txt | 6 + cmake/options.cmake | 4 +- include/interval-tree/optional.hpp | 430 +++++++++++++++++++++++++++++ 3 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 include/interval-tree/optional.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 17b5d4f..b8b88ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,12 @@ project(interval-tree) add_library(interval-tree INTERFACE) +if(INT_TREE_USE_OPTIONAL_POLYFILL) + target_compile_definitions(interval-tree INTERFACE + -DINTERVAL_TREE_USE_OPTIONAL_POLYFILL + ) +endif() + target_include_directories(interval-tree INTERFACE ./include) if(INT_TREE_DRAW_EXAMPLES) diff --git a/cmake/options.cmake b/cmake/options.cmake index 5eef87f..4cb7f7e 100644 --- a/cmake/options.cmake +++ b/cmake/options.cmake @@ -1,2 +1,4 @@ option(INT_TREE_DRAW_EXAMPLES "Draws some examples in a subdirectory. run make_drawable.sh before this" OFF) -option(INT_TREE_ENABLE_TESTS "Enable tests?" OFF) \ No newline at end of file +option(INT_TREE_ENABLE_TESTS "Enable tests?" OFF) +# You generally do not want to turn this on, unless you are testing the library. +option(INT_TREE_USE_OPTIONAL_POLYFILL "Use optional polyfill?" OFF) \ No newline at end of file diff --git a/include/interval-tree/optional.hpp b/include/interval-tree/optional.hpp new file mode 100644 index 0000000..e50bc9c --- /dev/null +++ b/include/interval-tree/optional.hpp @@ -0,0 +1,430 @@ +#pragma once + +#ifndef INTERVAL_TREE_USE_OPTIONAL_POLYFILL +# if __cplusplus >= 201703L +# include +namespace lib_interval_tree +{ + using nullopt_t = std::nullopt_t; + template + using optional = std::optional; + constexpr auto nullopt = std::nullopt; +} +# elif defined(INTERVAL_TREE_HAVE_BOOST_OPTIONAL) +# include +template +namespace lib_interval_tree +{ + template + using optional = boost::optional; + constexpr auto nullopt = boost::none; + using nullopt_t = decltype(boost::none); +} +# else +# define INTERVAL_TREE_USE_OPTIONAL_POLYFILL +# endif +#endif + +/** + * Note that the interval tree optional is not a complete replacement for std::optional or boost::optional. + * It is missing some features, such as comparison operators. It does not follow the standard. + */ +#ifdef INTERVAL_TREE_USE_OPTIONAL_POLYFILL +# include +# include +# include +namespace lib_interval_tree +{ + class bad_optional_access : public std::logic_error + { + public: + bad_optional_access() + : logic_error("bad optional access") + {} + virtual ~bad_optional_access() noexcept = default; + }; + + struct nullopt_t + { + nullopt_t() = delete; + enum class construct + { + token + }; + explicit constexpr nullopt_t(construct) noexcept + {} + }; + constexpr nullopt_t nullopt{nullopt_t::construct::token}; + + struct in_place_t + { + in_place_t() = delete; + enum class construct + { + token + }; + explicit constexpr in_place_t(construct) noexcept + {} + }; + constexpr in_place_t in_place{in_place_t::construct::token}; + +# define INTERVAL_TREE_OPTIONAL_INTESTINES \ +\ + public: \ + using stored_type = typename std::remove_const::type; \ +\ + public: \ + constexpr optional_base(nullopt_t) noexcept \ + : optional_base{} \ + {} \ +\ + template \ + constexpr explicit optional_base(in_place_t, Args&&... args) \ + : value_(std::forward(args)...) \ + , engaged_{true} \ + {} \ +\ + template < \ + typename U, \ + typename... Args, \ + std::enable_if_t&, Args&&...>::value, int>...> \ + constexpr explicit optional_base(in_place_t, std::initializer_list il, Args&&... args) \ + : value_(il, std::forward(args)...) \ + , engaged_{true} \ + {} \ +\ + constexpr optional_base() noexcept \ + : empty_value_{} \ + {} \ +\ + optional_base(optional_base const& other) \ + { \ + if (other.engaged_) \ + construct(other.value_); \ + } \ +\ + optional_base(optional_base&& other) noexcept(std::is_nothrow_move_constructible::value) \ + { \ + if (other.engaged_) \ + construct(std::move(other.value_)); \ + } \ +\ + optional_base& operator=(optional_base const& other) \ + { \ + if (engaged_ && other.engaged_) \ + value_ = other.value_; \ + else \ + { \ + if (other.engaged_) \ + construct(other.value_); \ + else \ + reset(); \ + } \ +\ + return *this; \ + } \ +\ + optional_base& operator=(optional_base&& other) noexcept( \ + std::is_nothrow_move_constructible::value && std::is_nothrow_move_assignable::value \ + ) \ + { \ + if (engaged_ && other.engaged_) \ + value_ = std::move(other.value_); \ + else \ + { \ + if (other.engaged_) \ + construct(std::move(other.value_)); \ + else \ + reset(); \ + } \ + return *this; \ + } \ +\ + void reset() \ + { \ + if (engaged_) \ + { \ + engaged_ = false; \ + value_.~stored_type(); \ + } \ + } \ +\ + protected: \ + template \ + void construct(Args&&... args) \ + { \ + ::new (std::addressof(value_)) stored_type(std::forward(args)...); \ + engaged_ = true; \ + } \ +\ + struct empty_byte \ + {}; \ + union \ + { \ + empty_byte empty_value_; \ + stored_type value_; \ + }; \ + bool engaged_ = false; + + template ::value> + class optional_base + { + INTERVAL_TREE_OPTIONAL_INTESTINES + + public: + ~optional_base() + { + if (engaged_) + value_.~stored_type(); + } + }; + + template + class optional_base + { + INTERVAL_TREE_OPTIONAL_INTESTINES + }; + + template + class optional : private optional_base + { + public: + using value_type = T; + + using optional_base::optional_base; + + constexpr optional() = default; + + template < + typename U = T, + std::enable_if_t< + !std::is_same, std::decay_t>::value && std::is_constructible::value && + std::is_convertible::value, + bool> = true> + constexpr optional(U&& value) + : optional_base{in_place, std::forward(value)} + {} + + template < + typename U = T, + std::enable_if_t< + !std::is_same, std::decay_t>::value && std::is_constructible::value && + !std::is_convertible::value, + bool> = false> + constexpr optional(U&& value) + : optional_base{in_place, std::forward(value)} + {} + + template < + typename U, + std::enable_if_t< + !std::is_same::value && std::is_constructible::value && + std::is_convertible::value, + bool> = true> + constexpr optional(const optional& other) + { + if (other) + emplace(*other); + } + + template < + typename U, + std::enable_if_t< + !std::is_same::value && std::is_constructible::value && + !std::is_convertible::value, + bool> = false> + explicit constexpr optional(const optional& other) + { + if (other) + emplace(*other); + } + + template < + typename U, + std::enable_if_t< + !std::is_same::value && std::is_constructible::value && + std::is_convertible::value, + bool> = true> + constexpr optional(optional&& other) + { + if (other) + emplace(std::move(*other)); + } + + template < + typename U, + std::enable_if_t< + !std::is_same::value && std::is_constructible::value && + !std::is_convertible::value, + bool> = false> + explicit constexpr optional(optional&& other) + { + if (other) + emplace(std::move(*other)); + } + + optional& operator=(nullopt_t) noexcept + { + this->reset(); + return *this; + } + + template < + typename U = T, + std::enable_if_t< + !std::is_same, std::decay_t>::value && std::is_constructible::value && + !(std::is_scalar::value && std::is_same>::value) && + std::is_assignable::value, + int> = 0> + optional& operator=(U&& value) + { + if (this->engaged_) + this->value_ = std::forward(value); + else + this->construct(std::forward(value)); + return *this; + } + + template + std::enable_if_t< + !std::is_same::value && std::is_constructible::value && + std::is_assignable::value, + optional&> + operator=(const optional& other) + { + if (other) + { + if (this->engaged_) + this->value_ = *other; + else + this->construct(*other); + } + else + { + this->reset(); + } + return *this; + } + + template + std::enable_if_t< + !std::is_same::value && std::is_constructible::value && std::is_assignable::value, + optional&> + operator=(optional&& other) + { + if (other) + { + if (this->engaged_) + this->value_ = std::move(*other); + else + this->construct(std::move(*other)); + } + else + { + this->reset(); + } + return *this; + } + + template + std::enable_if_t::value, void> emplace(Args&&... args) + { + this->reset(); + this->construct(std::forward(args)...); + } + + template + std::enable_if_t&, Args&&...>::value, void> + emplace(std::initializer_list il, Args&&... args) + { + this->reset(); + this->construct(il, std::forward(args)...); + } + + void swap(optional& other) noexcept( + std::is_nothrow_move_constructible::value && std::is_nothrow_swappable::value + ) + { + using std::swap; + + if (this->engaged_ && other.engaged_) + { + swap(this->value_, other.value_); + } + else if (this->engaged_) + { + other.construct(std::move(this->value_)); + this->reset(); + } + else if (other.engaged_) + { + this->construct(std::move(other.value_)); + other.reset(); + } + } + + constexpr const T& value() const& + { + if (this->engaged_) + return this->value_; + throw lib_interval_tree::bad_optional_access(); + } + + constexpr T& value() & + { + if (this->engaged_) + return this->value_; + throw lib_interval_tree::bad_optional_access(); + } + + constexpr T&& value() && + { + if (this->engaged_) + return std::move(this->value_); + throw lib_interval_tree::bad_optional_access(); + } + + constexpr const T&& value() const&& + { + if (this->engaged_) + return std::move(this->value_); + throw lib_interval_tree::bad_optional_access(); + } + + constexpr const T* operator->() const + { + return std::addressof(this->value_); + } + + T* operator->() + { + return std::addressof(this->value_); + } + + constexpr const T& operator*() const& + { + return this->value_; + } + + constexpr T& operator*() & + { + return this->value_; + } + + constexpr T&& operator*() && + { + return std::move(this->value_); + } + + constexpr const T&& operator*() const&& + { + return std::move(this->value_); + } + + constexpr explicit operator bool() const noexcept + { + return this->engaged_; + } + }; +} +#endif From 4456c3ee1d8f7f8b3c7e7ddbbe88ab7b27fedc3f Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Sat, 26 Jul 2025 22:18:55 +0200 Subject: [PATCH 02/11] Added bracket type to draw function. --- include/interval-tree/draw.hpp | 231 +++++++++++++++++---------------- 1 file changed, 116 insertions(+), 115 deletions(-) diff --git a/include/interval-tree/draw.hpp b/include/interval-tree/draw.hpp index 6665975..8b8c792 100644 --- a/include/interval-tree/draw.hpp +++ b/include/interval-tree/draw.hpp @@ -16,72 +16,73 @@ namespace lib_interval_tree { namespace { - template - struct NumericalPointerEquivalent - {}; - - template <> - struct NumericalPointerEquivalent - { - using type = uint32_t; - }; - - template <> - struct NumericalPointerEquivalent - { - using type = uint64_t; - }; - - template - std::string iterCaption(typename lib_interval_tree::interval_tree ::const_iterator iter) + template + struct NumericalPointerEquivalent + {}; + + template <> + struct NumericalPointerEquivalent + { + using type = uint32_t; + }; + + template <> + struct NumericalPointerEquivalent + { + using type = uint64_t; + }; + + template + std::string iterCaption(typename lib_interval_tree::interval_tree::const_iterator iter) { auto ival = *iter.node()->interval(); std::stringstream sstr; - sstr << '[' << ival.low() << ',' << ival.high() << ']'; + char leftBorder = [&ival]() { + if (ival.within(ival.low())) + return '['; + else + return '('; + }(); + char rightBorder = [&ival]() { + if (ival.within(ival.high())) + return ']'; + else + return ')'; + }(); + sstr << leftBorder << ival.low() << ',' << ival.high() << rightBorder; return sstr.str(); } std::string pointerString(void const* ptr) { std::stringstream sstr; - sstr << "0x" << std::hex << reinterpret_cast ::type> (ptr); + sstr << "0x" << std::hex << reinterpret_cast::type>(ptr); return sstr.str(); } - constexpr double margin = 5.; - constexpr double gridMargin = 5.; - constexpr double yPadding = 0.; - constexpr double xPadding = 30.; - constexpr double leftPadding = 10.; - constexpr double rightPadding = 10.; - constexpr double topPadding = 10.; - constexpr double bottomPadding = 10.; - constexpr Cairo::Pen blackPen = {3., Cairo::Colors::Black}; - constexpr Cairo::Pen iterCaptionPen = {3., Cairo::Colors::Black}; - constexpr Cairo::Pen ptrPen = {3., Cairo::Colors::Red}; - constexpr Cairo::Pen edgePen = {8., Cairo::Colors::Black}; - constexpr auto whitePen = Cairo::Colors::White; - - auto getiterBounds() + constexpr double margin = 5.; + constexpr double gridMargin = 5.; + constexpr double yPadding = 0.; + constexpr double xPadding = 30.; + constexpr double leftPadding = 10.; + constexpr double rightPadding = 10.; + constexpr double topPadding = 10.; + constexpr double bottomPadding = 10.; + constexpr Cairo::Pen blackPen = {3., Cairo::Colors::Black}; + constexpr Cairo::Pen iterCaptionPen = {3., Cairo::Colors::Black}; + constexpr Cairo::Pen ptrPen = {3., Cairo::Colors::Red}; + constexpr Cairo::Pen edgePen = {8., Cairo::Colors::Black}; + constexpr auto whitePen = Cairo::Colors::White; + + auto getiterBounds() { Cairo::Surface dummySurface(0, 0); Cairo::DrawContext dummyContext(&dummySurface); - auto const captionBoundProvider = Cairo::Text( - &dummyContext, - 0, - 0, - "[00,00]", - {"Arial", 18, CAIRO_FONT_WEIGHT_BOLD} - ); - - auto ptr = Cairo::Text( - &dummyContext, - 0, - 0, - pointerString(nullptr), - {"Arial", 10} - ); + auto const captionBoundProvider = + Cairo::Text(&dummyContext, 0, 0, "[00,00]", {"Arial", 18, CAIRO_FONT_WEIGHT_BOLD}); + + auto ptr = Cairo::Text(&dummyContext, 0, 0, pointerString(nullptr), {"Arial", 10}); auto ptrBounds = ptr.calculateBounds(ptrPen); auto bounds = captionBoundProvider.calculateBounds(iterCaptionPen); @@ -96,43 +97,42 @@ namespace lib_interval_tree return getiterBounds().getWidth() / 2. + margin * 2.; } } -//##################################################################################################################### + // ##################################################################################################################### template struct TreeGriditer { - typename lib_interval_tree::interval_tree ::const_iterator iter; - std::pair parentCoords; + typename lib_interval_tree::interval_tree::const_iterator iter; + std::pair parentCoords; }; template struct TreeGrid { // (row-major) - std::vector < // rows - std::vector < // columns - boost::optional > - > - > grid; + std::vector< // rows + std::vector< // columns + boost::optional>>> + grid; int xMax = 0; int xMin = 0; int yMax = 0; }; -//##################################################################################################################### + // ##################################################################################################################### template - void drawIterator(Cairo::DrawContext* ctx, typename lib_interval_tree::interval_tree ::const_iterator iter, double x, double y, bool drawPointers) + void drawIterator( + Cairo::DrawContext* ctx, + typename lib_interval_tree::interval_tree::const_iterator iter, + double x, + double y, + bool drawPointers + ) { - auto caption = Cairo::Text( - ctx, - 0, - 0, - iterCaption(iter), - {"Arial", 18, CAIRO_FONT_WEIGHT_BOLD} - ); + auto caption = Cairo::Text(ctx, 0, 0, iterCaption(iter), {"Arial", 18, CAIRO_FONT_WEIGHT_BOLD}); auto max = Cairo::Text( ctx, 0, 0, - //pointerString(iter), + // pointerString(iter), std::to_string(iter.max()), {"Arial", 12} ); @@ -151,7 +151,7 @@ namespace lib_interval_tree auto iterCaptionBounds = getiterBounds(); auto bounds = iterCaptionBounds; auto maxBounds = max.calculateBounds(ptrPen); - //auto ptrBounds = ptr.calculateBounds(ptrPen); + // auto ptrBounds = ptr.calculateBounds(ptrPen); iterCaptionBounds.setWidth(bounds.getWidth() - 30.); iterCaptionBounds.setHeight(bounds.getWidth() - maxBounds.getHeight() - 5.); @@ -166,29 +166,27 @@ namespace lib_interval_tree maxBounds = max.calculateBounds(ptrPen); } - Cairo::Arc circle { - ctx, - circleX, - circleY, - circleRadius - }; + Cairo::Arc circle{ctx, circleX, circleY, circleRadius}; switch (iter.node()->color()) { - case (rb_color::red): - circle.draw(blackPen, Cairo::Colors::Red); - break; - case (rb_color::black): - circle.draw(blackPen, Cairo::Colors::Black); - break; - case (rb_color::fail): - circle.draw(blackPen, Cairo::Colors::White); - break; - case (rb_color::double_black): - circle.draw(blackPen, Cairo::Colors::Gray); - break; + case (rb_color::red): + circle.draw(blackPen, Cairo::Colors::Red); + break; + case (rb_color::black): + circle.draw(blackPen, Cairo::Colors::Black); + break; + case (rb_color::fail): + circle.draw(blackPen, Cairo::Colors::White); + break; + case (rb_color::double_black): + circle.draw(blackPen, Cairo::Colors::Gray); + break; } - caption.move(circleX - actualCaptionBounds.getWidth() / 2., circleY - actualCaptionBounds.getHeight() / 2. - maxBounds.getHeight()); + caption.move( + circleX - actualCaptionBounds.getWidth() / 2., + circleY - actualCaptionBounds.getHeight() / 2. - maxBounds.getHeight() + ); if (iter.node()->color() != rb_color::black) caption.draw(iterCaptionPen); @@ -196,7 +194,8 @@ namespace lib_interval_tree caption.draw(Cairo::Colors::White); max.move(circleX - maxBounds.getWidth() / 2., circleY - maxBounds.getHeight() / 2. + 10.); - //ptr.move(circleX - ptrBounds.getWidth() / 2., circleY - ptrBounds.getHeight() / 2. + 10. + maxBounds.getHeight() + margin); + // ptr.move(circleX - ptrBounds.getWidth() / 2., circleY - ptrBounds.getHeight() / 2. + 10. + + // maxBounds.getHeight() + margin); if (iter.node()->color() != rb_color::red) max.draw(ptrPen); @@ -213,9 +212,9 @@ namespace lib_interval_tree } */ } -//--------------------------------------------------------------------------------------------------------------------- + //--------------------------------------------------------------------------------------------------------------------- template - TreeGrid createGrid(lib_interval_tree::interval_tree const& tree) + TreeGrid createGrid(lib_interval_tree::interval_tree const& tree) { auto root = tree.root(); if (root == std::end(tree)) @@ -223,7 +222,7 @@ namespace lib_interval_tree TreeGrid grid; - using tree_const_iterator = typename lib_interval_tree::interval_tree ::const_iterator; + using tree_const_iterator = typename lib_interval_tree::interval_tree::const_iterator; struct GridPoint { @@ -233,9 +232,9 @@ namespace lib_interval_tree int y; }; - std::vector gridPoints; + std::vector gridPoints; - std::function subtreeSize; + std::function subtreeSize; subtreeSize = [&](tree_const_iterator iter) { if (iter == std::end(tree)) return 0; @@ -250,9 +249,8 @@ namespace lib_interval_tree return 0; }; - std::function deduceCoordinates; - deduceCoordinates = [&](tree_const_iterator iter, int pX, int pY) - { + std::function deduceCoordinates; + deduceCoordinates = [&](tree_const_iterator iter, int pX, int pY) { int y = pY; int x = pX; if (!iter.node()->is_root()) @@ -298,7 +296,7 @@ namespace lib_interval_tree for (auto& i : gridPoints) { - std::pair parentCoords = {-1, -1}; + std::pair parentCoords = {-1, -1}; for (auto const& j : gridPoints) { if (j.iter == i.parent) @@ -313,15 +311,19 @@ namespace lib_interval_tree return grid; } -//--------------------------------------------------------------------------------------------------------------------- + //--------------------------------------------------------------------------------------------------------------------- template void drawGrid(Cairo::DrawContext* ctx, TreeGrid const& grid, bool drawPointers, bool drawEmpty) { auto iterRadius = getiterRadius(); auto cellSize = iterRadius * 2. + gridMargin; - auto iterX = [&](auto x_) {return leftPadding + iterRadius + x_ * cellSize + x_ * xPadding;}; - auto iterY = [&](auto y_) {return topPadding + iterRadius + y_ * cellSize + y_ * yPadding;}; + auto iterX = [&](auto x_) { + return leftPadding + iterRadius + x_ * cellSize + x_ * xPadding; + }; + auto iterY = [&](auto y_) { + return topPadding + iterRadius + y_ * cellSize + y_ * yPadding; + }; // Draw Lines int y = 0; @@ -332,7 +334,7 @@ namespace lib_interval_tree { if (cell && cell.get().parentCoords.first != -1) { - auto line = Cairo::Line { + auto line = Cairo::Line{ ctx, iterX(x), iterY(y), @@ -360,21 +362,15 @@ namespace lib_interval_tree } else if (drawEmpty) { - Cairo::Arc circle { - ctx, - iterX(x), - iterY(y), - iterRadius - }; + Cairo::Arc circle{ctx, iterX(x), iterY(y), iterRadius}; circle.draw(blackPen, Cairo::Colors::White); - } ++x; } ++y; } } -//--------------------------------------------------------------------------------------------------------------------- + //--------------------------------------------------------------------------------------------------------------------- template Cairo::Surface createSurface(TreeGrid const& grid) { @@ -384,13 +380,18 @@ namespace lib_interval_tree int height = grid.yMax + 1; return { - static_cast (leftPadding + (width) * cellSize + (width-1) * xPadding + rightPadding), - static_cast (topPadding + (height) * cellSize + (height-1) * yPadding + bottomPadding) + static_cast(leftPadding + (width)*cellSize + (width - 1) * xPadding + rightPadding), + static_cast(topPadding + (height)*cellSize + (height - 1) * yPadding + bottomPadding) }; } -//--------------------------------------------------------------------------------------------------------------------- + //--------------------------------------------------------------------------------------------------------------------- template - void drawTree(std::string const& fileName, lib_interval_tree::interval_tree const& tree, bool drawPointers = false, bool drawEmpty = false) + void drawTree( + std::string const& fileName, + lib_interval_tree::interval_tree const& tree, + bool drawPointers = false, + bool drawEmpty = false + ) { auto grid = createGrid(tree); auto surface = createSurface(grid); @@ -398,5 +399,5 @@ namespace lib_interval_tree drawGrid(&ctx, grid, drawPointers, drawEmpty); surface.saveToFile(fileName); } -//###################################################################################################### + // ###################################################################################################### } From b43d87f92ad7d8bb5b2ac9db720d4fe1e79e3147 Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Sat, 26 Jul 2025 22:19:29 +0200 Subject: [PATCH 03/11] Improved interval types --- include/interval-tree/interval_types.hpp | 199 +++++++++++++++++++++-- 1 file changed, 189 insertions(+), 10 deletions(-) diff --git a/include/interval-tree/interval_types.hpp b/include/interval-tree/interval_types.hpp index 055060d..80574fb 100644 --- a/include/interval-tree/interval_types.hpp +++ b/include/interval-tree/interval_types.hpp @@ -29,6 +29,18 @@ namespace lib_interval_tree { return high - low; } + + template + static inline numerical_type left_slice_upper_bound(numerical_type value) + { + return value; + } + + template + static inline numerical_type right_slice_lower_bound(numerical_type value) + { + return value; + } }; // [) struct right_open @@ -50,6 +62,18 @@ namespace lib_interval_tree { return high - low; } + + template + static inline numerical_type left_slice_upper_bound(numerical_type high) + { + return high; + } + + template + static inline numerical_type right_slice_lower_bound(numerical_type value) + { + return value; + } }; // [] struct closed @@ -67,7 +91,12 @@ namespace lib_interval_tree } template - static inline typename std::enable_if::value, numerical_type>::type +#ifdef LIB_INTERVAL_TREE_CONCEPTS + requires std::is_integral_v + static inline numerical_type +#else + static inline typename std::enable_if::value, numerical_type>::type +#endif size(numerical_type low, numerical_type high) { return high - low + 1; @@ -84,6 +113,68 @@ namespace lib_interval_tree { return high - low; } + + template +#ifdef LIB_INTERVAL_TREE_CONCEPTS + requires(std::is_signed_v && !std::is_floating_point_v) + static inline numerical_type +#else + static inline typename std::enable_if< + std::is_signed::value && !std::is_floating_point::value, + numerical_type>::type +#endif + left_slice_upper_bound(numerical_type value) + { + return value - 1; + } + + template +#ifdef LIB_INTERVAL_TREE_CONCEPTS + requires(std::is_floating_point_v) + static inline numerical_type +#else + static inline typename std::enable_if::value, numerical_type>::type +#endif + left_slice_upper_bound(numerical_type value) + { + return value; + } + + template +#ifdef LIB_INTERVAL_TREE_CONCEPTS + requires std::is_unsigned_v + static inline numerical_type +#else + static inline typename std::enable_if::value, numerical_type>::type +#endif + left_slice_upper_bound(numerical_type value) + { + return value > 0 ? value - 1 : 0; + } + + template +#ifdef LIB_INTERVAL_TREE_CONCEPTS + requires(!std::is_floating_point_v) + static inline numerical_type +#else + static inline typename std::enable_if::value, numerical_type>::type +#endif + right_slice_lower_bound(numerical_type value) + { + return value + 1; + } + + template +#ifdef LIB_INTERVAL_TREE_CONCEPTS + requires std::is_floating_point_v + static inline numerical_type +#else + static inline typename std::enable_if::value, numerical_type>::type +#endif + right_slice_lower_bound(numerical_type value) + { + return value; + } }; // () struct open @@ -101,7 +192,12 @@ namespace lib_interval_tree } template +#ifdef LIB_INTERVAL_TREE_CONCEPTS + requires(!std::is_floating_point_v) + static inline numerical_type +#else static inline typename std::enable_if::value, numerical_type>::type +#endif size(numerical_type low, numerical_type high) { return high - low - 1; @@ -118,6 +214,18 @@ namespace lib_interval_tree { return high - low; } + + template + static inline numerical_type left_slice_upper_bound(numerical_type high) + { + return high; + } + + template + static inline numerical_type right_slice_lower_bound(numerical_type high) + { + return high; + } }; /// [] and adjacent counts as overlapping struct closed_adjacent @@ -125,8 +233,11 @@ namespace lib_interval_tree template #ifdef LIB_INTERVAL_TREE_CONCEPTS requires std::is_integral_v + static inline bool +#else + static inline typename std::enable_if::value, bool>::type #endif - static inline bool within(numerical_type low, numerical_type high, numerical_type p) + within(numerical_type low, numerical_type high, numerical_type p) { return (low <= p) && (p <= high); } @@ -134,20 +245,68 @@ namespace lib_interval_tree template #ifdef LIB_INTERVAL_TREE_CONCEPTS requires std::is_integral_v + static inline bool +#else + static inline typename std::enable_if::value, bool>::type #endif - static inline bool overlaps(numerical_type l1, numerical_type h1, numerical_type l2, numerical_type h2) + overlaps(numerical_type l1, numerical_type h1, numerical_type l2, numerical_type h2) { - return (l1 <= (h2 + 1)) && ((l2 - 1) <= h1); + return (l1 <= (h2 + 1)) && (l2 <= (h1 + 1)); } template #ifdef LIB_INTERVAL_TREE_CONCEPTS requires std::is_integral_v + static inline numerical_type +#else + static inline typename std::enable_if::value, numerical_type>::type #endif - static inline numerical_type size(numerical_type low, numerical_type high) + size(numerical_type low, numerical_type high) { return high - low + 1; } + + template +#ifdef LIB_INTERVAL_TREE_CONCEPTS + requires std::is_floating_point_v + static inline numerical_type +#else + static inline typename std::enable_if::value, numerical_type>::type +#endif + size(numerical_type low, numerical_type high) + { + return high - low; + } + + template +#ifdef LIB_INTERVAL_TREE_CONCEPTS + requires std::is_signed_v + static inline numerical_type +#else + static inline typename std::enable_if::value, numerical_type>::type +#endif + left_slice_upper_bound(numerical_type value) + { + return value - 1; + } + + template +#ifdef LIB_INTERVAL_TREE_CONCEPTS + requires std::is_unsigned_v + static inline numerical_type +#else + static inline typename std::enable_if::value, numerical_type>::type +#endif + left_slice_upper_bound(numerical_type value) + { + return value > 0 ? value - 1 : 0; + } + + template + static inline numerical_type right_slice_lower_bound(numerical_type value) + { + return value + 1; + } }; enum class interval_border @@ -166,8 +325,11 @@ namespace lib_interval_tree template #ifdef LIB_INTERVAL_TREE_CONCEPTS requires std::is_integral_v + static inline bool +#else + static inline typename std::enable_if::value, bool>::type #endif - static inline bool within(interval_type const& ival, typename interval_type::value_type p) + within(interval_type const& ival, typename interval_type::value_type p) { switch (ival.left_border()) { @@ -217,8 +379,11 @@ namespace lib_interval_tree template #ifdef LIB_INTERVAL_TREE_CONCEPTS requires std::is_integral_v + static inline bool +#else + static inline typename std::enable_if::value, bool>::type #endif - static inline bool overlaps(interval_type const& ival1, interval_type const& ival2) + overlaps(interval_type const& ival1, interval_type const& ival2) { const auto lowToClosed = [](auto const& ival) { if (ival.left_border() == interval_border::open) @@ -263,8 +428,13 @@ namespace lib_interval_tree template #ifdef LIB_INTERVAL_TREE_CONCEPTS requires std::is_integral_v + static inline typename interval_type::value_type +#else + static inline typename std::enable_if< + std::is_integral::value, + typename interval_type::value_type>::type #endif - static typename interval_type::value_type distance(interval_type const& ival1, interval_type const& ival2) + distance(interval_type const& ival1, interval_type const& ival2) { using value_type = typename interval_type::value_type; @@ -303,8 +473,12 @@ namespace lib_interval_tree template #ifdef LIB_INTERVAL_TREE_CONCEPTS requires std::is_integral_v + static inline interval_type +#else + static inline + typename std::enable_if::value, interval_type>::type #endif - static interval_type join(interval_type const& ival1, interval_type const& ival2) + join(interval_type const& ival1, interval_type const& ival2) { typename interval_type::value_type low; typename interval_type::value_type high; @@ -391,8 +565,13 @@ namespace lib_interval_tree template #ifdef LIB_INTERVAL_TREE_CONCEPTS requires std::is_integral_v + static inline typename interval_type::value_type +#else + static inline typename std::enable_if< + std::is_integral::value, + typename interval_type::value_type>::type #endif - typename interval_type::value_type size(interval_type const& ival) + size(interval_type const& ival) { auto left = ival.left_border(); if (left == interval_border::closed_adjacent) From 234115e0fdb3fe4339397b064eb2837475e69ffd Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Sun, 27 Jul 2025 02:50:06 +0200 Subject: [PATCH 04/11] Reimplemented punch function properly. --- README.md | 85 ++- drawings/example_drawings.hpp | 116 +++- include/interval-tree/interval_tree.hpp | 234 ++++++++- tests/custom_interval_tests.hpp | 16 + tests/punch_tests.hpp | 671 ++++++++++++++++++++++++ tests/tests.cpp | 3 +- 6 files changed, 1086 insertions(+), 39 deletions(-) create mode 100644 tests/punch_tests.hpp diff --git a/README.md b/README.md index aeae98a..09a63ae 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,11 @@ Create a build folder, navigate there, run cmake and build the tree-tests target You might have to adapt the linker line for gtest, if you built it yourself and didn't install it into your system. If you want to generate the pretty drawings, install cairo, pull the submodule and pass INT_TREE_DRAW_EXAMPLES=on to the cmake command line to generate a drawings/make_drawings executeable. +Some features of this library require the presence of an optional type. +If you are using C++17 and up, it will be std::optional. +Otherwise you can specify INTERVAL_TREE_HAVE_BOOST_OPTIONAL to use boost::optional. +And if neither, a reduced version of optional is provided in the library, not perfectly exchangeable with std::optional, but sufficient for the library to work. + ## Draw Dot Graph This draws a dot graph of the tree: ```c++ @@ -137,37 +142,73 @@ Options are: ## Members of IntervalTree - - [Members of IntervalTree](#members-of-intervaltreeinterval) - - [iterator insert(interval_type const& ival)](#iterator-insertinterval_type-const-ival) - - [iterator insert_overlap(interval_type const& ival, bool, bool)](#iterator-insert_overlapinterval_type-const-ival-bool-bool) +- [interval-tree](#interval-tree) + - [How an interval tree looks like:](#how-an-interval-tree-looks-like) + - [Example](#example) + - [Compile \& Run Testing](#compile--run-testing) + - [Draw Dot Graph](#draw-dot-graph) + - [Free Functions](#free-functions) + - [interval\ make\_safe\_interval(NumericT border1, NumericT border2)](#intervalnumerict-kind-make_safe_intervalnumerict-border1-numerict-border2) + - [draw\_dot\_graph(std::ostream\& os, interval\_tree\_t const\& tree, DrawOptions const\& options)](#draw_dot_graphstdostream-os-interval_tree_t-const-tree-drawoptions-const-options) + - [Members of IntervalTree](#members-of-intervaltree) + - [iterator insert(interval\_type const\& ival)](#iterator-insertinterval_type-const-ival) + - [Parameters](#parameters) + - [iterator insert\_overlap(interval\_type const\& ival, bool, bool)](#iterator-insert_overlapinterval_type-const-ival-bool-bool) + - [Parameters](#parameters-1) - [iterator erase(iterator iter)](#iterator-eraseiterator-iter) - - [size_type size() const](#size_type-size-const) - - [(const)iterator find(interval_type const& ival)](#constiterator-findinterval_type-const-ival) - - [(const)iterator find(interval_type const& ival, CompareFunctionT const& compare)](#constiterator-findinterval_type-const-ival-comparefunctiont-const-compare) - - [(const)iterator find_all(interval_type const& ival, OnFindFunctionT const& on_find)](#constiterator-find_allinterval_type-const-ival-onfindfunctiont-const-on_find) + - [Parameters](#parameters-2) + - [size\_type size() const](#size_type-size-const) + - [(const)iterator find(interval\_type const\& ival)](#constiterator-findinterval_type-const-ival) + - [Parameters](#parameters-3) + - [(const)iterator find(interval\_type const\& ival, CompareFunctionT const\& compare)](#constiterator-findinterval_type-const-ival-comparefunctiont-const-compare) + - [Parameters](#parameters-4) + - [(const)iterator find\_all(interval\_type const\& ival, OnFindFunctionT const\& on\_find)](#constiterator-find_allinterval_type-const-ival-onfindfunctiont-const-on_find) + - [Parameters](#parameters-5) - [Example](#example-1) - - [(const)iterator find_all(interval_type const& ival, OnFindFunctionT const& on_find, CompareFunctionT const& compare)](#constiterator-find_allinterval_type-const-ival-onfindfunctiont-const-on_find-comparefunctiont-const-compare) - - [(const)iterator find_next_in_subtree(iterator from, interval_type const& ival)](#constiterator-find_next_in_subtreeiterator-from-interval_type-const-ival) - - [(const)iterator find_next_in_subtree(iterator from, interval_type const& ival, CompareFunctionT const& compare)](#constiterator-find_next_in_subtreeiterator-from-interval_type-const-ival-comparefunctiont-const-compare) - - [(const)iterator overlap_find(interval_type const& ival, bool exclusive)](#constiterator-overlap_findinterval_type-const-ival-bool-exclusive) - - [(const)iterator overlap_find_all(interval_type const& ival, OnFindFunctionT const& on_find, bool exclusive)](#constiterator-overlap_find_allinterval_type-const-ival-onfindfunctiont-const-on_find-bool-exclusive) + - [(const)iterator find\_all(interval\_type const\& ival, OnFindFunctionT const\& on\_find, CompareFunctionT const\& compare)](#constiterator-find_allinterval_type-const-ival-onfindfunctiont-const-on_find-comparefunctiont-const-compare) + - [Parameters](#parameters-6) + - [(const)iterator find\_next\_in\_subtree(iterator from, interval\_type const\& ival)](#constiterator-find_next_in_subtreeiterator-from-interval_type-const-ival) + - [Parameters](#parameters-7) + - [(const)iterator find\_next\_in\_subtree(iterator from, interval\_type const\& ival, CompareFunctionT const\& compare)](#constiterator-find_next_in_subtreeiterator-from-interval_type-const-ival-comparefunctiont-const-compare) + - [Parameters](#parameters-8) + - [(const)iterator overlap\_find(interval\_type const\& ival, bool exclusive)](#constiterator-overlap_findinterval_type-const-ival-bool-exclusive) + - [Parameters](#parameters-9) + - [(const)iterator overlap\_find\_all(interval\_type const\& ival, OnFindFunctionT const\& on\_find, bool exclusive)](#constiterator-overlap_find_allinterval_type-const-ival-onfindfunctiont-const-on_find-bool-exclusive) + - [Parameters](#parameters-10) - [Example](#example-2) - - [(const)iterator overlap_find_next_in_subtree(interval_type const& ival, bool exclusive)](#constiterator-overlap_find_next_in_subtreeinterval_type-const-ival-bool-exclusive) - - [interval_tree& deoverlap()](#interval_tree-deoverlap) + - [(const)iterator overlap\_find\_next\_in\_subtree(interval\_type const\& ival, bool exclusive)](#constiterator-overlap_find_next_in_subtreeinterval_type-const-ival-bool-exclusive) + - [Parameters](#parameters-11) + - [interval\_tree\& deoverlap()](#interval_tree-deoverlap) - [After deoverlap](#after-deoverlap) - - [interval_tree& deoverlap_copy()](#interval_tree-deoverlap_copy) - - [interval_tree punch(interval_type const& ival)](#interval_tree-punchinterval_type-const-ival) - - [After punching (with [0, 50])](#after-punching-with-0-50) - - [interval_tree punch()](#interval_tree-punch) + - [interval\_tree deoverlap\_copy()](#interval_tree-deoverlap_copy) + - [interval\_tree punch(interval\_type const\& ival)](#interval_tree-punchinterval_type-const-ival) + - [After punching (with \[0, 50\])](#after-punching-with-0-50) + - [interval\_tree punch()](#interval_tree-punch) - [bool empty() const noexcept](#bool-empty-const-noexcept) - [iterator begin()](#iterator-begin) - [iterator end()](#iterator-end) - [iterator cbegin()](#iterator-cbegin) - [iterator cend()](#iterator-cend) - - [reverse_iterator rbegin()](#reverse_iterator-rbegin) - - [reverse_iterator rend()](#reverse_iterator-rend) - - [reverse_iterator crbegin()](#reverse_iterator-crbegin) - - [reverse_iterator crend()](#reverse_iterator-crend) + - [reverse\_iterator rbegin()](#reverse_iterator-rbegin) + - [reverse\_iterator rend()](#reverse_iterator-rend) + - [reverse\_iterator crbegin()](#reverse_iterator-crbegin) + - [reverse\_iterator crend()](#reverse_iterator-crend) + - [Members of Interval](#members-of-interval) + - [using value\_type](#using-value_type) + - [using interval\_kind](#using-interval_kind) + - [friend bool operator==(interval const\& lhs, interval const\& other)](#friend-bool-operatorinterval-const-lhs-interval-const-other) + - [friend bool operator!=(interval const\& lhs, interval const\& other)](#friend-bool-operatorinterval-const-lhs-interval-const-other-1) + - [value\_type low() const](#value_type-low-const) + - [value\_type high() const](#value_type-high-const) + - [\[\[deprecated\]\] bool overlaps(value\_type l, value\_type h) const](#deprecated-bool-overlapsvalue_type-l-value_type-h-const) + - [bool overlaps\_exclusive(value\_type l, value\_type h) const](#bool-overlaps_exclusivevalue_type-l-value_type-h-const) + - [bool overlaps(interval const\& other) const](#bool-overlapsinterval-const-other-const) + - [bool overlaps\_exclusive(interval const\& other) const](#bool-overlaps_exclusiveinterval-const-other-const) + - [bool within(value\_type value) const](#bool-withinvalue_type-value-const) + - [bool within(interval const\& other) const](#bool-withininterval-const-other-const) + - [value\_type operator-(interval const\& other) const](#value_type-operator-interval-const-other-const) + - [value\_type size() const](#value_type-size-const) + - [interval join(interval const\& other) const](#interval-joininterval-const-other-const) ### iterator insert(interval_type const& ival) Adds an interval into the tree. diff --git a/drawings/example_drawings.hpp b/drawings/example_drawings.hpp index dd80602..00ff5af 100644 --- a/drawings/example_drawings.hpp +++ b/drawings/example_drawings.hpp @@ -2,7 +2,9 @@ #include +#include #include +#include static void drawDocExample() { @@ -31,7 +33,7 @@ static void drawFromTests1() interval_tree_t tree; - std::vector ::interval_type> intervalCollection; + std::vector::interval_type> intervalCollection; intervalCollection.push_back({-51, 11}); intervalCollection.push_back({26, 68}); @@ -67,8 +69,120 @@ static void drawFromTests1() drawTree("drawings/from_tests_1_deoverlapped.png", tree, false, false); } +static void drawLargeOverlapFree() +{ + using namespace lib_interval_tree; + + interval_tree_t tree; + + for (int i = 0; i < 30; ++i) + { + tree.insert({i * 10, i * 10 + 5}); + } + + drawTree("drawings/large_overlap_free.png", tree, false, false); + + std::vector> intervals; + for (int i = 0; i < 30; ++i) + { + intervals.emplace_back(i * 10, i * 10 + 5); + } + + // insert shuffled into new tree + interval_tree_t tree2; + std::mt19937 rng(std::random_device{}()); + std::shuffle(intervals.begin(), intervals.end(), rng); + for (const auto& interval : intervals) + { + tree2.insert({interval.first, interval.second}); + } + drawTree("drawings/large_overlap_free_shuffled.png", tree2, false, false); +} + +static void drawOpenPunchExample() +{ + constexpr int iterations = 5; + using namespace lib_interval_tree; + + interval_tree> tree; + + // insert shuffled into new tree + std::vector> intervals; + for (int i = 0; i < iterations; ++i) + { + intervals.emplace_back(i * 10, i * 10 + 5); + } + + std::mt19937 rng(std::random_device{}()); + std::shuffle(intervals.begin(), intervals.end(), rng); + for (const auto& interval : intervals) + { + tree.insert({interval.first, interval.second}); + } + + drawTree("drawings/open_punch_source.png", tree, false, false); + const auto punched = tree.punch({-10, iterations * 10 + 10}); + drawTree("drawings/open_punched.png", punched, false, false); +} + +static void drawClosedPunchExample() +{ + constexpr int iterations = 5; + using namespace lib_interval_tree; + + interval_tree> tree; + + // insert shuffled into new tree + std::vector> intervals; + for (int i = 0; i < iterations; ++i) + { + intervals.emplace_back(i * 10, i * 10 + 5); + } + + std::mt19937 rng(std::random_device{}()); + std::shuffle(intervals.begin(), intervals.end(), rng); + for (const auto& interval : intervals) + { + tree.insert({interval.first, interval.second}); + } + + drawTree("drawings/closed_punch_source.png", tree, false, false); + const auto punched = tree.punch({-10, iterations * 10 + 10}); + drawTree("drawings/closed_punched.png", punched, false, false); +} + +static void drawFloatPunchExample() +{ + constexpr int iterations = 5; + using namespace lib_interval_tree; + + interval_tree> tree; + + // insert shuffled into new tree + std::vector> intervals; + for (int i = 0; i < iterations; ++i) + { + intervals.emplace_back(i * 10.0f, i * 10.0f + 5.0f); + } + + std::mt19937 rng(std::random_device{}()); + std::shuffle(intervals.begin(), intervals.end(), rng); + for (const auto& interval : intervals) + { + tree.insert({interval.first, interval.second}); + } + + drawTree("drawings/float_punch_source.png", tree, false, false); + const auto punched = tree.punch({-10.0f, iterations * 10.0f + 10.0f}); + drawTree("drawings/float_punched.png", punched, false, false); +} + static void drawAll() { drawDocExample(); drawFromTests1(); + drawLargeOverlapFree(); + drawOpenPunchExample(); + drawClosedPunchExample(); + drawFloatPunchExample(); } diff --git a/include/interval-tree/interval_tree.hpp b/include/interval-tree/interval_tree.hpp index 950f488..f38751a 100644 --- a/include/interval-tree/interval_tree.hpp +++ b/include/interval-tree/interval_tree.hpp @@ -4,6 +4,7 @@ #include "interval_types.hpp" #include "tree_hooks.hpp" #include "feature_test.hpp" +#include "optional.hpp" #include #include @@ -21,6 +22,13 @@ namespace lib_interval_tree double_black }; // ############################################################################################################ + template + struct slice_type + { + optional left_slice{}; + optional right_slice{}; + }; + // ############################################################################################################ using default_interval_value_type = int; // ############################################################################################################ template @@ -201,6 +209,35 @@ namespace lib_interval_tree { return interval_kind::size(low_, high_); } + + /** + * @brief Extrudes other from this interval returning what is remaining. + * + * @param other + * @return slice_type + */ + slice_type extrude(interval const& other) const + { + slice_type slices{}; + if (low_ < other.low_) + { + auto slice = interval{low_, std::min(interval_kind::left_slice_upper_bound(other.low_), high_)}; + // >= comparison avoids overflows in case of unsigned integers + if (slice.high_ >= slice.low_ && slice.size() > 0) + slices.left_slice = std::move(slice); + } + // low_ == other.low_ does not produce a left slice in any case. + if (high_ > other.high_) + { + // FIXME: think: is the max violating edge conditions? + auto slice = interval{std::max(interval_kind::right_slice_lower_bound(other.high_), low_), high_}; + // >= comparison avoids overflows in case of unsigned integers + if (slice.high_ >= slice.low_ && slice.size() > 0) + slices.right_slice = std::move(slice); + } + // high_ == other.high_ does not produce a right slice in any case. + return slices; + } }; template @@ -323,6 +360,47 @@ namespace lib_interval_tree return right_border_; } + /** + * @brief Extrudes other from this interval returning what is remaining. + * + * @param other + * @return slice_type + */ + slice_type extrude(interval const& other) const + { + if (!overlaps(other)) + return {}; + + slice_type slices{}; + if (low_ < other.low_) + { + auto slice = interval{ + low_, + std::min(other.low_, high_), + left_border_, + other.left_border() == interval_border::open ? interval_border::closed : interval_border::open + }; + // >= comparison avoids overflows in case of unsigned integers + if (slice.high_ >= slice.low_ && slice.size() > 0) + slices.left_slice = std::move(slice); + } + // low_ == other.low_ does not produce a left slice in any case. + if (high_ > other.high_) + { + auto slice = interval{ + std::max(other.high_, low_), + high_, + right_border_ == interval_border::open ? interval_border::closed : interval_border::open, + other.right_border() + }; + // >= comparison avoids overflows in case of unsigned integers + if (slice.high_ >= slice.low_ && slice.size() > 0) + slices.right_slice = std::move(slice); + } + // high_ == other.high_ does not produce a right slice in any case. + return slices; + } + protected: interval_border left_border_; interval_border right_border_; @@ -1343,33 +1421,159 @@ namespace lib_interval_tree return punch({min, max}); } + // TODO: private /** - * Only works with deoverlapped trees. - * Removes all intervals from the given interval and produces a tree that contains the remaining intervals. - * This is basically the other punch overload with ival = [tree_lowest, tree_highest] + * @brief Finds the interval that is right of the given value and does not contain it. + * Only works with deoverlapped trees. + * + * @param low + * @return node_type* */ - interval_tree punch(interval_type const& ival) const + node_type* find_directly_right_of_i(value_type search_value) const { if (empty()) - return {}; + return nullptr; + + // There can be no interval strictly right of the value, if the value + // is larger than the max. + if (search_value > root_->max_) + return nullptr; + + const auto is_interval_strictly_right_of_value = [search_value](node_type* node) { + return node->low() > search_value || + (node->low() == search_value && !node->interval()->within(search_value)); + }; + + auto* node = root_; + + // If the interval is not strictly right of the value, we can only go down right + // And dont have to check left. + while (!is_interval_strictly_right_of_value(node) && node->right_) + node = node->right_; + + bool go_left = false; + bool go_right = false; + do + { + go_left = node->left_ && is_interval_strictly_right_of_value(node->left_); + go_right = node->right_ && is_interval_strictly_right_of_value(node->right_); + + if (go_left) + node = node->left_; + else if (go_right) + node = node->right_; + } while (go_left || go_right); + + if (is_interval_strictly_right_of_value(node)) + return node; + + // We only end up here when node == root_, otherwise we never went down the tree to begin with. + return nullptr; + } + /** + * @brief Find the interval that is left of the given value or contains it. + * Only works in deoverlapped trees. Because a deoverlapped tree is indistinguishable + * from a regular binary search tree. The tree is then also sorted by the upper interval bound. + * Making this search possible in the first place. + * + * @param search_value + * @return node_type* + */ + node_type* find_leftest_interval_of_value_i(value_type search_value) const + { + if (empty()) + return nullptr; + + auto* node = root_; + + // low of a node is always lower than the lows of all nodes right of that node + // high of a node is always lower than the lows of all nodes right of that node + + bool go_left = false; + bool go_right = false; + + do + { + go_right = search_value > node->high(); + if (go_right) + { + go_right &= node->right_ != nullptr; + if (go_right) + node = node->right_; + continue; + } + + go_left = node->left_ != nullptr && search_value < node->low(); + if (go_left) + node = node->left_; + } while (go_left || go_right); + + if (search_value < node->low()) + return nullptr; + + return node; + } + + /** + * Only works with deoverlapped trees. + * Removes all intervals from the given interval and produces a tree that contains the remaining intervals. + * This is basically the other punch overload with ival = [tree_lowest, tree_highest] + * + * @param ival The range in which to punch out the gaps as a new tree + */ + interval_tree punch(interval_type ival) const + { interval_tree result; - auto i = std::begin(*this); - if (ival.low() < i->interval()->low()) - result.insert({ival.low(), i->interval()->low()}); - for (auto e = end(); i != e; ++i) + if (empty()) + { + // Nothing to punch, so return the whole interval + result.insert(ival); + return result; + } + + auto* left_of_or_contains = find_leftest_interval_of_value_i(ival.low()); + + const_iterator iter{nullptr, this}; + if (left_of_or_contains == nullptr) { - auto next = i; - ++next; - if (next != e) - result.insert({i->interval()->high(), next->interval()->low()}); + iter = cbegin(); + // not adjacent or overlapping? + if (ival.high() < iter->low()) + { + result.insert(ival); + return result; + } + } + else + { + iter = const_iterator{left_of_or_contains, this}; + } + + slice_type ex; + bool insert_remaining = false; + for (; iter != cend(); ++iter) + { + ex = ival.extrude(*iter); + if (ex.left_slice) + result.insert(*ex.left_slice); + + if (ex.right_slice) + { + ival = std::move(*ex.right_slice); + // TODO: Can I avoid assigning this every loop? -> maybe by extracting the first iteration? + insert_remaining = true; + } else + { + insert_remaining = false; break; + } } - if (i != end() && i->interval()->high() < ival.high()) - result.insert({i->interval()->high(), ival.high()}); + if (insert_remaining && ival.size() > 0) + result.insert(ival); return result; } diff --git a/tests/custom_interval_tests.hpp b/tests/custom_interval_tests.hpp index 8c97518..ef6fe2b 100644 --- a/tests/custom_interval_tests.hpp +++ b/tests/custom_interval_tests.hpp @@ -51,6 +51,22 @@ struct custom_interval : public lib_interval_tree::interval diff --git a/tests/punch_tests.hpp b/tests/punch_tests.hpp new file mode 100644 index 0000000..d3c52f8 --- /dev/null +++ b/tests/punch_tests.hpp @@ -0,0 +1,671 @@ +#pragma once + +#include + +#include + +class PunchTests : public ::testing::Test +{ + public: + template + struct closed + { + using interval_type = lib_interval_tree::interval; + using tree_type = lib_interval_tree::interval_tree; + using iterator_type = typename tree_type::iterator; + }; + template + struct open + { + using interval_type = lib_interval_tree::interval; + using tree_type = lib_interval_tree::interval_tree; + using iterator_type = typename tree_type::iterator; + }; + template + struct left_open + { + using interval_type = lib_interval_tree::interval; + using tree_type = lib_interval_tree::interval_tree; + using iterator_type = typename tree_type::iterator; + }; + template + struct right_open + { + using interval_type = lib_interval_tree::interval; + using tree_type = lib_interval_tree::interval_tree; + using iterator_type = typename tree_type::iterator; + }; + template + struct closed_adjacent + { + using interval_type = lib_interval_tree::interval; + using tree_type = lib_interval_tree::interval_tree; + using iterator_type = typename tree_type::iterator; + }; + template + struct dynamic + { + using interval_type = lib_interval_tree::interval; + using tree_type = lib_interval_tree::interval_tree; + using iterator_type = typename tree_type::iterator; + }; + + template + auto i(NumericalT l, NumericalT h) const + { + return lib_interval_tree::interval{l, h}; + } + + template + auto + i(NumericalT l, + NumericalT h, + lib_interval_tree::interval_border l_border, + lib_interval_tree::interval_border r_border) const + { + return lib_interval_tree::interval{l, h, l_border, r_border}; + } +}; + +TEST_F(PunchTests, PunchEmptyTree) +{ + using types = closed; + + auto tree = types::tree_type{}; + auto result = tree.punch(types::interval_type{0, 5}); + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), 0); + EXPECT_EQ(result.begin()->high(), 5); +} + +TEST_F(PunchTests, PunchFullyRightOfTree) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{20, 25}); + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), 20); + EXPECT_EQ(result.begin()->high(), 25); +} + +TEST_F(PunchTests, PunchFullyLeftOfTree) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{-10, -5}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), -10); + EXPECT_EQ(result.begin()->high(), -5); +} + +TEST_F(PunchTests, PunchAjdacentLeftOfClosedTree) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{-10, 0}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), -10); + EXPECT_EQ(result.begin()->high(), -1); +} + +TEST_F(PunchTests, PunchAjdacentLeftOfOpenTree) +{ + using types = open; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{-10, 0}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), -10); + EXPECT_EQ(result.begin()->high(), 0); +} + +TEST_F(PunchTests, PunchAjdacentLeftOfLeftOpenTree) +{ + using types = left_open; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{-10, 0}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), -10); + EXPECT_EQ(result.begin()->high(), 0); +} + +TEST_F(PunchTests, PunchAjdacentLeftOfRightOpenTree) +{ + using types = left_open; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{-10, 0}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), -10); + EXPECT_EQ(result.begin()->high(), 0); +} + +TEST_F(PunchTests, PunchOverlapsLeftOfTreeLeftHanging) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{-10, 2}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), -10); + EXPECT_EQ(result.begin()->high(), -1); +} + +TEST_F(PunchTests, PunchOverlapsLeftOfTreeFullyExact) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{-10, 5}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), -10); + EXPECT_EQ(result.begin()->high(), -1); +} + +TEST_F(PunchTests, PunchOverlapsLeftOfTreeRightFullyOverNoGap) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{-10, 7}); + + ASSERT_EQ(result.size(), 1); + auto iter = result.begin(); + + EXPECT_EQ(iter->low(), -10); + EXPECT_EQ(iter->high(), -1); +} + +TEST_F(PunchTests, PunchOverlapsLeftOfTreeRightFullyOver) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{10, 15}); + auto result = tree.punch(types::interval_type{-10, 7}); + + ASSERT_EQ(result.size(), 2); + auto iter = result.begin(); + + EXPECT_EQ(iter->low(), -10); + EXPECT_EQ(iter->high(), -1); + + EXPECT_EQ((++iter)->low(), 6); + EXPECT_EQ(iter->high(), 7); +} + +TEST_F(PunchTests, PunchOverlapsLeftOfTreeFullyInsideInterval) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{1, 3}); + + EXPECT_TRUE(result.empty()); +} + +TEST_F(PunchTests, PunchOverlapsMiddleOfTreeFullyInsideInterval) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + tree.insert(types::interval_type{10, 15}); + auto result = tree.punch(types::interval_type{6, 9}); + + EXPECT_TRUE(result.empty()); +} + +TEST_F(PunchTests, PunchOverlapsRightOfTreeFullyInsideInterval) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{6, 9}); + + // Expect nothing + EXPECT_TRUE(result.empty()); +} + +TEST_F(PunchTests, ClosedPunchOverhangsLastIntervalOnRight) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{8, 15}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), 11); + EXPECT_EQ(result.begin()->high(), 15); +} + +TEST_F(PunchTests, OpenPunchOverhangsLastIntervalOnRight) +{ + using types = open; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{8, 12}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), 10); + EXPECT_EQ(result.begin()->high(), 12); +} + +TEST_F(PunchTests, LeftOpenPunchOverhangsLastIntervalOnRight) +{ + using types = left_open; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{8, 12}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), 10); + EXPECT_EQ(result.begin()->high(), 12); +} + +TEST_F(PunchTests, RightOpenPunchOverhangsLastIntervalOnRight) +{ + using types = right_open; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{8, 12}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), 10); + EXPECT_EQ(result.begin()->high(), 12); +} + +TEST_F(PunchTests, PunchSingleElementTreeEncompassing) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + auto result = tree.punch(types::interval_type{0, 5}); + + EXPECT_TRUE(result.empty()); +} + +TEST_F(PunchTests, PunchSingleElementTreeEncompassingWithRightOverlap) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + auto result = tree.punch(types::interval_type{0, 6}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), 6); + EXPECT_EQ(result.begin()->high(), 6); +} + +TEST_F(PunchTests, PunchSingleElementTreeEncompassingWithLeftOverlap) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + auto result = tree.punch(types::interval_type{-1, 5}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), -1); + EXPECT_EQ(result.begin()->high(), -1); +} + +TEST_F(PunchTests, DynamicPunchOverhangsLastIntervalOnRight) +{ + using namespace lib_interval_tree; + + using types = dynamic; + + auto tree_closed = types::tree_type{}; + tree_closed.insert(types::interval_type{0, 5, interval_border::closed, interval_border::closed}); + tree_closed.insert(types::interval_type{5, 10, interval_border::closed, interval_border::closed}); + auto result_closed = + tree_closed.punch(types::interval_type{8, 12, interval_border::closed, interval_border::closed}); + EXPECT_EQ(result_closed.size(), 1); + EXPECT_EQ(result_closed.begin()->low(), 10); + EXPECT_EQ(result_closed.begin()->left_border(), interval_border::open); + EXPECT_EQ(result_closed.begin()->high(), 12); + + auto tree_open = types::tree_type{}; + tree_open.insert(types::interval_type{0, 5, interval_border::open, interval_border::open}); + tree_open.insert(types::interval_type{5, 10, interval_border::open, interval_border::open}); + auto result_open = tree_open.punch(types::interval_type{8, 12, interval_border::open, interval_border::open}); + EXPECT_EQ(result_open.size(), 1); + EXPECT_EQ(result_open.begin()->low(), 10); + EXPECT_EQ(result_open.begin()->left_border(), interval_border::closed); + EXPECT_EQ(result_open.begin()->high(), 12); + + auto tree_left_open = types::tree_type{}; + tree_left_open.insert(types::interval_type{0, 5, interval_border::open, interval_border::closed}); + tree_left_open.insert(types::interval_type{5, 10, interval_border::open, interval_border::closed}); + auto result_left_open = + tree_left_open.punch(types::interval_type{8, 12, interval_border::open, interval_border::closed}); + EXPECT_EQ(result_left_open.size(), 1); + EXPECT_EQ(result_left_open.begin()->low(), 10); + EXPECT_EQ(result_left_open.begin()->left_border(), interval_border::open); + EXPECT_EQ(result_left_open.begin()->high(), 12); + + auto tree_right_open = types::tree_type{}; + tree_right_open.insert(types::interval_type{0, 5, interval_border::closed, interval_border::open}); + tree_right_open.insert(types::interval_type{5, 10, interval_border::closed, interval_border::open}); + auto result_right_open = + tree_right_open.punch(types::interval_type{8, 12, interval_border::closed, interval_border::open}); + EXPECT_EQ(result_right_open.size(), 1); + EXPECT_EQ(result_right_open.begin()->low(), 10); + EXPECT_EQ(result_right_open.begin()->left_border(), interval_border::closed); + EXPECT_EQ(result_right_open.begin()->high(), 12); + + auto tree_closed_adjacent = types::tree_type{}; + tree_closed_adjacent.insert( + types::interval_type{0, 5, interval_border::closed_adjacent, interval_border::closed_adjacent} + ); + tree_closed_adjacent.insert( + types::interval_type{5, 10, interval_border::closed_adjacent, interval_border::closed_adjacent} + ); + auto result_closed_adjacent = tree_closed_adjacent.punch( + types::interval_type{8, 12, interval_border::closed_adjacent, interval_border::closed_adjacent} + ); + EXPECT_EQ(result_closed_adjacent.size(), 1); + EXPECT_EQ(result_closed_adjacent.begin()->low(), 10); + EXPECT_EQ(result_closed_adjacent.begin()->left_border(), interval_border::open); + EXPECT_EQ(result_closed_adjacent.begin()->high(), 12); +} + +TEST_F(PunchTests, DynamicPunchOverhangsFirstIntervalOnLeft) +{ + using namespace lib_interval_tree; + + using types = dynamic; + + auto tree_closed = types::tree_type{}; + tree_closed.insert(types::interval_type{0, 5, interval_border::closed, interval_border::closed}); + tree_closed.insert(types::interval_type{5, 10, interval_border::closed, interval_border::closed}); + auto result_closed = + tree_closed.punch(types::interval_type{-10, 3, interval_border::closed, interval_border::closed}); + EXPECT_EQ(result_closed.size(), 1); + EXPECT_EQ(result_closed.begin()->low(), -10); + EXPECT_EQ(result_closed.begin()->right_border(), interval_border::open); + EXPECT_EQ(result_closed.begin()->high(), 0); + + auto tree_open = types::tree_type{}; + tree_open.insert(types::interval_type{0, 5, interval_border::open, interval_border::open}); + tree_open.insert(types::interval_type{5, 10, interval_border::open, interval_border::open}); + auto result_open = tree_open.punch(types::interval_type{-10, 3, interval_border::open, interval_border::open}); + EXPECT_EQ(result_open.size(), 1); + EXPECT_EQ(result_open.begin()->low(), -10); + EXPECT_EQ(result_open.begin()->right_border(), interval_border::closed); + EXPECT_EQ(result_open.begin()->high(), 0); + + auto tree_left_open = types::tree_type{}; + tree_left_open.insert(types::interval_type{0, 5, interval_border::open, interval_border::closed}); + tree_left_open.insert(types::interval_type{5, 10, interval_border::open, interval_border::closed}); + auto result_left_open = + tree_left_open.punch(types::interval_type{-10, 3, interval_border::open, interval_border::closed}); + EXPECT_EQ(result_left_open.size(), 1); + EXPECT_EQ(result_left_open.begin()->low(), -10); + EXPECT_EQ(result_left_open.begin()->right_border(), interval_border::closed); + EXPECT_EQ(result_left_open.begin()->high(), 0); + + auto tree_right_open = types::tree_type{}; + tree_right_open.insert(types::interval_type{0, 5, interval_border::closed, interval_border::open}); + tree_right_open.insert(types::interval_type{5, 10, interval_border::closed, interval_border::open}); + auto result_right_open = + tree_right_open.punch(types::interval_type{-10, 3, interval_border::closed, interval_border::open}); + EXPECT_EQ(result_right_open.size(), 1); + EXPECT_EQ(result_right_open.begin()->low(), -10); + EXPECT_EQ(result_right_open.begin()->right_border(), interval_border::open); + EXPECT_EQ(result_right_open.begin()->high(), 0); + + auto tree_closed_adjacent = types::tree_type{}; + tree_closed_adjacent.insert( + types::interval_type{0, 5, interval_border::closed_adjacent, interval_border::closed_adjacent} + ); + tree_closed_adjacent.insert( + types::interval_type{5, 10, interval_border::closed_adjacent, interval_border::closed_adjacent} + ); + auto result_closed_adjacent = tree_closed_adjacent.punch( + types::interval_type{-10, 3, interval_border::closed_adjacent, interval_border::closed_adjacent} + ); + EXPECT_EQ(result_closed_adjacent.size(), 1); + EXPECT_EQ(result_closed_adjacent.begin()->low(), -10); + EXPECT_EQ(result_closed_adjacent.begin()->right_border(), interval_border::open); + EXPECT_EQ(result_closed_adjacent.begin()->high(), 0); +} + +TEST_F(PunchTests, PunchEncompassesTree) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{10, 15}); + tree.insert(types::interval_type{20, 25}); + tree.insert(types::interval_type{30, 35}); + auto result = tree.punch(types::interval_type{0, 35}); + + ASSERT_EQ(result.size(), 3); + auto iter = result.begin(); + EXPECT_EQ(iter->low(), 6); + EXPECT_EQ(iter->high(), 9); + + EXPECT_EQ((++iter)->low(), 16); + EXPECT_EQ(iter->high(), 19); + + EXPECT_EQ((++iter)->low(), 26); + EXPECT_EQ(iter->high(), 29); +} + +TEST_F(PunchTests, PunchEncompassesOpenTree) +{ + using types = open; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{10, 15}); + tree.insert(types::interval_type{20, 25}); + tree.insert(types::interval_type{30, 35}); + auto result = tree.punch(types::interval_type{0, 35}); + + ASSERT_EQ(result.size(), 3); + auto iter = result.begin(); + EXPECT_EQ(iter->low(), 5); + EXPECT_EQ(iter->high(), 10); + EXPECT_EQ((++iter)->low(), 15); + EXPECT_EQ(iter->high(), 20); + EXPECT_EQ((++iter)->low(), 25); + EXPECT_EQ(iter->high(), 30); +} + +TEST_F(PunchTests, PunchEncompassesTreeWithOverlap) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{10, 15}); + tree.insert(types::interval_type{20, 25}); + tree.insert(types::interval_type{30, 35}); + auto result = tree.punch(types::interval_type{-5, 40}); + + ASSERT_EQ(result.size(), 5); + auto iter = result.begin(); + EXPECT_EQ(iter->low(), -5); + EXPECT_EQ(iter->high(), -1); + + EXPECT_EQ((++iter)->low(), 6); + EXPECT_EQ(iter->high(), 9); + + EXPECT_EQ((++iter)->low(), 16); + EXPECT_EQ(iter->high(), 19); + + EXPECT_EQ((++iter)->low(), 26); + EXPECT_EQ(iter->high(), 29); + + EXPECT_EQ((++iter)->low(), 36); + EXPECT_EQ(iter->high(), 40); +} + +TEST_F(PunchTests, PunchEncompassesOpenTreeWithOverlap) +{ + using types = open; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{10, 15}); + tree.insert(types::interval_type{20, 25}); + tree.insert(types::interval_type{30, 35}); + auto result = tree.punch(types::interval_type{-5, 40}); + + ASSERT_EQ(result.size(), 5); + auto iter = result.begin(); + EXPECT_EQ(iter->low(), -5); + EXPECT_EQ(iter->high(), 0); + + EXPECT_EQ((++iter)->low(), 5); + EXPECT_EQ(iter->high(), 10); + + EXPECT_EQ((++iter)->low(), 15); + EXPECT_EQ(iter->high(), 20); + + EXPECT_EQ((++iter)->low(), 25); + EXPECT_EQ(iter->high(), 30); + + EXPECT_EQ((++iter)->low(), 35); + EXPECT_EQ(iter->high(), 40); +} + +TEST_F(PunchTests, UnsignedPunchHasNoProblemWithZero) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + + auto result = tree.punch(types::interval_type{0, 7}); + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), 6); + EXPECT_EQ(result.begin()->high(), 7); + + auto tree2 = types::tree_type{}; + tree2.insert(types::interval_type{1, 5}); + + auto result2 = tree2.punch(types::interval_type{0, 7}); + + ASSERT_EQ(result2.size(), 2); + auto iter = result2.begin(); + EXPECT_EQ(iter->low(), 0); + EXPECT_EQ(iter->high(), 0); + EXPECT_EQ((++iter)->low(), 6); + EXPECT_EQ(iter->high(), 7); +} + +TEST_F(PunchTests, PunchDoesNotInsertIntervalWhenClosedGapIsEmpty) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{6, 10}); + + auto result = tree.punch(types::interval_type{0, 12}); + ASSERT_EQ(result.size(), 1); + + EXPECT_EQ(result.begin()->low(), 11); + EXPECT_EQ(result.begin()->high(), 12); +} + +TEST_F(PunchTests, PunchDoesNotInsertIntervalWhenOpenGapIsEmpty) +{ + using types = open; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{6, 10}); + + auto result = tree.punch(types::interval_type{0, 12}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), 10); + EXPECT_EQ(result.begin()->high(), 12); +} + +TEST_F(PunchTests, OpenFloatGap) +{ + using types = open; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0.0f, 5.0f}); + tree.insert(types::interval_type{6.0f, 10.0f}); + + auto result = tree.punch(types::interval_type{0.0f, 12.0f}); + ASSERT_EQ(result.size(), 2); + + auto iter = result.begin(); + EXPECT_FLOAT_EQ(iter->low(), 5.0f); + EXPECT_FLOAT_EQ(iter->high(), 6.0f); + EXPECT_FLOAT_EQ((++iter)->low(), 10.0f); + EXPECT_FLOAT_EQ(iter->high(), 12.0f); +} + +TEST_F(PunchTests, ClosedFloatGap) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0.0f, 5.0f}); + tree.insert(types::interval_type{6.0f, 10.0f}); + + auto result = tree.punch(types::interval_type{0.0f, 12.0f}); + ASSERT_EQ(result.size(), 2); + + auto iter = result.begin(); + EXPECT_NEAR(iter->low(), 5.0f, 0.001f); + EXPECT_NEAR(iter->high(), 6.0f, 0.001f); + EXPECT_NEAR((++iter)->low(), 10.0f, 0.001f); + EXPECT_FLOAT_EQ(iter->high(), 12.0f); +} \ No newline at end of file diff --git a/tests/tests.cpp b/tests/tests.cpp index 93a925c..b9f11d2 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -5,7 +5,6 @@ #include "typedefs.hpp" // following headers expect to be included after gtest headers and interval_tree -#include "interval_tests.hpp" #include "interval_tree_tests.hpp" #include "insert_tests.hpp" #include "erase_tests.hpp" @@ -16,6 +15,8 @@ #include "hook_tests.hpp" #include "custom_interval_tests.hpp" #include "dot_draw_tests.hpp" +#include "punch_tests.hpp" +#include "interval_tests.hpp" int main(int argc, char** argv) { From c15b88a39eb44d96ebe7e3d59c52d8907a8d5e3e Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Sat, 26 Jul 2025 22:30:43 +0200 Subject: [PATCH 05/11] Addressed some unsigned overflow concerns. --- include/interval-tree/interval_types.hpp | 65 +++++++++++++++++++++--- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/include/interval-tree/interval_types.hpp b/include/interval-tree/interval_types.hpp index 80574fb..5cb30d7 100644 --- a/include/interval-tree/interval_types.hpp +++ b/include/interval-tree/interval_types.hpp @@ -193,16 +193,35 @@ namespace lib_interval_tree template #ifdef LIB_INTERVAL_TREE_CONCEPTS - requires(!std::is_floating_point_v) + requires(!std::is_floating_point_v && std::is_signed_v) static inline numerical_type #else - static inline typename std::enable_if::value, numerical_type>::type + static inline typename std::enable_if< + !std::is_floating_point::value && std::is_signed::value, + numerical_type>::type #endif size(numerical_type low, numerical_type high) { return high - low - 1; } + template +#ifdef LIB_INTERVAL_TREE_CONCEPTS + requires(!std::is_floating_point_v && std::is_unsigned_v) + static inline numerical_type +#else + static inline typename std::enable_if< + !std::is_floating_point::value && std::is_unsigned::value, + numerical_type>::type +#endif + size(numerical_type low, numerical_type high) + { + if (high > low) + return high - low - 1; + else + return 0; + } + template #ifdef LIB_INTERVAL_TREE_CONCEPTS requires std::is_floating_point_v @@ -393,7 +412,13 @@ namespace lib_interval_tree const auto highToClosed = [](auto const& ival) { if (ival.right_border() == interval_border::open) + { + INTERVAL_TREE_CONSTEXPR_IF(std::is_unsigned::value) + { + return ival.high() > 0 ? ival.high() - 1 : 0; + } return ival.high() - 1; + } return ival.high(); }; @@ -442,11 +467,33 @@ namespace lib_interval_tree return 0; value_type adjusted_low = ival1.left_border() == interval_border::open ? ival1.low() + 1 : ival1.low(); - value_type adjusted_high = ival1.right_border() == interval_border::open ? ival1.high() - 1 : ival1.high(); + + value_type adjusted_high = [&]() { + INTERVAL_TREE_CONSTEXPR_IF(std::is_unsigned::value) + { + return ival1.right_border() == interval_border::open ? (ival1.high() > 0 ? ival1.high() - 1 : 0) + : ival1.high(); + } + else + { + return ival1.right_border() == interval_border::open ? ival1.high() - 1 : ival1.high(); + } + }(); + value_type other_adjusted_low = ival2.left_border() == interval_border::open ? ival2.low() + 1 : ival2.low(); - value_type other_adjusted_high = - ival2.right_border() == interval_border::open ? ival2.high() - 1 : ival2.high(); + + value_type other_adjusted_high = [&]() { + INTERVAL_TREE_CONSTEXPR_IF(std::is_unsigned::value) + { + return ival2.right_border() == interval_border::open ? (ival2.high() > 0 ? ival2.high() - 1 : 0) + : ival2.high(); + } + else + { + return ival2.right_border() == interval_border::open ? ival2.high() - 1 : ival2.high(); + } + }(); if (adjusted_high < other_adjusted_low) return other_adjusted_low - adjusted_high; @@ -540,7 +587,13 @@ namespace lib_interval_tree ? &ival1 : &ival2; - const auto openAdjusted = rightOpenInterval->high() - 1; + const auto openAdjusted = [&]() { + INTERVAL_TREE_CONSTEXPR_IF(std::is_unsigned::value) + { + return rightOpenInterval->high() > 0 ? rightOpenInterval->high() - 1 : 0; + } + return rightOpenInterval->high() - 1; + }(); if (openAdjusted == rightClosedInterval->high()) { From 11ec88a5466b841e0d5fec4a8fe69f09f2106726 Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Sat, 26 Jul 2025 22:43:40 +0200 Subject: [PATCH 06/11] Updated punch description. --- README.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 09a63ae..21da6a3 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,8 @@ Options are: - [After deoverlap](#after-deoverlap) - [interval\_tree deoverlap\_copy()](#interval_tree-deoverlap_copy) - [interval\_tree punch(interval\_type const\& ival)](#interval_tree-punchinterval_type-const-ival) - - [After punching (with \[0, 50\])](#after-punching-with-0-50) + - [Before punching (closed intervals)](#before-punching-closed-intervals) + - [After punching (with \[-10, 60\])](#after-punching-with--10-60) - [interval\_tree punch()](#interval_tree-punch) - [bool empty() const noexcept](#bool-empty-const-noexcept) - [iterator begin()](#iterator-begin) @@ -367,13 +368,19 @@ Same as deoverlap, but not inplace --- ### interval_tree punch(interval_type const& ival) -Removes all intervals from `ival` and produces a tree that contains the remaining intervals. -**The tree must be deoverlapped, or the result is undefined.** -`ival` is expected to encompass the entire interval range. +Cuts the intervals of the tree out of the given interval. Like a cookie cutter cuts out of dough. +This will return a new interval_tree containing the gaps between the intervals in the tree and the given interval. +Closed (and closed adjacent) intervals are treated as exclusive on the borders. [0,5][6,10] will not produce another interval between 5 and 6 as they are considered within the intervals and nothing fits inbetween. +Open intervals will not behave like this, so (0,5)(6,10) will produce a new interval (5,6). + +**IMPORTANT! The tree must be deoverlapped, or the result is undefined.** +`ival` can be any subrange of the tree, including encompassing the whole tree. **Returns**: A new interval_tree containing the gaps. -### After punching (with [0, 50]) -![AfterPunch](https://cloud.githubusercontent.com/assets/6238896/24613645/2dbf72e8-1889-11e7-813f-6d16fe0ad327.png) +### Before punching (closed intervals) +![BeforePunch](https://private-user-images.githubusercontent.com/6238896/471147224-5c631e00-dea4-4b75-a3bf-6fdd8ec1440b.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NTM1NjI1MzQsIm5iZiI6MTc1MzU2MjIzNCwicGF0aCI6Ii82MjM4ODk2LzQ3MTE0NzIyNC01YzYzMWUwMC1kZWE0LTRiNzUtYTNiZi02ZmRkOGVjMTQ0MGIucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDcyNiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTA3MjZUMjAzNzE0WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZDQ0NWIwMTcwMjZhNDA1YmUwNGI1YTIzNTBhZTQ5OTNhMWFiOTU5ZmU0N2E3NDI0NTQ0MzYwODA4N2E2MGFiZiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.P5zLeXg0-9bd20Thj6pfq_WxriMn4GC_lDSLzzGKMbw) +### After punching (with [-10, 60]) +![AfterPunch](https://private-user-images.githubusercontent.com/6238896/471147227-5c226d1d-d544-4a43-89a4-b3545145107d.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NTM1NjI1MzQsIm5iZiI6MTc1MzU2MjIzNCwicGF0aCI6Ii82MjM4ODk2LzQ3MTE0NzIyNy01YzIyNmQxZC1kNTQ0LTRhNDMtODlhNC1iMzU0NTE0NTEwN2QucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDcyNiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTA3MjZUMjAzNzE0WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9NmE2ZDUzMjU2ZTNjZWQ0Y2QzYjQ3ZGUyYjgyNWM2NDViYTAxMTdlY2RjYmQyMzg4OWFmZDlhMWU5YjY4NjlmZCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.Infe9i281LDOEC5GeBFuLHVE6Xjqw7KvcUo-gv3hjpk) --- ### interval_tree punch() From 846c9a0d975dfc6a83aa236e39bfd9d074db4c3c Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Sun, 27 Jul 2025 00:36:30 +0200 Subject: [PATCH 07/11] Cleaned up punch helper functions. --- include/interval-tree/interval_tree.hpp | 139 ++++++++---------------- 1 file changed, 44 insertions(+), 95 deletions(-) diff --git a/include/interval-tree/interval_tree.hpp b/include/interval-tree/interval_tree.hpp index f38751a..591e453 100644 --- a/include/interval-tree/interval_tree.hpp +++ b/include/interval-tree/interval_tree.hpp @@ -1421,100 +1421,6 @@ namespace lib_interval_tree return punch({min, max}); } - // TODO: private - /** - * @brief Finds the interval that is right of the given value and does not contain it. - * Only works with deoverlapped trees. - * - * @param low - * @return node_type* - */ - node_type* find_directly_right_of_i(value_type search_value) const - { - if (empty()) - return nullptr; - - // There can be no interval strictly right of the value, if the value - // is larger than the max. - if (search_value > root_->max_) - return nullptr; - - const auto is_interval_strictly_right_of_value = [search_value](node_type* node) { - return node->low() > search_value || - (node->low() == search_value && !node->interval()->within(search_value)); - }; - - auto* node = root_; - - // If the interval is not strictly right of the value, we can only go down right - // And dont have to check left. - while (!is_interval_strictly_right_of_value(node) && node->right_) - node = node->right_; - - bool go_left = false; - bool go_right = false; - do - { - go_left = node->left_ && is_interval_strictly_right_of_value(node->left_); - go_right = node->right_ && is_interval_strictly_right_of_value(node->right_); - - if (go_left) - node = node->left_; - else if (go_right) - node = node->right_; - } while (go_left || go_right); - - if (is_interval_strictly_right_of_value(node)) - return node; - - // We only end up here when node == root_, otherwise we never went down the tree to begin with. - return nullptr; - } - - /** - * @brief Find the interval that is left of the given value or contains it. - * Only works in deoverlapped trees. Because a deoverlapped tree is indistinguishable - * from a regular binary search tree. The tree is then also sorted by the upper interval bound. - * Making this search possible in the first place. - * - * @param search_value - * @return node_type* - */ - node_type* find_leftest_interval_of_value_i(value_type search_value) const - { - if (empty()) - return nullptr; - - auto* node = root_; - - // low of a node is always lower than the lows of all nodes right of that node - // high of a node is always lower than the lows of all nodes right of that node - - bool go_left = false; - bool go_right = false; - - do - { - go_right = search_value > node->high(); - if (go_right) - { - go_right &= node->right_ != nullptr; - if (go_right) - node = node->right_; - continue; - } - - go_left = node->left_ != nullptr && search_value < node->low(); - if (go_left) - node = node->left_; - } while (go_left || go_right); - - if (search_value < node->low()) - return nullptr; - - return node; - } - /** * Only works with deoverlapped trees. * Removes all intervals from the given interval and produces a tree that contains the remaining intervals. @@ -1562,7 +1468,6 @@ namespace lib_interval_tree if (ex.right_slice) { ival = std::move(*ex.right_slice); - // TODO: Can I avoid assigning this every loop? -> maybe by extracting the first iteration? insert_remaining = true; } else @@ -1671,6 +1576,50 @@ namespace lib_interval_tree } private: + /** + * @brief Find the interval that is left of the given value or contains it. + * Only works in deoverlapped trees. Because a deoverlapped tree is indistinguishable + * from a regular binary search tree. The tree is then also sorted by the upper interval bound. + * Making this search possible in the first place. + * + * @param search_value + * @return node_type* + */ + node_type* find_leftest_interval_of_value_i(value_type search_value) const + { + if (empty()) + return nullptr; + + auto* node = root_; + + // low of a node is always lower than the lows of all nodes right of that node + // high of a node is always lower than the lows of all nodes right of that node + + bool go_left = false; + bool go_right = false; + + do + { + go_right = search_value > node->high(); + if (go_right) + { + go_right &= node->right_ != nullptr; + if (go_right) + node = node->right_; + continue; + } + + go_left = node->left_ != nullptr && search_value < node->low(); + if (go_left) + node = node->left_; + } while (go_left || go_right); + + if (search_value < node->low()) + return nullptr; + + return node; + } + node_type* copy_tree_impl(node_type* root, node_type* parent) { if (root) From 7af4e38a775ea85c8e9256ec32c97a25627cf8e7 Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Sun, 27 Jul 2025 02:48:39 +0200 Subject: [PATCH 08/11] Renamed extrude to slice and added docs. --- README.md | 12 +++- include/interval-tree/interval_tree.hpp | 49 ++++++++++++++--- tests/interval_tests.hpp | 73 +++++++++++++++++++++++++ tests/punch_tests.hpp | 23 ++++++++ 4 files changed, 147 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 21da6a3..2cf1a01 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,7 @@ Options are: - [value\_type operator-(interval const\& other) const](#value_type-operator-interval-const-other-const) - [value\_type size() const](#value_type-size-const) - [interval join(interval const\& other) const](#interval-joininterval-const-other-const) + - [slice\_type slice(interval const\& other) const](#slice_type-sliceinterval-const-other-const) ### iterator insert(interval_type const& ival) Adds an interval into the tree. @@ -443,7 +444,7 @@ Returns a past the end const_iterator in reverse. **Returns**: past the end const_iterator. ## Members of Interval -___You can implement your own interval if you provide the same functions, except (operator-, size, operator!=).___ +___You can implement your own interval if you provide the same functions, except (slice, operator-, size, operator!=).___ There are 6 types of intervals: - open: (a, b) @@ -505,3 +506,12 @@ Overlapping intervals have 0 distance. Returns The amount of elements in the interval when integral, or the distance between the 2 bounds when floating point. ### interval join(interval const& other) const Joins 2 intervals and whatever is inbetween. +### slice_type slice(interval const& other) const +Removes other from this interval returning what is remaining. +The range of other going beyond the range of this is ignored. +Returns a struct with 2 members: left_slice and right_slice. +[ this interval ] +[left][other][right] + +When the intervals are closed, adjacent results are differenty by 1. +[0, 9].slice([5, 19]) => left: [0, 4], right: nullopt \ No newline at end of file diff --git a/include/interval-tree/interval_tree.hpp b/include/interval-tree/interval_tree.hpp index 591e453..05d4f7d 100644 --- a/include/interval-tree/interval_tree.hpp +++ b/include/interval-tree/interval_tree.hpp @@ -31,6 +31,30 @@ namespace lib_interval_tree // ############################################################################################################ using default_interval_value_type = int; // ############################################################################################################ + namespace detail + { + template + using void_t = void; + + template + struct has_slice_impl : std::false_type + {}; + + template + struct has_slice_impl< + interval_t, + void_t().slice(std::declval()))>> : std::true_type + {}; + +#if __cplusplus >= 202002L + template + concept has_slice = has_slice_impl::value; +#else + template + constexpr bool has_slice = has_slice_impl::value; +#endif + } + // ############################################################################################################ template struct interval_base { @@ -211,12 +235,14 @@ namespace lib_interval_tree } /** - * @brief Extrudes other from this interval returning what is remaining. + * @brief Removes other from this interval returning what is remaining. + * + * The range of other going beyond the range of this is ignored. * * @param other * @return slice_type */ - slice_type extrude(interval const& other) const + slice_type slice(interval const& other) const { slice_type slices{}; if (low_ < other.low_) @@ -361,12 +387,12 @@ namespace lib_interval_tree } /** - * @brief Extrudes other from this interval returning what is remaining. + * @brief Removes other from this interval returning what is remaining. * * @param other * @return slice_type */ - slice_type extrude(interval const& other) const + slice_type slice(interval const& other) const { if (!overlaps(other)) return {}; @@ -1416,9 +1442,7 @@ namespace lib_interval_tree { if (empty()) return {}; - auto min = std::begin(*this)->interval()->low(); - auto max = root_->max_; - return punch({min, max}); + return punch({begin()->low(), root_->max_}); } /** @@ -1428,7 +1452,14 @@ namespace lib_interval_tree * * @param ival The range in which to punch out the gaps as a new tree */ - interval_tree punch(interval_type ival) const + template +#ifdef LIB_INTERVAL_TREE_CONCEPTS + requires detail::has_slice + interval_tree +#else + typename std::enable_if, interval_tree>::type +#endif + punch(interval_type ival) const { interval_tree result; @@ -1461,7 +1492,7 @@ namespace lib_interval_tree bool insert_remaining = false; for (; iter != cend(); ++iter) { - ex = ival.extrude(*iter); + ex = ival.slice(*iter); if (ex.left_slice) result.insert(*ex.left_slice); diff --git a/tests/interval_tests.hpp b/tests/interval_tests.hpp index 0f4d3be..b659d53 100644 --- a/tests/interval_tests.hpp +++ b/tests/interval_tests.hpp @@ -899,4 +899,77 @@ TEST_F(IntervalTests, CanFindDynamicIntervalUsingComparisonFunction) EXPECT_EQ(iter->high(), 5); EXPECT_EQ(iter->left_border(), interval_border::closed); EXPECT_EQ(iter->right_border(), interval_border::closed); +} + +TEST_F(IntervalTests, ClosedSliceLeftOverlap) +{ + // [ this ] + // [ls][param] + const auto result = i(1, 8).slice(i(5, 100)); + ASSERT_TRUE(result.left_slice); + EXPECT_EQ(result.left_slice->low(), 1); + EXPECT_EQ(result.left_slice->high(), 4); + EXPECT_FALSE(result.right_slice); +} + +TEST_F(IntervalTests, OpenSliceLeftOverlap) +{ + // [ this ] + // [ls][param] + using lib_interval_tree::open; + const auto result = i(1, 8).slice(i(5, 10)); + ASSERT_TRUE(result.left_slice); + EXPECT_EQ(result.left_slice->low(), 1); + EXPECT_EQ(result.left_slice->high(), 5); + EXPECT_FALSE(result.right_slice); +} + +TEST_F(IntervalTests, ClosedRightRemains) +{ + // [ this ] + // [param][rs] + const auto result = i(8, 15).slice(i(8, 12)); + ASSERT_TRUE(result.right_slice); + EXPECT_EQ(result.right_slice->low(), 13); + EXPECT_EQ(result.right_slice->high(), 15); + EXPECT_FALSE(result.left_slice); +} + +TEST_F(IntervalTests, OpenRightRemains) +{ + // [ this ] + // [param][rs] + using lib_interval_tree::open; + const auto result = i(8, 15).slice(i(8, 12)); + ASSERT_TRUE(result.right_slice); + EXPECT_EQ(result.right_slice->low(), 12); + EXPECT_EQ(result.right_slice->high(), 15); + EXPECT_FALSE(result.left_slice); +} + +TEST_F(IntervalTests, ClosedMiddleExtrusion) +{ + // [ this ] + // [ls][param][rs] + const auto result = i(0, 10).slice(i(5, 8)); + ASSERT_TRUE(result.left_slice); + EXPECT_EQ(result.left_slice->low(), 0); + EXPECT_EQ(result.left_slice->high(), 4); + ASSERT_TRUE(result.right_slice); + EXPECT_EQ(result.right_slice->low(), 9); + EXPECT_EQ(result.right_slice->high(), 10); +} + +TEST_F(IntervalTests, OpenMiddleExtrusion) +{ + // [ this ] + // [ls][param][rs] + using lib_interval_tree::open; + const auto result = i(0, 10).slice(i(5, 8)); + ASSERT_TRUE(result.left_slice); + EXPECT_EQ(result.left_slice->low(), 0); + EXPECT_EQ(result.left_slice->high(), 5); + ASSERT_TRUE(result.right_slice); + EXPECT_EQ(result.right_slice->low(), 8); + EXPECT_EQ(result.right_slice->high(), 10); } \ No newline at end of file diff --git a/tests/punch_tests.hpp b/tests/punch_tests.hpp index d3c52f8..93dd476 100644 --- a/tests/punch_tests.hpp +++ b/tests/punch_tests.hpp @@ -668,4 +668,27 @@ TEST_F(PunchTests, ClosedFloatGap) EXPECT_NEAR(iter->high(), 6.0f, 0.001f); EXPECT_NEAR((++iter)->low(), 10.0f, 0.001f); EXPECT_FLOAT_EQ(iter->high(), 12.0f); +} + +TEST_F(PunchTests, PunchWithoutArgsEncompassesTree) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{10, 15}); + tree.insert(types::interval_type{20, 25}); + tree.insert(types::interval_type{30, 35}); + auto result = tree.punch(); + + ASSERT_EQ(result.size(), 3); + auto iter = result.begin(); + EXPECT_EQ(iter->low(), 6); + EXPECT_EQ(iter->high(), 9); + + EXPECT_EQ((++iter)->low(), 16); + EXPECT_EQ(iter->high(), 19); + + EXPECT_EQ((++iter)->low(), 26); + EXPECT_EQ(iter->high(), 29); } \ No newline at end of file From 46124fff7893e5c6d50171c182072bbcc7a5bcc2 Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Sun, 27 Jul 2025 01:49:31 +0200 Subject: [PATCH 09/11] Made recursive the default for insert_overlap. --- include/interval-tree/interval_tree.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/interval-tree/interval_tree.hpp b/include/interval-tree/interval_tree.hpp index 05d4f7d..2bb86cd 100644 --- a/include/interval-tree/interval_tree.hpp +++ b/include/interval-tree/interval_tree.hpp @@ -1128,7 +1128,7 @@ namespace lib_interval_tree * Be careful to not produce overlapping merge sets when doing recursive insertion, or it will recurse * endlessly. */ - iterator insert_overlap(interval_type const& ival, bool exclusive = false, bool recursive = false) + iterator insert_overlap(interval_type const& ival, bool exclusive = false, bool recursive = true) { auto iter = overlap_find(ival, exclusive); if (iter == end()) From c18955594b82ee9d21b26a64db1618721c94b1d8 Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Sun, 27 Jul 2025 02:42:07 +0200 Subject: [PATCH 10/11] Reworked how closed/closed_adjacent/open intervals punch out. --- README.md | 9 +- include/interval-tree/interval_tree.hpp | 1 - include/interval-tree/interval_types.hpp | 93 +++--- tests/interval_tests.hpp | 45 ++- tests/punch_tests.hpp | 374 +++++++++++++++++++++-- 5 files changed, 434 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index 2cf1a01..9acc9d7 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ Options are: - [After deoverlap](#after-deoverlap) - [interval\_tree deoverlap\_copy()](#interval_tree-deoverlap_copy) - [interval\_tree punch(interval\_type const\& ival)](#interval_tree-punchinterval_type-const-ival) - - [Before punching (closed intervals)](#before-punching-closed-intervals) + - [Before punching (closed\_adjacent intervals)](#before-punching-closed_adjacent-intervals) - [After punching (with \[-10, 60\])](#after-punching-with--10-60) - [interval\_tree punch()](#interval_tree-punch) - [bool empty() const noexcept](#bool-empty-const-noexcept) @@ -371,14 +371,15 @@ Same as deoverlap, but not inplace ### interval_tree punch(interval_type const& ival) Cuts the intervals of the tree out of the given interval. Like a cookie cutter cuts out of dough. This will return a new interval_tree containing the gaps between the intervals in the tree and the given interval. -Closed (and closed adjacent) intervals are treated as exclusive on the borders. [0,5][6,10] will not produce another interval between 5 and 6 as they are considered within the intervals and nothing fits inbetween. -Open intervals will not behave like this, so (0,5)(6,10) will produce a new interval (5,6). +Closed adjacent intervals are treated as exclusive on the borders. [0,5]a[6,10]a will not produce another interval between 5 and 6 as they are considered within the intervals and nothing fits inbetween. +Regular closed intervals will not behave like this, so [0,5][6,10] will produce a new interval [5,6]. +Open intervals with integral numbers will also not produce the gap (5, 6), because (5, 6) is empty for integers, not for floats. **IMPORTANT! The tree must be deoverlapped, or the result is undefined.** `ival` can be any subrange of the tree, including encompassing the whole tree. **Returns**: A new interval_tree containing the gaps. -### Before punching (closed intervals) +### Before punching (closed_adjacent intervals) ![BeforePunch](https://private-user-images.githubusercontent.com/6238896/471147224-5c631e00-dea4-4b75-a3bf-6fdd8ec1440b.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NTM1NjI1MzQsIm5iZiI6MTc1MzU2MjIzNCwicGF0aCI6Ii82MjM4ODk2LzQ3MTE0NzIyNC01YzYzMWUwMC1kZWE0LTRiNzUtYTNiZi02ZmRkOGVjMTQ0MGIucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDcyNiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTA3MjZUMjAzNzE0WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZDQ0NWIwMTcwMjZhNDA1YmUwNGI1YTIzNTBhZTQ5OTNhMWFiOTU5ZmU0N2E3NDI0NTQ0MzYwODA4N2E2MGFiZiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.P5zLeXg0-9bd20Thj6pfq_WxriMn4GC_lDSLzzGKMbw) ### After punching (with [-10, 60]) ![AfterPunch](https://private-user-images.githubusercontent.com/6238896/471147227-5c226d1d-d544-4a43-89a4-b3545145107d.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NTM1NjI1MzQsIm5iZiI6MTc1MzU2MjIzNCwicGF0aCI6Ii82MjM4ODk2LzQ3MTE0NzIyNy01YzIyNmQxZC1kNTQ0LTRhNDMtODlhNC1iMzU0NTE0NTEwN2QucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDcyNiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTA3MjZUMjAzNzE0WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9NmE2ZDUzMjU2ZTNjZWQ0Y2QzYjQ3ZGUyYjgyNWM2NDViYTAxMTdlY2RjYmQyMzg4OWFmZDlhMWU5YjY4NjlmZCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.Infe9i281LDOEC5GeBFuLHVE6Xjqw7KvcUo-gv3hjpk) diff --git a/include/interval-tree/interval_tree.hpp b/include/interval-tree/interval_tree.hpp index 2bb86cd..8b10117 100644 --- a/include/interval-tree/interval_tree.hpp +++ b/include/interval-tree/interval_tree.hpp @@ -255,7 +255,6 @@ namespace lib_interval_tree // low_ == other.low_ does not produce a left slice in any case. if (high_ > other.high_) { - // FIXME: think: is the max violating edge conditions? auto slice = interval{std::max(interval_kind::right_slice_lower_bound(other.high_), low_), high_}; // >= comparison avoids overflows in case of unsigned integers if (slice.high_ >= slice.low_ && slice.size() > 0) diff --git a/include/interval-tree/interval_types.hpp b/include/interval-tree/interval_types.hpp index 5cb30d7..10bbefa 100644 --- a/include/interval-tree/interval_types.hpp +++ b/include/interval-tree/interval_types.hpp @@ -115,63 +115,13 @@ namespace lib_interval_tree } template -#ifdef LIB_INTERVAL_TREE_CONCEPTS - requires(std::is_signed_v && !std::is_floating_point_v) - static inline numerical_type -#else - static inline typename std::enable_if< - std::is_signed::value && !std::is_floating_point::value, - numerical_type>::type -#endif - left_slice_upper_bound(numerical_type value) - { - return value - 1; - } - - template -#ifdef LIB_INTERVAL_TREE_CONCEPTS - requires(std::is_floating_point_v) - static inline numerical_type -#else - static inline typename std::enable_if::value, numerical_type>::type -#endif - left_slice_upper_bound(numerical_type value) - { - return value; - } - - template -#ifdef LIB_INTERVAL_TREE_CONCEPTS - requires std::is_unsigned_v - static inline numerical_type -#else - static inline typename std::enable_if::value, numerical_type>::type -#endif - left_slice_upper_bound(numerical_type value) - { - return value > 0 ? value - 1 : 0; - } - - template -#ifdef LIB_INTERVAL_TREE_CONCEPTS - requires(!std::is_floating_point_v) - static inline numerical_type -#else - static inline typename std::enable_if::value, numerical_type>::type -#endif - right_slice_lower_bound(numerical_type value) + static inline numerical_type left_slice_upper_bound(numerical_type high) { - return value + 1; + return high; } template -#ifdef LIB_INTERVAL_TREE_CONCEPTS - requires std::is_floating_point_v - static inline numerical_type -#else - static inline typename std::enable_if::value, numerical_type>::type -#endif - right_slice_lower_bound(numerical_type value) + static inline numerical_type right_slice_lower_bound(numerical_type value) { return value; } @@ -299,16 +249,30 @@ namespace lib_interval_tree template #ifdef LIB_INTERVAL_TREE_CONCEPTS - requires std::is_signed_v + requires(std::is_signed_v && !std::is_floating_point_v) static inline numerical_type #else - static inline typename std::enable_if::value, numerical_type>::type + static inline typename std::enable_if< + std::is_signed::value && !std::is_floating_point::value, + numerical_type>::type #endif left_slice_upper_bound(numerical_type value) { return value - 1; } + template +#ifdef LIB_INTERVAL_TREE_CONCEPTS + requires(std::is_floating_point_v) + static inline numerical_type +#else + static inline typename std::enable_if::value, numerical_type>::type +#endif + left_slice_upper_bound(numerical_type value) + { + return value; + } + template #ifdef LIB_INTERVAL_TREE_CONCEPTS requires std::is_unsigned_v @@ -322,10 +286,27 @@ namespace lib_interval_tree } template - static inline numerical_type right_slice_lower_bound(numerical_type value) +#ifdef LIB_INTERVAL_TREE_CONCEPTS + requires(!std::is_floating_point_v) + static inline numerical_type +#else + static inline typename std::enable_if::value, numerical_type>::type +#endif + right_slice_lower_bound(numerical_type value) { return value + 1; } + template +#ifdef LIB_INTERVAL_TREE_CONCEPTS + requires std::is_floating_point_v + static inline numerical_type +#else + static inline typename std::enable_if::value, numerical_type>::type +#endif + right_slice_lower_bound(numerical_type value) + { + return value; + } }; enum class interval_border diff --git a/tests/interval_tests.hpp b/tests/interval_tests.hpp index b659d53..f0c94aa 100644 --- a/tests/interval_tests.hpp +++ b/tests/interval_tests.hpp @@ -901,6 +901,17 @@ TEST_F(IntervalTests, CanFindDynamicIntervalUsingComparisonFunction) EXPECT_EQ(iter->right_border(), interval_border::closed); } +TEST_F(IntervalTests, ClosedAdjacentSliceLeftOverlap) +{ + // [ this ] + // [ls][param] + const auto result = i(1, 8).slice(i(5, 100)); + ASSERT_TRUE(result.left_slice); + EXPECT_EQ(result.left_slice->low(), 1); + EXPECT_EQ(result.left_slice->high(), 4); + EXPECT_FALSE(result.right_slice); +} + TEST_F(IntervalTests, ClosedSliceLeftOverlap) { // [ this ] @@ -908,7 +919,7 @@ TEST_F(IntervalTests, ClosedSliceLeftOverlap) const auto result = i(1, 8).slice(i(5, 100)); ASSERT_TRUE(result.left_slice); EXPECT_EQ(result.left_slice->low(), 1); - EXPECT_EQ(result.left_slice->high(), 4); + EXPECT_EQ(result.left_slice->high(), 5); EXPECT_FALSE(result.right_slice); } @@ -924,13 +935,25 @@ TEST_F(IntervalTests, OpenSliceLeftOverlap) EXPECT_FALSE(result.right_slice); } +TEST_F(IntervalTests, ClosedAdjacentRightRemains) +{ + // [ this ] + // [param][rs] + const auto result = i(8, 15).slice(i(8, 12)); + ASSERT_TRUE(result.right_slice); + EXPECT_EQ(result.right_slice->low(), 13); + EXPECT_EQ(result.right_slice->high(), 15); + EXPECT_FALSE(result.left_slice); +} + TEST_F(IntervalTests, ClosedRightRemains) { // [ this ] // [param][rs] + using lib_interval_tree::closed; const auto result = i(8, 15).slice(i(8, 12)); ASSERT_TRUE(result.right_slice); - EXPECT_EQ(result.right_slice->low(), 13); + EXPECT_EQ(result.right_slice->low(), 12); EXPECT_EQ(result.right_slice->high(), 15); EXPECT_FALSE(result.left_slice); } @@ -947,11 +970,11 @@ TEST_F(IntervalTests, OpenRightRemains) EXPECT_FALSE(result.left_slice); } -TEST_F(IntervalTests, ClosedMiddleExtrusion) +TEST_F(IntervalTests, ClosedAdjacentMiddleExtrusion) { // [ this ] // [ls][param][rs] - const auto result = i(0, 10).slice(i(5, 8)); + const auto result = i(0, 10).slice(i(5, 8)); ASSERT_TRUE(result.left_slice); EXPECT_EQ(result.left_slice->low(), 0); EXPECT_EQ(result.left_slice->high(), 4); @@ -972,4 +995,18 @@ TEST_F(IntervalTests, OpenMiddleExtrusion) ASSERT_TRUE(result.right_slice); EXPECT_EQ(result.right_slice->low(), 8); EXPECT_EQ(result.right_slice->high(), 10); +} + +TEST_F(IntervalTests, ClosedMiddleExtrusion) +{ + // [ this ] + // [ls][param][rs] + using lib_interval_tree::closed; + const auto result = i(0, 10).slice(i(5, 8)); + ASSERT_TRUE(result.left_slice); + EXPECT_EQ(result.left_slice->low(), 0); + EXPECT_EQ(result.left_slice->high(), 5); + ASSERT_TRUE(result.right_slice); + EXPECT_EQ(result.right_slice->low(), 8); + EXPECT_EQ(result.right_slice->high(), 10); } \ No newline at end of file diff --git a/tests/punch_tests.hpp b/tests/punch_tests.hpp index 93dd476..e8f492e 100644 --- a/tests/punch_tests.hpp +++ b/tests/punch_tests.hpp @@ -68,6 +68,28 @@ class PunchTests : public ::testing::Test }; TEST_F(PunchTests, PunchEmptyTree) +{ + using types = closed_adjacent; + + auto tree = types::tree_type{}; + auto result = tree.punch(types::interval_type{0, 5}); + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), 0); + EXPECT_EQ(result.begin()->high(), 5); +} + +TEST_F(PunchTests, PunchOpenEmptyTree) +{ + using types = open; + + auto tree = types::tree_type{}; + auto result = tree.punch(types::interval_type{0, 5}); + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), 0); + EXPECT_EQ(result.begin()->high(), 5); +} + +TEST_F(PunchTests, PunchClosedEmptyTree) { using types = closed; @@ -79,6 +101,19 @@ TEST_F(PunchTests, PunchEmptyTree) } TEST_F(PunchTests, PunchFullyRightOfTree) +{ + using types = closed_adjacent; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{20, 25}); + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), 20); + EXPECT_EQ(result.begin()->high(), 25); +} + +TEST_F(PunchTests, PunchFullyRightOfTreeClosed) { using types = closed; @@ -91,9 +126,22 @@ TEST_F(PunchTests, PunchFullyRightOfTree) EXPECT_EQ(result.begin()->high(), 25); } +TEST_F(PunchTests, PunchFullyRightOfTreeOpen) +{ + using types = open; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{20, 25}); + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), 20); + EXPECT_EQ(result.begin()->high(), 25); +} + TEST_F(PunchTests, PunchFullyLeftOfTree) { - using types = closed; + using types = closed_adjacent; auto tree = types::tree_type{}; tree.insert(types::interval_type{0, 5}); @@ -105,10 +153,38 @@ TEST_F(PunchTests, PunchFullyLeftOfTree) EXPECT_EQ(result.begin()->high(), -5); } -TEST_F(PunchTests, PunchAjdacentLeftOfClosedTree) +TEST_F(PunchTests, PunchFullyLeftOfTreeClosed) { using types = closed; + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{-10, -5}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), -10); + EXPECT_EQ(result.begin()->high(), -5); +} + +TEST_F(PunchTests, PunchFullyLeftOfTreeOpen) +{ + using types = open; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{-10, -5}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), -10); + EXPECT_EQ(result.begin()->high(), -5); +} + +TEST_F(PunchTests, PunchAdjacentLeftOfClosedAdjacentAdjacentTree) +{ + using types = closed_adjacent; + auto tree = types::tree_type{}; tree.insert(types::interval_type{0, 5}); tree.insert(types::interval_type{5, 10}); @@ -133,6 +209,20 @@ TEST_F(PunchTests, PunchAjdacentLeftOfOpenTree) EXPECT_EQ(result.begin()->high(), 0); } +TEST_F(PunchTests, PunchAjdacentLeftOfClosedTree) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{-10, 0}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), -10); + EXPECT_EQ(result.begin()->high(), 0); +} + TEST_F(PunchTests, PunchAjdacentLeftOfLeftOpenTree) { using types = left_open; @@ -149,7 +239,7 @@ TEST_F(PunchTests, PunchAjdacentLeftOfLeftOpenTree) TEST_F(PunchTests, PunchAjdacentLeftOfRightOpenTree) { - using types = left_open; + using types = right_open; auto tree = types::tree_type{}; tree.insert(types::interval_type{0, 5}); @@ -163,7 +253,7 @@ TEST_F(PunchTests, PunchAjdacentLeftOfRightOpenTree) TEST_F(PunchTests, PunchOverlapsLeftOfTreeLeftHanging) { - using types = closed; + using types = closed_adjacent; auto tree = types::tree_type{}; tree.insert(types::interval_type{0, 5}); @@ -175,10 +265,38 @@ TEST_F(PunchTests, PunchOverlapsLeftOfTreeLeftHanging) EXPECT_EQ(result.begin()->high(), -1); } -TEST_F(PunchTests, PunchOverlapsLeftOfTreeFullyExact) +TEST_F(PunchTests, PunchOverlapsLeftOfTreeLeftHangingClosed) { using types = closed; + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{-10, 2}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), -10); + EXPECT_EQ(result.begin()->high(), 0); +} + +TEST_F(PunchTests, PunchOverlapsLeftOfTreeLeftHangingOpen) +{ + using types = open; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{-10, 2}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), -10); + EXPECT_EQ(result.begin()->high(), 0); +} + +TEST_F(PunchTests, PunchOverlapsLeftOfTreeFullyExact) +{ + using types = closed_adjacent; + auto tree = types::tree_type{}; tree.insert(types::interval_type{0, 5}); tree.insert(types::interval_type{5, 10}); @@ -189,10 +307,38 @@ TEST_F(PunchTests, PunchOverlapsLeftOfTreeFullyExact) EXPECT_EQ(result.begin()->high(), -1); } -TEST_F(PunchTests, PunchOverlapsLeftOfTreeRightFullyOverNoGap) +TEST_F(PunchTests, PunchOverlapsLeftOfTreeFullyExactClosed) { using types = closed; + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{-10, 5}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), -10); + EXPECT_EQ(result.begin()->high(), 0); +} + +TEST_F(PunchTests, PunchOverlapsLeftOfTreeFullyExactOpen) +{ + using types = open; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(types::interval_type{-10, 5}); + + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result.begin()->low(), -10); + EXPECT_EQ(result.begin()->high(), 0); +} + +TEST_F(PunchTests, PunchOverlapsLeftOfTreeRightFullyOverNoGap) +{ + using types = closed_adjacent; + auto tree = types::tree_type{}; tree.insert(types::interval_type{0, 5}); tree.insert(types::interval_type{5, 10}); @@ -207,7 +353,7 @@ TEST_F(PunchTests, PunchOverlapsLeftOfTreeRightFullyOverNoGap) TEST_F(PunchTests, PunchOverlapsLeftOfTreeRightFullyOver) { - using types = closed; + using types = closed_adjacent; auto tree = types::tree_type{}; tree.insert(types::interval_type{0, 5}); @@ -226,7 +372,7 @@ TEST_F(PunchTests, PunchOverlapsLeftOfTreeRightFullyOver) TEST_F(PunchTests, PunchOverlapsLeftOfTreeFullyInsideInterval) { - using types = closed; + using types = closed_adjacent; auto tree = types::tree_type{}; tree.insert(types::interval_type{0, 5}); @@ -238,7 +384,7 @@ TEST_F(PunchTests, PunchOverlapsLeftOfTreeFullyInsideInterval) TEST_F(PunchTests, PunchOverlapsMiddleOfTreeFullyInsideInterval) { - using types = closed; + using types = closed_adjacent; auto tree = types::tree_type{}; tree.insert(types::interval_type{0, 5}); @@ -251,7 +397,7 @@ TEST_F(PunchTests, PunchOverlapsMiddleOfTreeFullyInsideInterval) TEST_F(PunchTests, PunchOverlapsRightOfTreeFullyInsideInterval) { - using types = closed; + using types = closed_adjacent; auto tree = types::tree_type{}; tree.insert(types::interval_type{0, 5}); @@ -262,9 +408,9 @@ TEST_F(PunchTests, PunchOverlapsRightOfTreeFullyInsideInterval) EXPECT_TRUE(result.empty()); } -TEST_F(PunchTests, ClosedPunchOverhangsLastIntervalOnRight) +TEST_F(PunchTests, ClosedAdjacentPunchOverhangsLastIntervalOnRight) { - using types = closed; + using types = closed_adjacent; auto tree = types::tree_type{}; tree.insert(types::interval_type{0, 5}); @@ -320,7 +466,7 @@ TEST_F(PunchTests, RightOpenPunchOverhangsLastIntervalOnRight) TEST_F(PunchTests, PunchSingleElementTreeEncompassing) { - using types = closed; + using types = closed_adjacent; auto tree = types::tree_type{}; tree.insert(types::interval_type{0, 5}); @@ -331,7 +477,7 @@ TEST_F(PunchTests, PunchSingleElementTreeEncompassing) TEST_F(PunchTests, PunchSingleElementTreeEncompassingWithRightOverlap) { - using types = closed; + using types = closed_adjacent; auto tree = types::tree_type{}; tree.insert(types::interval_type{0, 5}); @@ -344,7 +490,7 @@ TEST_F(PunchTests, PunchSingleElementTreeEncompassingWithRightOverlap) TEST_F(PunchTests, PunchSingleElementTreeEncompassingWithLeftOverlap) { - using types = closed; + using types = closed_adjacent; auto tree = types::tree_type{}; tree.insert(types::interval_type{0, 5}); @@ -479,7 +625,7 @@ TEST_F(PunchTests, DynamicPunchOverhangsFirstIntervalOnLeft) TEST_F(PunchTests, PunchEncompassesTree) { - using types = closed; + using types = closed_adjacent; auto tree = types::tree_type{}; tree.insert(types::interval_type{0, 5}); @@ -521,10 +667,66 @@ TEST_F(PunchTests, PunchEncompassesOpenTree) EXPECT_EQ(iter->high(), 30); } -TEST_F(PunchTests, PunchEncompassesTreeWithOverlap) +TEST_F(PunchTests, PunchEncompassesSmallGapOpenTree) +{ + using types = open; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{6, 10}); + tree.insert(types::interval_type{11, 15}); + tree.insert(types::interval_type{16, 20}); + auto result = tree.punch(); + + EXPECT_TRUE(result.empty()); +} + +TEST_F(PunchTests, PunchEncompassesSmallGapClosedTree) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{6, 10}); + tree.insert(types::interval_type{11, 15}); + tree.insert(types::interval_type{16, 20}); + auto result = tree.punch(); + + ASSERT_EQ(result.size(), 3); + auto iter = result.begin(); + EXPECT_EQ(iter->low(), 5); + EXPECT_EQ(iter->high(), 6); + EXPECT_EQ((++iter)->low(), 10); + EXPECT_EQ(iter->high(), 11); + EXPECT_EQ((++iter)->low(), 15); + EXPECT_EQ(iter->high(), 16); +} + +TEST_F(PunchTests, PunchEncompassesClosedTree) { using types = closed; + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{10, 15}); + tree.insert(types::interval_type{20, 25}); + tree.insert(types::interval_type{30, 35}); + auto result = tree.punch(types::interval_type{0, 35}); + + ASSERT_EQ(result.size(), 3); + auto iter = result.begin(); + EXPECT_EQ(iter->low(), 5); + EXPECT_EQ(iter->high(), 10); + EXPECT_EQ((++iter)->low(), 15); + EXPECT_EQ(iter->high(), 20); + EXPECT_EQ((++iter)->low(), 25); + EXPECT_EQ(iter->high(), 30); +} + +TEST_F(PunchTests, PunchEncompassesTreeWithOverlap) +{ + using types = closed_adjacent; + auto tree = types::tree_type{}; tree.insert(types::interval_type{0, 5}); tree.insert(types::interval_type{10, 15}); @@ -579,9 +781,38 @@ TEST_F(PunchTests, PunchEncompassesOpenTreeWithOverlap) EXPECT_EQ(iter->high(), 40); } +TEST_F(PunchTests, PunchEncompassesClosedTreeWithOverlap) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{10, 15}); + tree.insert(types::interval_type{20, 25}); + tree.insert(types::interval_type{30, 35}); + auto result = tree.punch(types::interval_type{-5, 40}); + + ASSERT_EQ(result.size(), 5); + auto iter = result.begin(); + EXPECT_EQ(iter->low(), -5); + EXPECT_EQ(iter->high(), 0); + + EXPECT_EQ((++iter)->low(), 5); + EXPECT_EQ(iter->high(), 10); + + EXPECT_EQ((++iter)->low(), 15); + EXPECT_EQ(iter->high(), 20); + + EXPECT_EQ((++iter)->low(), 25); + EXPECT_EQ(iter->high(), 30); + + EXPECT_EQ((++iter)->low(), 35); + EXPECT_EQ(iter->high(), 40); +} + TEST_F(PunchTests, UnsignedPunchHasNoProblemWithZero) { - using types = closed; + using types = closed_adjacent; auto tree = types::tree_type{}; tree.insert(types::interval_type{0, 5}); @@ -604,9 +835,9 @@ TEST_F(PunchTests, UnsignedPunchHasNoProblemWithZero) EXPECT_EQ(iter->high(), 7); } -TEST_F(PunchTests, PunchDoesNotInsertIntervalWhenClosedGapIsEmpty) +TEST_F(PunchTests, PunchDoesNotInsertIntervalWhenClosedAdjacentGapIsEmpty) { - using types = closed; + using types = closed_adjacent; auto tree = types::tree_type{}; tree.insert(types::interval_type{0, 5}); @@ -619,7 +850,25 @@ TEST_F(PunchTests, PunchDoesNotInsertIntervalWhenClosedGapIsEmpty) EXPECT_EQ(result.begin()->high(), 12); } -TEST_F(PunchTests, PunchDoesNotInsertIntervalWhenOpenGapIsEmpty) +TEST_F(PunchTests, PunchDoesInsertIntervalWhenClosedGapIsSmallNotEmpty) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{6, 10}); + + auto result = tree.punch(types::interval_type{0, 12}); + + ASSERT_EQ(result.size(), 2); + auto iter = result.begin(); + EXPECT_EQ(iter->low(), 5); + EXPECT_EQ(iter->high(), 6); + EXPECT_EQ((++iter)->low(), 10); + EXPECT_EQ(iter->high(), 12); +} + +TEST_F(PunchTests, PunchDoesInsertIntervalWhenOpenGapIsEmpty) { using types = open; @@ -629,9 +878,12 @@ TEST_F(PunchTests, PunchDoesNotInsertIntervalWhenOpenGapIsEmpty) auto result = tree.punch(types::interval_type{0, 12}); + // (5, 6) is the empty set on the whole number line. + ASSERT_EQ(result.size(), 1); - EXPECT_EQ(result.begin()->low(), 10); - EXPECT_EQ(result.begin()->high(), 12); + auto iter = result.begin(); + EXPECT_EQ(iter->low(), 10); + EXPECT_EQ(iter->high(), 12); } TEST_F(PunchTests, OpenFloatGap) @@ -663,6 +915,24 @@ TEST_F(PunchTests, ClosedFloatGap) auto result = tree.punch(types::interval_type{0.0f, 12.0f}); ASSERT_EQ(result.size(), 2); + auto iter = result.begin(); + EXPECT_FLOAT_EQ(iter->low(), 5.0f); + EXPECT_FLOAT_EQ(iter->high(), 6.0f); + EXPECT_FLOAT_EQ((++iter)->low(), 10.0f); + EXPECT_FLOAT_EQ(iter->high(), 12.0f); +} + +TEST_F(PunchTests, ClosedAdjacentFloatGap) +{ + using types = closed_adjacent; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0.0f, 5.0f}); + tree.insert(types::interval_type{6.0f, 10.0f}); + + auto result = tree.punch(types::interval_type{0.0f, 12.0f}); + ASSERT_EQ(result.size(), 2); + auto iter = result.begin(); EXPECT_NEAR(iter->low(), 5.0f, 0.001f); EXPECT_NEAR(iter->high(), 6.0f, 0.001f); @@ -671,6 +941,29 @@ TEST_F(PunchTests, ClosedFloatGap) } TEST_F(PunchTests, PunchWithoutArgsEncompassesTree) +{ + using types = open; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{10, 15}); + tree.insert(types::interval_type{20, 25}); + tree.insert(types::interval_type{30, 35}); + auto result = tree.punch(); + + ASSERT_EQ(result.size(), 3); + auto iter = result.begin(); + EXPECT_EQ(iter->low(), 5); + EXPECT_EQ(iter->high(), 10); + + EXPECT_EQ((++iter)->low(), 15); + EXPECT_EQ(iter->high(), 20); + + EXPECT_EQ((++iter)->low(), 25); + EXPECT_EQ(iter->high(), 30); +} + +TEST_F(PunchTests, PunchWithoutArgsEncompassesTreeClosed) { using types = closed; @@ -681,6 +974,29 @@ TEST_F(PunchTests, PunchWithoutArgsEncompassesTree) tree.insert(types::interval_type{30, 35}); auto result = tree.punch(); + ASSERT_EQ(result.size(), 3); + auto iter = result.begin(); + EXPECT_EQ(iter->low(), 5); + EXPECT_EQ(iter->high(), 10); + + EXPECT_EQ((++iter)->low(), 15); + EXPECT_EQ(iter->high(), 20); + + EXPECT_EQ((++iter)->low(), 25); + EXPECT_EQ(iter->high(), 30); +} + +TEST_F(PunchTests, PunchWithoutArgsEncompassesTreeClosedAdjacent) +{ + using types = closed_adjacent; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{10, 15}); + tree.insert(types::interval_type{20, 25}); + tree.insert(types::interval_type{30, 35}); + auto result = tree.punch(); + ASSERT_EQ(result.size(), 3); auto iter = result.begin(); EXPECT_EQ(iter->low(), 6); @@ -691,4 +1007,16 @@ TEST_F(PunchTests, PunchWithoutArgsEncompassesTree) EXPECT_EQ((++iter)->low(), 26); EXPECT_EQ(iter->high(), 29); +} + +TEST_F(PunchTests, ClosedNoGap) +{ + using types = closed; + + auto tree = types::tree_type{}; + tree.insert(types::interval_type{0, 5}); + tree.insert(types::interval_type{5, 10}); + auto result = tree.punch(); + + EXPECT_TRUE(result.empty()); } \ No newline at end of file From 00ad98f9d3086f776fd8e5fb989cd26566704768 Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Sun, 27 Jul 2025 02:58:19 +0200 Subject: [PATCH 11/11] Added adjacent closed punch drawing. --- drawings/example_drawings.hpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/drawings/example_drawings.hpp b/drawings/example_drawings.hpp index 00ff5af..e0b8fcf 100644 --- a/drawings/example_drawings.hpp +++ b/drawings/example_drawings.hpp @@ -151,6 +151,32 @@ static void drawClosedPunchExample() drawTree("drawings/closed_punched.png", punched, false, false); } +static void drawAdjacentClosedPunchExample() +{ + constexpr int iterations = 5; + using namespace lib_interval_tree; + + interval_tree> tree; + + // insert shuffled into new tree + std::vector> intervals; + for (int i = 0; i < iterations; ++i) + { + intervals.emplace_back(i * 10, i * 10 + 5); + } + + std::mt19937 rng(std::random_device{}()); + std::shuffle(intervals.begin(), intervals.end(), rng); + for (const auto& interval : intervals) + { + tree.insert({interval.first, interval.second}); + } + + drawTree("drawings/closed_adjacent_punch_source.png", tree, false, false); + const auto punched = tree.punch({-10, iterations * 10 + 10}); + drawTree("drawings/closed_adjacent_punched.png", punched, false, false); +} + static void drawFloatPunchExample() { constexpr int iterations = 5; @@ -184,5 +210,6 @@ static void drawAll() drawLargeOverlapFree(); drawOpenPunchExample(); drawClosedPunchExample(); + drawAdjacentClosedPunchExample(); drawFloatPunchExample(); }