From 4d0368b5f12454952cb3e8d3c8c158f4de7efbfd Mon Sep 17 00:00:00 2001 From: Philip Rideout Date: Wed, 19 Jan 2022 13:12:38 -0800 Subject: [PATCH] RangeMap: improve the auto-merge functionality. --- libs/utils/include/utils/RangeMap.h | 62 ++++++++++++++++++++++++----- libs/utils/test/test_RangeMap.cpp | 29 ++++++++++++++ 2 files changed, 80 insertions(+), 11 deletions(-) diff --git a/libs/utils/include/utils/RangeMap.h b/libs/utils/include/utils/RangeMap.h index 576b7a55f4..b437a03b9c 100644 --- a/libs/utils/include/utils/RangeMap.h +++ b/libs/utils/include/utils/RangeMap.h @@ -52,6 +52,7 @@ public: if (existing.last < last) { wipe(existing.last, last); existing.last = last; + merge_right(iter); } return; } @@ -65,8 +66,12 @@ public: } // Clip the end of the existing range and potentially remove it. existing.last = first; - if (existing.empty()) { - mMap.erase(iter); + clean(iter); + // Check there is a range to the right that needs to be clipped. + iter = find(last); + if (iter != end()) { + get_range(iter).first = last; + clean(iter); } wipe(first, last); insert(first, last, value); @@ -88,15 +93,14 @@ public: if (existing.first > first) { wipe(first, existing.first); existing.first = first; + merge_left(iter); } return; } // Clip the beginning of the existing range and potentially remove it. existing.first = last; - if (existing.empty()) { - mMap.erase(iter); - } + clean(iter); wipe(first, last); insert(first, last, value); } @@ -142,9 +146,7 @@ public: } // Clip the end of the existing range and potentially remove it. existing.last = first; - if (existing.empty()) { - mMap.erase(iter); - } + clean(iter); wipe(first, last); return; } @@ -160,9 +162,7 @@ public: // Clip the beginning of the existing range and potentially remove it. existing.first = last; - if (existing.empty()) { - mMap.erase(iter); - } + clean(iter); wipe(first, last); } @@ -206,6 +206,7 @@ private: KeyType previous = first; if (iterator iter = find(--previous); iter != end() && get_value(iter) == value) { get_range(iter).last = last; + merge_right(iter); return; } @@ -232,6 +233,45 @@ private: } } + // Check if there is range to the right that touches the given range. + // If so, erase it, extend the given range rightwards, and return true. + bool merge_right(iterator iter) { + iterator next = iter; + if (++next == end() || get_value(next) != get_value(iter)) { + return false; + } + if (get_range(next).first != get_range(iter).last) { + return false; + } + get_range(iter).last = get_range(next).last; + mMap.erase(next); + return true; + } + + // Check if there is range to left right that touches the given range. + // If so, erase it, extend the given range leftwards, and return true. + bool merge_left(iterator iter) { + iterator prev = iter; + if (--prev == end() || get_value(prev) != get_value(iter)) { + return false; + } + if (get_range(prev).last != get_range(iter).first) { + return false; + } + get_range(iter).first = get_range(prev).first; + mMap.erase(prev); + return true; + } + + // Erase the given range if it contains no elements. + void clean(iterator iter) { + Range& range = get_range(iter); + assert_invariant(range.first <= range.last); + if (range.first == range.last) { + mMap.erase(iter); + } + } + const_iterator find(KeyType key) const noexcept { // Find the first range whose beginning is greater than or equal to the given key. const_iterator iter = mMap.lower_bound(key); diff --git a/libs/utils/test/test_RangeMap.cpp b/libs/utils/test/test_RangeMap.cpp index 5dae7fd2e5..908d4f9bbb 100644 --- a/libs/utils/test/test_RangeMap.cpp +++ b/libs/utils/test/test_RangeMap.cpp @@ -22,6 +22,7 @@ using namespace utils; TEST(RangeMapTest, Simple) { RangeMap map; + map.add(45, 104, 'c'); EXPECT_FALSE(map.has(104)); EXPECT_EQ(map.get(45), 'c'); @@ -89,4 +90,32 @@ TEST(RangeMapTest, Simple) { EXPECT_TRUE(map.has(54)); EXPECT_FALSE(map.has(55)); EXPECT_TRUE(map.has(56)); + + map.clear(-100, 200); + map.add(0, 10, 'a'); + map.add(12, 15, 'a'); + map.add(20, 22, 'b'); + map.add(23, 26, 'c'); + EXPECT_EQ(map.range_count(), 4); + + // Test a bunch of different ways that the first and second intervals can be merged. + map.add(10, 12, 'a'); + EXPECT_EQ(map.range_count(), 3); + map.clear(10, 12); + EXPECT_EQ(map.range_count(), 4); + map.add(9, 12, 'a'); + EXPECT_EQ(map.range_count(), 3); + map.clear(10, 12); + EXPECT_EQ(map.range_count(), 4); + map.add(10, 13, 'a'); + EXPECT_EQ(map.range_count(), 3); + map.add(10, 12, 'b'); + EXPECT_EQ(map.range_count(), 4); + + map.add(0, 26, 'b'); + EXPECT_EQ(map.range_count(), 1); + map.add(10, 12, 'c'); + EXPECT_EQ(map.range_count(), 3); + map.add(9, 13, 'c'); + EXPECT_EQ(map.range_count(), 3); }