1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Add erase() and drop() to Ranges and RangedValues

This commit is contained in:
attila 2024-03-31 12:26:21 +02:00
parent 4e106a76e8
commit 453e57bade
2 changed files with 571 additions and 487 deletions

View file

@ -195,9 +195,12 @@ public:
auto ops = ranges.split (47);
expectEquals ((int64) ops.size(), (int64) 2, "");
expect (std::get_if<Ranges::Ops::Changed> (&ops[0]) != nullptr);
expect (std::get_if<Ranges::Ops::Inserted> (&ops[1]) != nullptr);
expectEquals ((int64) ops.size(), (int64) 1, "");
const auto op0 = std::get_if<Ranges::Ops::Split> (&ops[0]);
expect (op0 != nullptr);
if (op0 != nullptr)
expectEquals ((int) op0->index, 0, "The 0th element should be split");
expectEquals ((int64) ranges.size(), (int64) 3, "");
expectRange (ranges.get (0), { 0, 47 });
@ -252,6 +255,157 @@ public:
expect (ranges.isEmpty());
expect (ops.empty());
}
const auto getTestRanges = []
{
Ranges ranges;
ranges.set ({ 0, 48 });
ranges.set ({ 48, 49 });
ranges.set ({ 55, 94 });
ranges.set ({ 94, 127 });
return ranges;
};
beginTest ("Ranges::eraseFrom() - erasing beyond all ranges has no effect");
{
auto ranges = getTestRanges();
ranges.eraseFrom (ranges.get (ranges.size() - 1).getEnd() + 5);
expectEquals ((int64) ranges.size(), (int64) 4, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 48, 49 });
expectRange (ranges.get (2), { 55, 94 });
expectRange (ranges.get (3), { 94, 127 });
}
beginTest ("Ranges::eraseFrom() - erasing modifies the range that encloses the starting index");
{
auto ranges = getTestRanges();
ranges.eraseFrom (122);
expectEquals ((int64) ranges.size(), (int64) 4, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 48, 49 });
expectRange (ranges.get (2), { 55, 94 });
expectRange (ranges.get (3), { 94, 122 });
}
beginTest ("Ranges::eraseFrom() - ranges starting after the erased index are deleted entirely");
{
auto ranges = getTestRanges();
ranges.eraseFrom (60);
expectEquals ((int64) ranges.size(), (int64) 3, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 48, 49 });
expectRange (ranges.get (2), { 55, 60 });
}
beginTest ("Ranges::eraseFrom() - erasing from a location outside any ranges will still drop subsequent ranges");
{
auto ranges = getTestRanges();
ranges.eraseFrom (51);
expectEquals ((int64) ranges.size(), (int64) 2, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 48, 49 });
}
beginTest ("Ranges::erase() - erasing a zero length range has no effect");
{
auto ranges = getTestRanges();
ranges.erase ({ 30, 30 });
expectEquals ((int64) ranges.size(), (int64) 4, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 48, 49 });
expectRange (ranges.get (2), { 55, 94 });
expectRange (ranges.get (3), { 94, 127 });
}
beginTest ("Ranges::erase() - erasing inside a range splits that range");
{
auto ranges = getTestRanges();
ranges.erase ({ 30, 31 });
expectEquals ((int64) ranges.size(), (int64) 5, "");
expectRange (ranges.get (0), { 0, 30 });
expectRange (ranges.get (1), { 31, 48 });
expectRange (ranges.get (2), { 48, 49 });
expectRange (ranges.get (3), { 55, 94 });
expectRange (ranges.get (4), { 94, 127 });
}
beginTest ("Ranges::erase() - erasing a range that completely overlaps existing ranges erases those");
{
auto ranges = getTestRanges();
ranges.erase ({ 30, 70 });
expectEquals ((int64) ranges.size(), (int64) 3, "");
expectRange (ranges.get (0), { 0, 30 });
expectRange (ranges.get (1), { 70, 94 });
expectRange (ranges.get (2), { 94, 127 });
}
beginTest ("Ranges::erase() - erasing a range that no previous ranges covered has no effect");
{
auto ranges = getTestRanges();
ranges.erase ({ 51, 53 });
expectEquals ((int64) ranges.size(), (int64) 4, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 48, 49 });
expectRange (ranges.get (2), { 55, 94 });
expectRange (ranges.get (3), { 94, 127 });
}
beginTest ("Ranges::erase() - erasing over a range beyond all limits clears all ranges");
{
auto ranges = getTestRanges();
ranges.erase ({ -1000, 1000 });
expect (ranges.isEmpty());
}
beginTest ("Ranges::drop() - dropping a range shifts all following ranges downward");
{
auto ranges = getTestRanges();
ranges.drop ({ 48, 49 });
expectEquals ((int64) ranges.size(), (int64) 3, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 54, 93 });
expectRange (ranges.get (2), { 93, 126 });
}
beginTest ("Ranges::drop() - dropping a range shifts all following ranges downward even if no range covered the dropped range previously");
{
auto ranges = getTestRanges();
ranges.drop ({ 51, 53 });
expectEquals ((int64) ranges.size(), (int64) 4, "");
expectRange (ranges.get (0), { 0, 48 });
expectRange (ranges.get (1), { 48, 49 });
expectRange (ranges.get (2), { 53, 92 });
expectRange (ranges.get (3), { 92, 125 });
}
beginTest ("Ranges::drop() - dropping a range covering all other ranges empties the collection");
{
auto ranges = getTestRanges();
ranges.drop ({ -1000, 1000 });
expect (ranges.isEmpty());
}
}
};
@ -481,6 +635,79 @@ public:
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
}
}
const auto createRangedValuesObjectForErase = [&]
{
RangedValues<char> rangedValues;
rangedValues.set ({ 0, 10 }, 'a');
rangedValues.set ({ 11, 20 }, 'b');
rangedValues.set ({ 23, 30 }, 'c');
rangedValues.set ({ 35, 45 }, 'c');
rangedValues.set ({ 45, 60 }, 'd');
return rangedValues;
};
beginTest ("RangedValues::erase() - erase will not shift subsequent ranges downward");
{
auto rangedValues = createRangedValuesObjectForErase();
rangedValues.erase ({ 15, 16 });
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 11, 15 }, 'b');
expectRangedValuesItem (rangedValues.getItem (2), { 16, 20 }, 'b');
expectRangedValuesItem (rangedValues.getItem (3), { 23, 30 }, 'c');
expectRangedValuesItem (rangedValues.getItem (4), { 35, 45 }, 'c');
expectRangedValuesItem (rangedValues.getItem (5), { 45, 60 }, 'd');
}
beginTest ("RangedValues::drop() - drop shifts ranges downward on the right side");
{
auto rangedValues = createRangedValuesObjectForErase();
rangedValues.drop<MergeEqualItems::no> ({ 15, 16 });
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 11, 15 }, 'b');
expectRangedValuesItem (rangedValues.getItem (2), { 15, 19 }, 'b');
expectRangedValuesItem (rangedValues.getItem (3), { 22, 29 }, 'c');
expectRangedValuesItem (rangedValues.getItem (4), { 34, 44 }, 'c');
expectRangedValuesItem (rangedValues.getItem (5), { 44, 59 }, 'd');
}
beginTest ("RangedValues::drop() - drop can be used to merge equal values");
{
auto rangedValues = createRangedValuesObjectForErase();
rangedValues.drop<MergeEqualItems::yes> ({ 15, 16 });
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
expectRangedValuesItem (rangedValues.getItem (1), { 11, 19 }, 'b');
expectRangedValuesItem (rangedValues.getItem (2), { 22, 29 }, 'c');
expectRangedValuesItem (rangedValues.getItem (3), { 34, 44 }, 'c');
expectRangedValuesItem (rangedValues.getItem (4), { 44, 59 }, 'd');
}
beginTest ("RangedValues::drop() - the merge happens at the seam caused by the drop and does not extend beyond");
{
auto rangedValues = createRangedValuesObjectForErase();
rangedValues.set<MergeEqualItems::no> ({ 20, 30 }, 'b');
rangedValues.drop<MergeEqualItems::yes> ({ 15, 16 });
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
// These two items are not merged, even though they form a contiguous range, because
// they were disjoint before the drop and they don't touch each other at the drop
// seam of 15.
expectRangedValuesItem (rangedValues.getItem (1), { 11, 19 }, 'b');
expectRangedValuesItem (rangedValues.getItem (2), { 19, 29 }, 'b');
expectRangedValuesItem (rangedValues.getItem (3), { 34, 44 }, 'c');
expectRangedValuesItem (rangedValues.getItem (4), { 44, 59 }, 'd');
}
}
};

