Add update and merge methods to Intervals<T>

Also updated build files and added tests for `Intervals<T>`.
This commit is contained in:
Benson Joeris
2019-02-08 16:15:12 -05:00
committed by Baldur Karlsson
parent a6a1a76ca5
commit 3bc53094da
5 changed files with 491 additions and 7 deletions
+2
View File
@@ -105,6 +105,8 @@ set(sources
core/remote_server.cpp
core/replay_proxy.cpp
core/replay_proxy.h
core/intervals.h
core/intervals_tests.cpp
android/android.cpp
android/android_patch.cpp
android/android_tools.cpp
+92 -7
View File
@@ -189,21 +189,106 @@ public:
inline iterator begin() { return Wrap(StartPoints.begin()); }
inline const_iterator begin() const { return Wrap(StartPoints.begin()); }
inline const_iterator end() const { return Wrap(StartPoints.end()); }
// finds the interval containing `x`.
// Find the interval containing `x`.
iterator find(uint64_t x)
{
auto it = StartPoints.upper_bound(x); // first starting after `x`
// Find the first interval starting after `x`; return the preceding interval.
auto it = StartPoints.upper_bound(x);
RDCASSERT(it != StartPoints.begin());
it--; // last *not* starting after `x`
it--;
return Wrap(it);
}
// finds the interval containing `x`.
// Find the interval containing `x`.
const_iterator find(uint64_t x) const
{
auto it = StartPoints.upper_bound(x); // first starting after `x`
// Find the first interval starting after `x`; return the preceding interval.
auto it = StartPoints.upper_bound(x);
RDCASSERT(it != StartPoints.begin());
it--; // last *not* starting after `x`
it--;
return Wrap(it);
}
};
// Update the values of overlapping intervals to `comp(oldValue, val)`
// (where `oldValue` is the value of the interval prior to calling `update`).
// If start/finish do not lie on the boundaries between intervals, the intervals
// will be split as necessary.
template <typename Compose>
void update(uint64_t start, uint64_t finish, T val, Compose comp)
{
auto i = find(start);
// Split the interval so that `i.start == start`
i->split(start);
// Loop over all the intervals in `a` that intersect the interval [start, finish)
for(; i->start() < finish; i++)
{
if(i->finish() > finish)
{
// In this case, interval `i` extends beyond `finish`;
// split `i` so that we only update the portion of `i` in [start, finish).
i->split(finish);
// `split` leaves `i` pointing at the interval starting at `finish`;
// move back to the interval finishing at `finish`.
i--;
}
i->setValue(comp(i->value(), val));
i->mergeLeft();
}
// `i` now points to the interval following the last interval whose value was
// modified; merge `i` with that last modified interval, if the values match.
i->mergeLeft();
}
// Update `this` by composing the value of each interval with the value of the
// corresponding interval in `other`.
// If the intervals in `this` and `other` do not line up, then the intervals in
// `this` will be split as necessary.
template <typename Compose>
void merge(const Intervals &other, Compose comp)
{
auto j = other.begin();
auto i = begin();
// Loop over the intervals in `this` (iterator `i`), while maintaining the
// interval `j` in `other` that contains `i`.
// The intervals in `this` are split as necessary, so that each `i` is
// contained in a single interval of `other`.
// Loop invariants:
// * i.start() >= j.start()
// * i.start() < j.end()
while(true)
{
RDCASSERT(i->start() >= j->start());
RDCASSERT(i->start() < j->finish());
if(i->finish() > j->finish())
{
i->split(j->finish());
i--;
}
// Now i is contained in j, so we can update the value of all of i
i->setValue(comp(i->value(), j->value()));
// The value of i and the interval left of i are now final;
// if these two intervals now have the same value, they can safely be
// merged into a single interval.
i->mergeLeft();
// Move to the next interval in `this`; also advance to the next interval
// in `other`, if necessary to maintain the invariant `i.start < j.end`.
i++;
if(i == end())
{
j++;
RDCASSERT(j == other.end());
return;
}
if(i->start() >= j->finish())
j++;
}
}
};
+393
View File
@@ -0,0 +1,393 @@
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "intervals.h"
#include "common/globalconfig.h"
#if ENABLED(ENABLE_UNIT_TESTS)
#include "3rdparty/catch/catch.hpp"
#include <stdint.h>
#include <vector>
struct Interval
{
uint64_t start;
uint64_t value;
uint64_t end;
};
void check_intervals(Intervals<uint64_t> &value, const std::vector<Interval> &expected)
{
auto i = value.begin();
auto j = expected.begin();
for(; i != value.end() && j != expected.end(); i++, j++)
{
CHECK(i->start() == j->start);
CHECK(i->value() == j->value);
CHECK(i->finish() == j->end);
}
CHECK((i == value.end()));
CHECK((j == expected.end()));
}
Intervals<uint64_t> make_intervals(const std::vector<Interval> &intervals)
{
Intervals<uint64_t> res;
for(auto i = intervals.begin(); i != intervals.end(); i++)
{
auto j = res.end();
j--;
if(i->start > j->start())
j->split(i->start);
if(i->end < j->finish())
{
j->split(i->end);
j--;
}
j->setValue(i->value);
}
check_intervals(res, intervals);
return res;
}
TEST_CASE("Test Intervals type", "[intervals]")
{
SECTION("update tests")
{
SECTION("empty Intervals")
{
Intervals<uint64_t> test;
check_intervals(test, {{0, 0, UINT64_MAX}});
};
SECTION("update a sub-interval")
{
Intervals<uint64_t> test;
test.update(5, 10, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}});
};
SECTION("update a sub-interval matching on the left")
{
Intervals<uint64_t> test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}});
test.update(5, 7, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 5}, {5, 2, 7}, {7, 1, 10}, {10, 0, UINT64_MAX}});
};
SECTION("update a sub-interval matching on the right")
{
Intervals<uint64_t> test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}});
test.update(7, 10, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 5}, {5, 1, 7}, {7, 2, 10}, {10, 0, UINT64_MAX}});
};
SECTION("update an interval that exactly matches an existing interval")
{
Intervals<uint64_t> test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}});
test.update(5, 10, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 5}, {5, 2, 10}, {10, 0, UINT64_MAX}});
};
SECTION("update a properly overlapping interval")
{
Intervals<uint64_t> test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}});
test.update(7, 15, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 5}, {5, 1, 7}, {7, 2, 10}, {10, 1, 15}, {15, 0, UINT64_MAX}});
};
SECTION("update a super-interval")
{
Intervals<uint64_t> test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}});
test.update(2, 15, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 2}, {2, 1, 5}, {5, 2, 10}, {10, 1, 15}, {15, 0, UINT64_MAX}});
};
SECTION("update a super-interval matching on the left")
{
Intervals<uint64_t> test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}});
test.update(5, 15, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 5}, {5, 2, 10}, {10, 1, 15}, {15, 0, UINT64_MAX}});
};
SECTION("update a super-interval matching on the right")
{
Intervals<uint64_t> test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}});
test.update(2, 10, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 2}, {2, 1, 5}, {5, 2, 10}, {10, 0, UINT64_MAX}});
};
SECTION("update overlapping 2 intervals")
{
Intervals<uint64_t> test =
make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, 20}, {20, 10, 30}, {30, 0, UINT64_MAX}});
test.update(7, 25, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 5},
{5, 1, 7},
{7, 2, 10},
{10, 1, 20},
{20, 11, 25},
{25, 10, 30},
{30, 0, UINT64_MAX}});
};
SECTION("update overlapping 2 intervals matching on start of leftmost interval")
{
Intervals<uint64_t> test =
make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, 20}, {20, 10, 30}, {30, 0, UINT64_MAX}});
test.update(5, 25, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(
test,
{{0, 0, 5}, {5, 2, 10}, {10, 1, 20}, {20, 11, 25}, {25, 10, 30}, {30, 0, UINT64_MAX}});
};
SECTION("update overlapping 2 intervals matching on end of leftmost interval")
{
Intervals<uint64_t> test =
make_intervals({{0, 0, 5}, {5, 5, 10}, {10, 0, 20}, {20, 10, 30}, {30, 0, UINT64_MAX}});
test.update(10, 25, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(
test,
{{0, 0, 5}, {5, 5, 10}, {10, 1, 20}, {20, 11, 25}, {25, 10, 30}, {30, 0, UINT64_MAX}});
};
SECTION("update overlapping 2 intervals matching on start of rightmost interval")
{
Intervals<uint64_t> test =
make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, 20}, {20, 10, 30}, {30, 0, UINT64_MAX}});
test.update(7, 20, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(
test, {{0, 0, 5}, {5, 1, 7}, {7, 2, 10}, {10, 1, 20}, {20, 10, 30}, {30, 0, UINT64_MAX}});
};
SECTION("update overlapping 2 intervals matching on end of rightmost interval")
{
Intervals<uint64_t> test =
make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, 20}, {20, 10, 30}, {30, 0, UINT64_MAX}});
test.update(7, 30, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(
test, {{0, 0, 5}, {5, 1, 7}, {7, 2, 10}, {10, 1, 20}, {20, 11, 30}, {30, 0, UINT64_MAX}});
};
SECTION("update triggering merge on left")
{
Intervals<uint64_t> test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}});
test.update(10, 20, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 5}, {5, 1, 20}, {20, 0, UINT64_MAX}});
};
SECTION("update triggering merge on right")
{
Intervals<uint64_t> test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}});
test.update(2, 5, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 2}, {2, 1, 10}, {10, 0, UINT64_MAX}});
};
SECTION("overlapping update triggering merge on left")
{
Intervals<uint64_t> test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}});
test.update(7, 20, 1, [](uint64_t, uint64_t) -> uint64_t { return 1; });
check_intervals(test, {{0, 0, 5}, {5, 1, 20}, {20, 0, UINT64_MAX}});
};
SECTION("overlapping update triggering merge on right")
{
Intervals<uint64_t> test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}});
test.update(2, 7, 1, [](uint64_t, uint64_t) -> uint64_t { return 1; });
check_intervals(test, {{0, 0, 2}, {2, 1, 10}, {10, 0, UINT64_MAX}});
};
SECTION("update triggering multiple merges")
{
Intervals<uint64_t> test = make_intervals(
{{0, 0, 5}, {5, 1, 10}, {10, 0, 12}, {12, 5, 18}, {18, 0, 20}, {20, 1, 30}, {30, 0, UINT64_MAX}});
test.update(7, 25, 1, [](uint64_t, uint64_t) -> uint64_t { return 1; });
check_intervals(test, {{0, 0, 5}, {5, 1, 30}, {30, 0, UINT64_MAX}});
};
SECTION(
"update triggering multiple merges, including merge with non-overlapping interval on left")
{
Intervals<uint64_t> test = make_intervals(
{{0, 0, 5}, {5, 1, 10}, {10, 0, 12}, {12, 5, 18}, {18, 0, 20}, {20, 1, 30}, {30, 0, UINT64_MAX}});
test.update(10, 25, 1, [](uint64_t, uint64_t) -> uint64_t { return 1; });
check_intervals(test, {{0, 0, 5}, {5, 1, 30}, {30, 0, UINT64_MAX}});
};
SECTION(
"update triggering multiple merges, including merge with non-overlapping interval on right")
{
Intervals<uint64_t> test = make_intervals(
{{0, 0, 5}, {5, 1, 10}, {10, 0, 12}, {12, 5, 18}, {18, 0, 20}, {20, 1, 30}, {30, 0, UINT64_MAX}});
test.update(7, 20, 1, [](uint64_t, uint64_t) -> uint64_t { return 1; });
check_intervals(test, {{0, 0, 5}, {5, 1, 30}, {30, 0, UINT64_MAX}});
};
};
SECTION("mergeIntervals tests")
{
SECTION("merge matching intervals")
{
Intervals<uint64_t> test =
make_intervals({{0, 0, 10}, {10, 1, 20}, {20, 0, 30}, {30, 1, 40}, {40, 0, UINT64_MAX}});
Intervals<uint64_t> other =
make_intervals({{0, 0, 10}, {10, 1, 20}, {20, 0, 30}, {30, 1, 40}, {40, 0, UINT64_MAX}});
test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 10}, {10, 2, 20}, {20, 0, 30}, {30, 2, 40}, {40, 0, UINT64_MAX}});
};
SECTION("merge shifted intervals")
{
Intervals<uint64_t> test =
make_intervals({{0, 0, 10}, {10, 1, 20}, {20, 0, 30}, {30, 1, 40}, {40, 0, UINT64_MAX}});
Intervals<uint64_t> other =
make_intervals({{0, 0, 5}, {5, 1, 15}, {15, 0, 25}, {25, 1, 35}, {35, 0, UINT64_MAX}});
test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 5},
{5, 1, 10},
{10, 2, 15},
{15, 1, 20},
{20, 0, 25},
{25, 1, 30},
{30, 2, 35},
{35, 1, 40},
{40, 0, UINT64_MAX}});
};
SECTION("merge into empty intervals")
{
Intervals<uint64_t> test = make_intervals({{0, 0, UINT64_MAX}});
Intervals<uint64_t> other =
make_intervals({{0, 0, 5}, {5, 1, 15}, {15, 0, 25}, {25, 1, 35}, {35, 0, UINT64_MAX}});
test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 5}, {5, 1, 15}, {15, 0, 25}, {25, 1, 35}, {35, 0, UINT64_MAX}});
};
SECTION("merge with empty intervals")
{
Intervals<uint64_t> test =
make_intervals({{0, 0, 5}, {5, 1, 15}, {15, 0, 25}, {25, 1, 35}, {35, 0, UINT64_MAX}});
Intervals<uint64_t> other = make_intervals({{0, 0, UINT64_MAX}});
test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 5}, {5, 1, 15}, {15, 0, 25}, {25, 1, 35}, {35, 0, UINT64_MAX}});
};
SECTION("merge into single interval")
{
Intervals<uint64_t> test = make_intervals({{0, 0, 10}, {10, 1, 30}, {30, 0, UINT64_MAX}});
Intervals<uint64_t> other =
make_intervals({{0, 0, 5}, {5, 1, 15}, {15, 0, 25}, {25, 1, 35}, {35, 0, UINT64_MAX}});
test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 5},
{5, 1, 10},
{10, 2, 15},
{15, 1, 25},
{25, 2, 30},
{30, 1, 35},
{35, 0, UINT64_MAX}});
};
SECTION("merge with single interval")
{
Intervals<uint64_t> test =
make_intervals({{0, 0, 5}, {5, 1, 15}, {15, 0, 25}, {25, 1, 35}, {35, 0, UINT64_MAX}});
Intervals<uint64_t> other = make_intervals({{0, 0, 10}, {10, 1, 30}, {30, 0, UINT64_MAX}});
test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 5},
{5, 1, 10},
{10, 2, 15},
{15, 1, 25},
{25, 2, 30},
{30, 1, 35},
{35, 0, UINT64_MAX}});
};
SECTION("merge disjoint before")
{
Intervals<uint64_t> test =
make_intervals({{0, 0, 50}, {50, 1, 60}, {60, 0, 70}, {70, 1, 80}, {80, 0, UINT64_MAX}});
Intervals<uint64_t> other =
make_intervals({{0, 0, 10}, {10, 1, 20}, {20, 0, 30}, {30, 1, 40}, {40, 0, UINT64_MAX}});
test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 10},
{10, 1, 20},
{20, 0, 30},
{30, 1, 40},
{40, 0, 50},
{50, 1, 60},
{60, 0, 70},
{70, 1, 80},
{80, 0, UINT64_MAX}});
};
SECTION("merge disjoint after")
{
Intervals<uint64_t> test =
make_intervals({{0, 0, 10}, {10, 1, 20}, {20, 0, 30}, {30, 1, 40}, {40, 0, UINT64_MAX}});
Intervals<uint64_t> other =
make_intervals({{0, 0, 50}, {50, 1, 60}, {60, 0, 70}, {70, 1, 80}, {80, 0, UINT64_MAX}});
test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 10},
{10, 1, 20},
{20, 0, 30},
{30, 1, 40},
{40, 0, 50},
{50, 1, 60},
{60, 0, 70},
{70, 1, 80},
{80, 0, UINT64_MAX}});
};
SECTION("merge disjoint interleaved")
{
Intervals<uint64_t> test =
make_intervals({{0, 0, 10}, {10, 1, 20}, {20, 0, 50}, {50, 1, 60}, {60, 0, UINT64_MAX}});
Intervals<uint64_t> other =
make_intervals({{0, 0, 30}, {30, 1, 40}, {40, 0, 70}, {70, 1, 80}, {80, 0, UINT64_MAX}});
test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 10},
{10, 1, 20},
{20, 0, 30},
{30, 1, 40},
{40, 0, 50},
{50, 1, 60},
{60, 0, 70},
{70, 1, 80},
{80, 0, UINT64_MAX}});
};
SECTION("merge disjoint interleaved touching")
{
Intervals<uint64_t> test =
make_intervals({{0, 0, 10}, {10, 1, 20}, {20, 0, 30}, {30, 1, 40}, {40, 0, UINT64_MAX}});
Intervals<uint64_t> other =
make_intervals({{0, 0, 20}, {20, 1, 30}, {30, 0, 40}, {40, 1, 50}, {50, 0, UINT64_MAX}});
test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; });
check_intervals(test, {{0, 0, 10}, {10, 1, 50}, {50, 0, UINT64_MAX}});
};
};
};
#endif // ENABLED(ENABLE_UNIT_TESTS)
+1
View File
@@ -410,6 +410,7 @@
<ClCompile Include="common\threading_tests.cpp" />
<ClCompile Include="core\core.cpp" />
<ClCompile Include="core\image_viewer.cpp" />
<ClCompile Include="core\intervals_tests.cpp" />
<ClCompile Include="core\plugins.cpp" />
<ClCompile Include="core\precompiled.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
+3
View File
@@ -824,6 +824,9 @@
<ClCompile Include="common\threading_tests.cpp">
<Filter>Common</Filter>
</ClCompile>
<ClCompile Include="core\intervals_tests.cpp">
<Filter>Core</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="os\win32\comexport.def">