diff --git a/include/interval-tree/interval_tree.hpp b/include/interval-tree/interval_tree.hpp index 28860bd..40b033a 100644 --- a/include/interval-tree/interval_tree.hpp +++ b/include/interval-tree/interval_tree.hpp @@ -707,6 +707,10 @@ namespace lib_interval_tree const_interval_tree_iterator& operator=(const_interval_tree_iterator const&) = default; const_interval_tree_iterator& operator=(const_interval_tree_iterator&&) noexcept = default; + explicit const_interval_tree_iterator(interval_tree_iterator const& other) + : iterator_base{other.node_, other.owner_} + {} + template friend void increment(T& iter); template @@ -1357,11 +1361,18 @@ namespace lib_interval_tree } template const_iterator - find_next_in_subtree(iterator from, interval_type const& ival, CompareFunctionT const& compare) const + find_next_in_subtree(const_iterator from, interval_type const& ival, CompareFunctionT const& compare) const { if (root_ == nullptr) return end(); - return iterator{find_i_ex(from.node_, ival, compare), this}; + return const_iterator{find_i_ex(from.node_, ival, compare), this}; + } + // Convenience, so that a mutable iterator does not have to be converted by hand. + template + const_iterator + find_next_in_subtree(iterator from, interval_type const& ival, CompareFunctionT const& compare) const + { + return find_next_in_subtree(const_iterator{from}, ival, compare); } /** @@ -1377,12 +1388,17 @@ namespace lib_interval_tree return lhs == rhs; }); } - const_iterator find_next_in_subtree(iterator from, interval_type const& ival) const + const_iterator find_next_in_subtree(const_iterator from, interval_type const& ival) const { return find_next_in_subtree(from, ival, [](auto const& lhs, auto const& rhs) { return lhs == rhs; }); } + // Convenience, so that a mutable iterator does not have to be converted by hand. + const_iterator find_next_in_subtree(iterator from, interval_type const& ival) const + { + return find_next_in_subtree(const_iterator{from}, ival); + } /** * Finds the first interval that overlaps with ival. @@ -1447,14 +1463,26 @@ namespace lib_interval_tree { if (root_ == nullptr) return end(); - return iterator{overlap_find_i_ex(from.node_, ival, exclusive), this}; + if (exclusive) + return iterator{overlap_find_i_ex(from.node_, ival), this}; + else + return iterator{overlap_find_i_ex(from.node_, ival), this}; } const_iterator overlap_find_next_in_subtree(const_iterator from, interval_type const& ival, bool exclusive = false) const { if (root_ == nullptr) return end(); - return const_iterator{overlap_find_i_ex(from.node_, ival, exclusive), this}; + if (exclusive) + return const_iterator{overlap_find_i_ex(from.node_, ival), this}; + else + return const_iterator{overlap_find_i_ex(from.node_, ival), this}; + } + // Convenience, so that a mutable iterator does not have to be converted by hand. + const_iterator + overlap_find_next_in_subtree(iterator from, interval_type const& ival, bool exclusive = false) const + { + return overlap_find_next_in_subtree(const_iterator{from}, ival, exclusive); } /** @@ -1793,9 +1821,10 @@ namespace lib_interval_tree return find_i_ex(ptr, ival, compare); } - // excludes ptr + // excludes ptr, which is therefore never dereferenced for anything but its child pointers. template - node_type* find_i_ex(node_type* ptr, interval_type const& ival, ComparatorFunctionT const& compare) const + node_type* + find_i_ex(node_type const* ptr, interval_type const& ival, ComparatorFunctionT const& compare) const { if (ptr->left_ && ival.high() <= ptr->left_->max()) { @@ -1877,9 +1906,9 @@ namespace lib_interval_tree return true; } - // excludes ptr + // excludes ptr, which is therefore never dereferenced for anything but its child pointers. template - node_type* overlap_find_i_ex(node_type* ptr, interval_type const& ival) const + node_type* overlap_find_i_ex(node_type const* ptr, interval_type const& ival) const { if (ptr->left_ && ptr->left_->max() >= ival.low()) { diff --git a/tests/find_tests.hpp b/tests/find_tests.hpp index c1f5429..f31d7d0 100644 --- a/tests/find_tests.hpp +++ b/tests/find_tests.hpp @@ -165,6 +165,71 @@ TEST_F(FindTests, CanFindAllOnConstTree) EXPECT_EQ(intervals[0], targetInterval); } +TEST_F(FindTests, WillReturnEndForNextInSubtreeIfTreeIsEmpty) +{ + EXPECT_EQ(tree.find_next_in_subtree(tree.root(), {2, 7}), std::end(tree)); +} + +TEST_F(FindTests, WillFindNextExactMatchInSubtree) +{ + const auto targetInterval = decltype(tree)::interval_type{0, 5}; + tree.insert(targetInterval); + tree.insert({10, 15}); + tree.insert(targetInterval); + tree.insert({30, 35}); + + // The node started from is excluded, so this must find the duplicate and not the root itself. + const auto found = tree.find_next_in_subtree(tree.root(), targetInterval); + ASSERT_NE(found, std::end(tree)); + EXPECT_EQ(*found, targetInterval); + EXPECT_NE(found, tree.root()); + + EXPECT_EQ(tree.find_next_in_subtree(tree.root(), {99, 100}), std::end(tree)); +} + +TEST_F(FindTests, WillFindNextExactMatchInSubtreeOnConstTree) +{ + const auto targetInterval = decltype(tree)::interval_type{0, 5}; + tree.insert(targetInterval); + tree.insert({10, 15}); + tree.insert(targetInterval); + tree.insert({30, 35}); + + [&targetInterval](auto const& tree) { + const auto found = tree.find_next_in_subtree(tree.root(), targetInterval); + static_assert( + std::is_same::const_iterator const>::value, + "the const overload must yield a const_iterator" + ); + ASSERT_NE(found, std::end(tree)); + EXPECT_EQ(*found, targetInterval); + }(tree); +} + +TEST_F(FindTests, CanFindNextExactMatchInSubtreeOnConstTreeFromMutableIterator) +{ + const auto targetInterval = decltype(tree)::interval_type{0, 5}; + tree.insert(targetInterval); + tree.insert({10, 15}); + tree.insert(targetInterval); + tree.insert({30, 35}); + + // A mutable iterator is accepted by the const overloads without converting it by hand. + const auto from = tree.root(); + const auto compare = [](auto const& lhs, auto const& rhs) { + return lhs == rhs; + }; + [&from, &targetInterval, &compare](auto const& tree) { + const auto found = tree.find_next_in_subtree(from, targetInterval); + ASSERT_NE(found, std::end(tree)); + EXPECT_EQ(*found, targetInterval); + + const auto foundWithCompare = tree.find_next_in_subtree(from, targetInterval, compare); + ASSERT_NE(foundWithCompare, std::end(tree)); + EXPECT_EQ(*foundWithCompare, targetInterval); + }(tree); +} + TEST_F(FindTests, FuzzyFindAllInTree) { std::mt19937 gen{0}; diff --git a/tests/overlap_find_tests.hpp b/tests/overlap_find_tests.hpp index ed2671b..c7632ea 100644 --- a/tests/overlap_find_tests.hpp +++ b/tests/overlap_find_tests.hpp @@ -182,6 +182,86 @@ TEST_F(OverlapFindTests, CanOverlapFindAllOnConstTree) EXPECT_EQ(intervals[0], targetInterval); } +TEST_F(OverlapFindTests, WillReturnEndForNextInSubtreeIfTreeIsEmpty) +{ + EXPECT_EQ(tree.overlap_find_next_in_subtree(tree.root(), {2, 7}), std::end(tree)); +} + +TEST_F(OverlapFindTests, WillFindNextOverlapInSubtree) +{ + tree.insert({0, 5}); + tree.insert({10, 15}); + tree.insert({20, 25}); + tree.insert({30, 35}); + + const auto found = tree.overlap_find_next_in_subtree(tree.root(), {21, 22}); + ASSERT_NE(found, std::end(tree)); + EXPECT_EQ(*found, (decltype(tree)::interval_type{20, 25})); +} + +TEST_F(OverlapFindTests, WillNotFindNextOverlapInSubtreeWithTheStartNodeItself) +{ + tree.insert({0, 5}); + tree.insert({10, 15}); + tree.insert({20, 25}); + tree.insert({30, 35}); + + // The node started from is excluded and all intervals are disjoint, so nothing else can overlap it. + const auto rootInterval = *tree.root(); + EXPECT_EQ(tree.overlap_find_next_in_subtree(tree.root(), rootInterval), std::end(tree)); +} + +TEST_F(OverlapFindTests, WillObeyExclusiveWhenFindingNextOverlapInSubtree) +{ + tree.insert({0, 5}); + tree.insert({10, 15}); + tree.insert({20, 25}); + tree.insert({30, 35}); + + // {25, 27} touches {20, 25} on the border only. + const auto inclusive = tree.overlap_find_next_in_subtree(tree.root(), {25, 27}, false); + ASSERT_NE(inclusive, std::end(tree)); + EXPECT_EQ(*inclusive, (decltype(tree)::interval_type{20, 25})); + + EXPECT_EQ(tree.overlap_find_next_in_subtree(tree.root(), {25, 27}, true), std::end(tree)); +} + +TEST_F(OverlapFindTests, WillFindNextOverlapInSubtreeOnConstTree) +{ + tree.insert({0, 5}); + tree.insert({10, 15}); + tree.insert({20, 25}); + tree.insert({30, 35}); + + const auto expected = decltype(tree)::interval_type{20, 25}; + [&expected](auto const& tree) { + const auto found = tree.overlap_find_next_in_subtree(tree.root(), {21, 22}); + ASSERT_NE(found, std::end(tree)); + EXPECT_EQ(*found, expected); + }(tree); +} + +TEST_F(OverlapFindTests, CanConvertIteratorToConstIteratorForNextOverlapInSubtree) +{ + tree.insert({0, 5}); + tree.insert({10, 15}); + tree.insert({20, 25}); + tree.insert({30, 35}); + + // The conversion itself is explicit, but the const overloads also accept a mutable iterator directly. + const auto converted = decltype(tree)::const_iterator{tree.root()}; + const auto mutableIterator = tree.root(); + const auto expected = decltype(tree)::interval_type{20, 25}; + [&converted, &mutableIterator, &expected](auto const& tree) { + const auto found = tree.overlap_find_next_in_subtree(converted, {21, 22}); + ASSERT_NE(found, std::end(tree)); + EXPECT_EQ(*found, expected); + + const auto fromMutable = tree.overlap_find_next_in_subtree(mutableIterator, {21, 22}); + EXPECT_EQ(fromMutable, found); + }(tree); +} + TEST_F(OverlapFindTests, FuzzyOverlapFindAllInTree) { std::mt19937 gen{0};