View file

@ -45,19 +45,15 @@ inline std::optional<Range<int64>> getRangeIntersectionWith (Range<int64> r1, Ra
return Range<int64> { intersection };
}
inline size_t clampCast (int64 v)
{
return v < 0 ? 0 : (size_t) v;
}
/* Used in Ranges::getAffectedElements(). Yes means that if a change occurs to range i, we also
want ranges i - 1 and i + 1 included in the affected elements.
/* This is to get rid of the warning where advance isn't of type difference_type.
*/
enum class IncludeAdjacentRanges
template <typename Iterator, typename Value>
auto iteratorWithAdvance (Iterator&& it, Value advance)
{
no,
yes
};
auto outIt = std::move (it);
std::advance (outIt, static_cast<typename std::iterator_traits<Iterator>::difference_type> (advance));
return outIt;
}
struct Ranges final
{
@ -65,16 +61,53 @@ struct Ranges final
{
Ops() = delete;
struct Erased { Range<size_t> range; };
struct Inserted { size_t index; };
struct Reinserted { size_t index; };
struct Changed { size_t index; };
struct New
{
New() = delete;
size_t index;
};
struct Split
{
Split() = delete;
size_t index;
};
struct Erase
{
Erase() = delete;
Range<size_t> range;
};
struct Change
{
Change() = delete;
size_t index;
};
};
using Op = std::variant<Ops::Erased, Ops::Inserted, Ops::Reinserted, Ops::Changed>;
using Op = std::variant<Ops::New, Ops::Split, Ops::Erase, Ops::Change>;
using Operations = std::vector<Op>;
static auto withOperationsFrom (const Operations& ops, const Operations& newOps)
{
auto result = ops;
result.insert (result.end(), newOps.begin(), newOps.end());
return result;
}
static auto withOperationsFrom (const Operations& ops, Op newOp)
{
auto result = ops;
result.insert (result.end(), newOp);
return result;
}
Ranges() = default;
explicit Ranges (std::vector<Range<int64>> rangesIn)
@ -88,11 +121,182 @@ struct Ranges final
#endif
}
//==============================================================================
// Basic operations
Operations split (int64 i)
{
Operations ops;
const auto elemIndex = getIndexForEnclosingRange (i);
if (! elemIndex.has_value())
return {};
auto& elem = ranges[*elemIndex];
if (elem.getStart() == i)
return {};
ops = withOperationsFrom (ops, Ops::Split { *elemIndex });
const auto oldLength = elem.getLength();
elem.setEnd (i);
ranges.insert (iteratorWithAdvance (ranges.begin(), *elemIndex + 1),
{ i, i + oldLength - elem.getLength() });
return ops;
}
Operations erase (Range<int64> r)
{
if (r.isEmpty())
return {};
Operations ops;
for (auto i : { r.getStart(), r.getEnd() })
ops = withOperationsFrom (ops, split (i));
const auto firstToDelete = std::lower_bound (ranges.begin(),
ranges.end(),
r.getStart(),
[] (auto& elem, auto& value)
{ return elem.getStart() < value; });
const auto beyondLastToDelete = std::lower_bound (firstToDelete,
ranges.end(),
r.getEnd(),
[] (auto& elem, auto& value)
{ return elem.getStart() < value; });
if (firstToDelete != ranges.end())
ops = withOperationsFrom (ops, Ops::Erase { { getIndex (firstToDelete), getIndex (beyondLastToDelete) } });
ranges.erase (firstToDelete, beyondLastToDelete);
return ops;
}
Operations drop (Range<int64> r)
{
auto ops = erase (r);
ops = withOperationsFrom (ops, shift (r.getEnd(), -r.getLength()));
return ops;
}
/* Shift all ranges starting at or beyond the specified from parameter, by the specified amount.
*/
Operations shift (int64 from, int64 amount)
{
if (amount == 0)
return {};
const auto shiftStartingFrom = std::lower_bound (ranges.begin(),
ranges.end(),
from,
[] (auto& elem, auto& value)
{ return elem.getStart() < value; });
Operations ops;
for (auto it = shiftStartingFrom; it < ranges.end(); ++it)
{
*it += amount;
ops = withOperationsFrom (ops, Ops::Change { getIndex (it) });
}
return ops;
}
Operations set (Range<int64> newRange)
{
if (newRange.isEmpty())
return {};
Operations ops;
ops = withOperationsFrom (ops, erase (newRange));
const auto insertBefore = std::lower_bound (ranges.begin(),
ranges.end(),
newRange.getStart(),
[] (auto& elem, auto& value)
{ return elem.getStart() < value; });
ops = withOperationsFrom (ops, Ops::New { getIndex (insertBefore) });
ranges.insert (insertBefore, newRange);
return ops;
}
Operations insert (Range<int64> newRange)
{
if (newRange.isEmpty())
return {};
Operations ops;
ops = withOperationsFrom (ops, split (newRange.getStart()));
ops = withOperationsFrom (ops, shift (newRange.getStart(), newRange.getLength()));
const auto insertBefore = std::lower_bound (ranges.begin(),
ranges.end(),
newRange.getStart(),
[] (auto& elem, auto& value)
{ return elem.getStart() < value; });
const auto insertBeforeIndex = getIndex (insertBefore);
ranges.insert (insertBefore, newRange);
ops = withOperationsFrom (ops, Ops::New { insertBeforeIndex });
return ops;
}
//==============================================================================
// Convenience functions
void clear()
{
ranges.clear();
}
Operations eraseFrom (int64 i)
{
if (ranges.empty())
return {};
return erase ({ i, ranges.back().getEnd() });
}
/* Merges neighbouring ranges backwards if they form a contiguous range.
*/
Operations mergeBack (size_t i)
{
jassert (isPositiveAndBelow (i, ranges.size()));
if (i == 0 || i >= ranges.size())
return {};
const auto start = i - 1;
const auto end = i;
if (ranges[start].getEnd() != ranges[end].getStart())
return {};
Operations ops;
ops = withOperationsFrom (ops, Ops::Change { start });
ranges[start].setEnd (ranges[end].getEnd());
ops = withOperationsFrom (ops, Ops::Erase { { end, end + 1 } });
ranges.erase (iteratorWithAdvance (ranges.begin(), end),
iteratorWithAdvance (ranges.begin(), end + 1));
return ops;
}
/* Returns the ranges that have an intersection with the provided range. */
std::vector<Range<int64>> getIntersectionsWith (Range<int64> r) const
{
@ -127,232 +331,6 @@ struct Ranges final
return result;
}
Operations set (Range<int64> newRange)
{
if (newRange.isEmpty())
return {};
Operations ops;
const auto firstStartingBeforeNewRange = [&]
{
auto it = std::lower_bound (ranges.begin(),
ranges.end(),
newRange,
[] (auto& elem, auto& value)
{ return elem.getStart() < value.getStart(); });
if (! ranges.empty() && it != ranges.begin())
return it - 1;
return ranges.end();
}();
const auto getFirstEndingAfterNewRange = [&]
{
auto it = std::lower_bound (ranges.begin(),
ranges.end(),
newRange,
[] (auto& elem, auto& value)
{ return elem.getEnd() <= value.getEnd(); });
return it;
};
auto firstEndingAfterNewRange = getFirstEndingAfterNewRange();
// This variable helps with handling the corner case, when the newValue to be set lies
// entirely inside an existing range. The set() operation in this case is expected to split
// the existing range.
auto remainderRangeBecauseOfSplit = [&]() -> std::optional<Range<int64>>
{
if (firstStartingBeforeNewRange == ranges.end() || firstStartingBeforeNewRange != firstEndingAfterNewRange)
return std::nullopt;
return Range<int64> { std::max (newRange.getEnd(), firstEndingAfterNewRange->getStart()),
firstEndingAfterNewRange->getEnd() };
}();
if (firstStartingBeforeNewRange != ranges.end())
{
const auto oldEnd = firstStartingBeforeNewRange->getEnd();
const auto newEnd = std::min (oldEnd, newRange.getStart());
firstStartingBeforeNewRange->setEnd (newEnd);
if (oldEnd != newEnd)
ops.push_back (Ops::Changed { getIndex (firstStartingBeforeNewRange) });
}
if (! remainderRangeBecauseOfSplit.has_value()
&& firstEndingAfterNewRange != ranges.end())
{
const auto oldStart = firstEndingAfterNewRange->getStart();
const auto newStart = std::max (firstEndingAfterNewRange->getStart(), newRange.getEnd());
firstEndingAfterNewRange->setStart (newStart);
if (oldStart != newStart)
ops.push_back (Ops::Changed { getIndex (firstStartingBeforeNewRange) });
}
const auto firstToDelete = std::lower_bound (ranges.begin(),
ranges.end(),
newRange,
[] (auto& elem, auto& value)
{ return elem.getStart() < value.getStart(); });
firstEndingAfterNewRange = getFirstEndingAfterNewRange();
if (firstToDelete != ranges.end() && firstToDelete != firstEndingAfterNewRange)
ops.push_back (Ops::Erased { { getIndex (firstToDelete),
getIndex (firstEndingAfterNewRange) } });
const auto beyondLastRemoved = ranges.erase (firstToDelete, firstEndingAfterNewRange);
const auto insertIt = ranges.insert (beyondLastRemoved, newRange);
ops.push_back (Ops::Inserted { getIndex (insertIt) });
if (remainderRangeBecauseOfSplit.has_value())
{
const auto it = ranges.insert (insertIt + 1, *remainderRangeBecauseOfSplit);
ops.push_back (Ops::Reinserted { getIndex (it) });
}
return ops;
}
size_t getIndex (std::vector<Range<int64>>::const_iterator it) const
{
return (size_t) std::distance (ranges.cbegin(), it);
}
Operations insert (Range<int64> newRange)
{
if (newRange.isEmpty())
return {};
Operations ops;
auto it = std::lower_bound (ranges.begin(),
ranges.end(),
newRange,
[] (auto& elem, auto& value)
{ return elem.getEnd() <= value.getStart(); });
if (it != ranges.end() && it->getStart() < newRange.getStart())
{
const auto oldEnd = it->getEnd();
it->setEnd (newRange.getStart());
ops.push_back (Ops::Changed { getIndex (it) });
Range<int64> newItems[] = { newRange,
{ newRange.getEnd(), newRange.getEnd() + oldEnd - it->getEnd() } };
it = ranges.insert (it + 1, std::begin (newItems), std::end (newItems));
ops.push_back (Ops::Inserted { getIndex (it) });
ops.push_back (Ops::Inserted { getIndex (it + 1) });
++it;
}
else
{
it = ranges.insert (it, newRange);
ops.push_back (Ops::Inserted { getIndex (it) });
}
for (auto& range : makeRange (std::next (it), ranges.end()))
range += newRange.getLength();
return ops;
}
Operations split (int64 i)
{
Operations ops;
const auto elemIndex = getIndexForEnclosingRange (i);
if (! elemIndex.has_value())
return ops;
auto& elem = ranges[*elemIndex];
if (elem.getStart() == i)
return ops;
const auto oldLength = elem.getLength();
elem.setEnd (i);
ops.push_back (Ops::Changed { *elemIndex });
auto setOps = set (Range<int64> { Range<int64> { i, i + oldLength - elem.getLength() } });
ops.insert (ops.end(), setOps.begin(), setOps.end());
return ops;
}
Operations eraseFrom (int64 i)
{
Operations ops;
const auto elemIndex = getIndexForEnclosingRange (i);
if (elemIndex.has_value())
{
ranges[*elemIndex].setEnd (i);
ops.push_back (Ops::Changed { *elemIndex });
}
const auto firstToDelete = std::lower_bound (ranges.begin(),
ranges.end(),
i,
[] (auto& elem, auto& value)
{ return elem.getStart() < value; });
if (firstToDelete != ranges.end())
ops.push_back (Ops::Erased { { getIndex (firstToDelete), getIndex (ranges.end()) } });
ranges.erase (firstToDelete, ranges.end());
return ops;
}
Operations merge (Range<size_t> elements)
{
jassert (elements.getEnd() <= ranges.size());
Operations ops;
for (auto i = elements.getStart(), j = i + 1; j < elements.getEnd(); ++j)
{
const auto inLastIteration = j == elements.getEnd() - 1;
if (inLastIteration || ranges[j].getEnd() != ranges[j + 1].getStart())
{
ranges[i].setEnd (ranges[j].getEnd());
ranges.erase (ranges.begin() + (int) i + 1, ranges.begin() + (int) j + 1);
// I like that the merging algorithm works regardless of i being equal to j, so
// I didn't add this if earlier. No need to handle a corner case where there is
// none. However, I don't want to omit events if nothing changed.
if (i != j)
{
ops.push_back (Ops::Changed { i });
ops.push_back (Ops::Erased { { i + 1, j + 1 } });
}
const auto numItemsDeleted = j - i;
elements.setEnd (elements.getEnd() - numItemsDeleted);
i = j + 1 - numItemsDeleted;
j = i;
}
}
return ops;
}
std::optional<size_t> getIndexForEnclosingRange (int64 positionInTextRange) const
{
auto it = std::lower_bound (ranges.begin(),
@ -366,11 +344,7 @@ struct Ranges final
return std::nullopt;
}
Range<int64> get (size_t rangeIndex) const
{
return ranges[rangeIndex];
}
//==============================================================================
size_t size() const
{
return ranges.size();
@ -381,100 +355,12 @@ struct Ranges final
return ranges.empty();
}
auto& operator[] (size_t rangeIndex)
Range<int64> get (size_t rangeIndex) const
{
return ranges[rangeIndex];
}
auto& operator[] (size_t rangeIndex) const
{
return ranges[rangeIndex];
}
[[nodiscard]] Range<size_t> getAffectedElements (const Ranges::Operations& ops,
IncludeAdjacentRanges includeAdjacent = IncludeAdjacentRanges::yes) const
{
if (ops.empty())
return {};
int64 start = std::numeric_limits<int64>::max();
int64 end = std::numeric_limits<int64>::min();
const auto startIsValid = [&start] { return start != std::numeric_limits<int64>::max(); };
const int64 includeAdjacentOffset = includeAdjacent == IncludeAdjacentRanges::yes ? 1 : 0;
const auto adjacentOffsetFor = [includeAdjacent, this] (size_t index, int64 offset) -> int64
{
if (includeAdjacent == IncludeAdjacentRanges::no)
return 0;
const auto adjacentRangeIndex = (int64) index + offset;
if (! isPositiveAndBelow (adjacentRangeIndex, (int64) ranges.size()))
return 0;
if (offset < 0)
return ranges[(size_t) adjacentRangeIndex].getEnd() == ranges[index].getStart() ? offset : 0;
return ranges[index].getEnd() == ranges[(size_t) adjacentRangeIndex].getStart() ? offset : 0;
};
for (auto& op : ops)
{
if (auto* inserted = std::get_if<Ranges::Ops::Inserted> (&op))
{
if (startIsValid() && (int64) inserted->index < start)
start += 1;
if ((int64) inserted->index < end)
end += 1;
start = std::min (start, (int64) inserted->index + adjacentOffsetFor (inserted->index, -1));
end = std::max (end, (int64) inserted->index + adjacentOffsetFor (inserted->index, 1) + 1);
}
else if (auto* reinserted = std::get_if<Ranges::Ops::Reinserted> (&op))
{
if (startIsValid() && (int64) reinserted->index < start)
start += 1;
if ((int64) reinserted->index < end)
end += 1;
start = std::min (start, (int64) reinserted->index + adjacentOffsetFor (reinserted->index, -1));
end = std::max (end, (int64) reinserted->index + adjacentOffsetFor (reinserted->index, 1) + 1);
}
else if (auto* erased = std::get_if<Ranges::Ops::Erased> (&op))
{
const auto eraseStart = (int64) erased->range.getStart();
if (startIsValid() && eraseStart < start)
start -= (int64) erased->range.getLength();
if (eraseStart < end - 1)
end -= (int64) erased->range.getLength();
}
else if (auto* changed = std::get_if<Ranges::Ops::Changed> (&op))
{
start = std::min (start, (int64) changed->index - includeAdjacentOffset);
end = std::max (end, (int64) changed->index + includeAdjacentOffset);
}
}
return { clampCast (start), std::min (clampCast (end), ranges.size()) };
}
[[nodiscard]] Range<int64> getSpannedRange (Range<size_t> r) const
{
auto start = (int64) r.getStart();
auto end = (int64) r.getEnd();
jassert (start < (int64) ranges.size() && end <= (int64) ranges.size());
return { ranges[(size_t) start].getStart(),
ranges[(size_t) std::max (start, end - 1)].getEnd() };
}
//==============================================================================
auto begin() const
{
return ranges.cbegin();
@ -496,6 +382,11 @@ struct Ranges final
}
private:
size_t getIndex (std::vector<Range<int64>>::const_iterator it) const
{
return (size_t) std::distance (ranges.cbegin(), it);
}
std::vector<Range<int64>> ranges;
};
@ -512,15 +403,6 @@ constexpr auto hasEqualityOperator = false;
template <typename T>
constexpr auto hasEqualityOperator<T, std::void_t<decltype (std::declval<T>() == std::declval<T>())>> = true;
/* This is to get rid of the warning where advance isn't of type difference_type.
*/
template <typename Iterator, typename Value>
auto iteratorWithAdvance (Iterator&& it, Value advance)
{
auto outIt = std::move (it);
std::advance (outIt, static_cast<typename std::iterator_traits<Iterator>::difference_type> (advance));
return outIt;
}
/* Data structure for storing values associated with non-overlapping ranges.
@ -632,6 +514,7 @@ public:
template <typename X, typename Y>
static auto makeIterator (X* x, Y y) { return RangedValuesIterator<X> (x, y); }
//==============================================================================
auto begin()
{
return makeIterator (this, ranges.cbegin());
@ -674,78 +557,23 @@ public:
const T& value;
};
void clear()
{
ranges.clear();
values.clear();
}
//==============================================================================
// Basic operations
template <MergeEqualItems mergeEquals = MergeEqualItems::yes>
auto set (Range<int64> r, T v)
{
static_assert (mergeEquals == MergeEqualItems::no || canMergeEqualItems,
"You can't use MergeEqualItems::yes if your type doesn't have operator==.");
auto ops = ranges.set (r);
Ranges::Operations ops;
// We use the copy constructor to avoid any dependency or restriction on the types default
// constructor.
T changedValue = v;
#if JUCE_DEBUG
int numInsert{};
#endif
for (auto& op : ops)
{
if (auto* changed = std::get_if<Ranges::Ops::Changed> (&op))
{
if (changed->index < values.size())
changedValue = values[changed->index];
}
else if (auto* inserted = std::get_if<Ranges::Ops::Inserted> (&op))
{
#if JUCE_DEBUG
++numInsert;
#endif
values.insert (iteratorWithAdvance (values.begin(), inserted->index), v);
}
else if (auto* reinserted = std::get_if<Ranges::Ops::Reinserted> (&op))
{
values.insert (iteratorWithAdvance (values.begin(), reinserted->index), changedValue);
}
else if (auto* erased = std::get_if<Ranges::Ops::Erased> (&op))
{
values.erase (iteratorWithAdvance (values.begin(), erased->range.getStart()),
iteratorWithAdvance (values.begin(), erased->range.getEnd()));
}
}
#if JUCE_DEBUG
jassert (numInsert <= 1);
#endif
ops = Ranges::withOperationsFrom (ops, ranges.set (r));
applyOperations (ops, std::move (v));
if constexpr (mergeEquals == MergeEqualItems::yes)
{
const auto mergeOps = mergeEqualItems (ranges.getAffectedElements (ops));
ops.insert (ops.begin(), mergeOps.begin(), mergeOps.end());
}
return ops;
}
/** Create a RangedValues object from non-overlapping ranges. */
template<MergeEqualItems mergeEquals, typename Iterable>
auto setForEach (Iterable begin, Iterable end)
{
Ranges::Operations ops;
for (auto it = begin; it != end; ++it)
{
const auto& [range, value] = *it;
const auto subOps = set<mergeEquals> (range, value);
ops.insert (ops.end(), subOps.begin(), subOps.end());
ops = Ranges::withOperationsFrom (ops, mergeEqualItems (r.getStart()));
ops = Ranges::withOperationsFrom (ops, mergeEqualItems (r.getEnd()));
}
return ops;
@ -758,103 +586,68 @@ public:
"You can't use MergeEqualItems::yes if your type doesn't have operator==.");
auto ops = ranges.insert (r);
std::optional<T> oldValue;
auto newValueInserted = false;
for (auto& op : ops)
{
if (auto* changed = std::get_if<Ranges::Ops::Changed> (&op))
{
oldValue = values[changed->index];
}
else if (auto* inserted = std::get_if<Ranges::Ops::Inserted> (&op))
{
if (! std::exchange (newValueInserted, true))
{
values.insert (values.begin() + (int) inserted->index, v);
}
else
{
jassert (oldValue.has_value());
values.insert (values.begin() + (int) inserted->index, *oldValue);
oldValue.reset();
}
}
else
jassertfalse;
}
applyOperations (ops, std::move (v));
if constexpr (mergeEquals == MergeEqualItems::yes)
{
const auto mergeOps = mergeEqualItems (ranges.getAffectedElements (ops));
ops.insert (ops.begin(), mergeOps.begin(), mergeOps.end());
ops = Ranges::withOperationsFrom (ops, mergeEqualItems (r.getStart()));
ops = Ranges::withOperationsFrom (ops, mergeEqualItems (r.getEnd()));
}
return ops;
}
auto eraseFrom (int64 i)
// erase will always cause a discontinuity and thus, there is no opportunity to merge
auto erase (Range<int64> r)
{
auto ops = ranges.eraseFrom (i);
for (auto& op : ops)
{
if (auto* erased = std::get_if<Ranges::Ops::Erased> (&op))
{
values.erase (iteratorWithAdvance (values.begin(), erased->range.getStart()),
iteratorWithAdvance (values.begin(), erased->range.getEnd()));
}
}
auto ops = ranges.erase (r);
applyOperations (ops);
return ops;
}
Range<size_t> getEqualElements (Range<size_t> rangesToCheck)
// drop moves subsequent ranges downward, and can end up in these ranges forming a contiguous
// range with the ones on the left side of the drop. Hence, it makes sense to ask if we want
// merging behaviour.
template <MergeEqualItems mergeEquals = MergeEqualItems::yes>
auto drop (Range<int64> r)
{
if constexpr (canMergeEqualItems)
{
std::optional<size_t> start;
size_t end{};
static_assert (mergeEquals == MergeEqualItems::no || canMergeEqualItems,
"You can't use MergeEqualItems::yes if your type doesn't have operator==.");
for (auto i = rangesToCheck.getStart(); i < rangesToCheck.getEnd() - 1; ++i)
{
if (exactlyEqual (values[i], values[i + 1]))
{
if (! start.has_value())
start = i;
auto ops = ranges.drop (r);
applyOperations (ops);
end = i + 2;
}
else
{
if (start.has_value())
break;
}
}
if constexpr (mergeEquals == MergeEqualItems::yes)
ops = Ranges::withOperationsFrom (ops, mergeEqualItems (r.getStart()));
return start.has_value() ? Range { *start, end }
: Range { rangesToCheck.getStart(),
rangesToCheck.getStart() };
}
else
{
return ops;
}
//==============================================================================
void clear()
{
ranges.clear();
values.clear();
}
Ranges::Operations eraseFrom (int64 i)
{
if (ranges.isEmpty())
return {};
}
return erase ({ i, ranges.get (ranges.size() - 1).getEnd() });
}
auto mergeEqualItems (Range<size_t> elements)
/** Create a RangedValues object from non-overlapping ranges. */
template<MergeEqualItems mergeEquals, typename Iterable>
auto setForEach (Iterable begin, Iterable end)
{
if (elements.isEmpty())
return Ranges::Operations{};
Ranges::Operations ops;
auto ops = ranges.merge (getEqualElements (elements));
for (const auto& op : ops)
for (auto it = begin; it != end; ++it)
{
if (auto* erased = std::get_if<Ranges::Ops::Erased> (&op))
{
values.erase (values.begin() + (int) erased->range.getStart(),
values.begin() + (int) erased->range.getEnd());
}
const auto& [range, value] = *it;
ops = Ranges::withOperationsFrom (ops, set<mergeEquals> (range, value));
}
return ops;
@ -941,6 +734,70 @@ public:
}
private:
Ranges::Operations mergeEqualItems (int64 i)
{
const auto endOpt = ranges.getIndexForEnclosingRange (i);
if (! endOpt.has_value() || *endOpt == 0)
return {};
const auto end = *endOpt;
const auto start = end - 1;
if (! exactlyEqual (values[start], values[end]))
return {};
const auto ops = ranges.mergeBack (end);
applyOperations (ops);
return ops;
}
void applyOperation (const Ranges::Op& op)
{
if (auto* split = std::get_if<Ranges::Ops::Split> (&op))
{
values.insert (iteratorWithAdvance (values.begin(), split->index), values[split->index]);
return;
}
if (auto* erase = std::get_if<Ranges::Ops::Erase> (&op))
{
values.erase (iteratorWithAdvance (values.begin(), erase->range.getStart()),
iteratorWithAdvance (values.begin(), erase->range.getEnd()));
return;
}
// This function can't handle New.
jassert (std::get_if<Ranges::Ops::New> (&op) == nullptr);
// This entire type doesn't have to do anything to handle Ranges::Ops::Change.
}
void applyOperation (const Ranges::Op& op, T v)
{
if (auto* newOp = std::get_if<Ranges::Ops::New> (&op))
{
values.insert (iteratorWithAdvance (values.begin(), newOp->index), std::move (v));
}
else
{
applyOperation (op);
}
}
void applyOperations (const Ranges::Operations& ops)
{
for (const auto& op : ops)
applyOperation (op);
}
void applyOperations (const Ranges::Operations& ops, T v)
{
for (const auto& op : ops)
applyOperation (op, v);
}
Ranges ranges;
std::vector<T> values;
};