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:
parent
4e106a76e8
commit
453e57bade
2 changed files with 571 additions and 487 deletions
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue