/* SPLIT-SPLICE.hpp - Algorithm to integrate a new interval into an axis-segmentation. Copyright (C) Lumiera.org 2023, Hermann Vosseler This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** @file splite-splice.hpp ** Generic algorithm to splice a new segment into a seamless segmentation of intervals. ** Here _"segmentation"_ denotes a partitioning of an ordered axis into a seamless sequence ** of intervals (here called "segment"). The axis is based on some _ordering type,_ like e.g. ** `int` (natural numbers) or lib::time::Time values, and the axis is assumed to cover a complete ** domain. The intervals / segments are defined by start and end point, where the start point ** is inclusive, yet the end point is exclusive (the next ordered point after the interval). ** ** The purpose of the algorithm is to splice in a new segment (interval) at the proper location, ** based on the ordering, such as to retain the ordering and the seamless coverage of the complete ** axis domain. Since there are a lot of possible arrangements of intervals relative to one another, ** this splicing operation may necessitate to adjust _predecessor_ or _successor_ segments, and ** possibly even to insert additional segments to fill a gap, thereby effectively _splitting_ ** some segment existing on the axis. ** ** # Specification ** ** The implementation relies upon the following assumptions: ** \par assumptions ** - there is an ordering type `ORD`, which is totally ordered, ** has value semantics and can be clone-initialised (can be immutable) ** - Segments are represented as custom data type and the Segmentation (axis) ** is implemented as a random-access list-like datatype with the ability to insert ** and delete ranges of elements, defined as pairs of _iterators_ ** - the algorithm works solely on this _iterator type_ `POS`, which must be assignable, ** can be compared for equality (especially with the "end" iterator) and can be _incremented._ ** ** All further bindings to the actual implementation are abstracted as _binding functors_ ** - `START`: when invoked on a POS (iterator), get the ORD value of the segment's start point ** - `AFTER`: likewise get the end point (exclusive) of the given segment ** - `CREATE`: create the representation of the desired new segment (which is the purpose ** of invoking this algorithm) and insert it _before_ the given insert POS, ** return an iterator (POS) pointing at the newly created segment ** - `EMPTY`: similarly create a new segment, which however is classified as _empty_ ** and serves to fill a gap, notably towards the end of the axis. ** - `CLONE`: insert a _clone_ of an existing segment but adapt it's start and after POS; ** this operation effectively allows to _adapt_ adjacent segments, by also deleting ** the original version after the new elements have been inserted. Return POS of clone ** - `DELETE`: delete all segments between the given start (incl) and end (excl) POS ** ** Moreover, POS (iterators) to indicate the complete domain are required, and a specification ** of the ORD values of the new segmen's _start_ and _after_ points -- which however can also ** be defined only partially (as optional values), to use contextual information... ** - if only the start point is given, then the existing segment containing this point ** will be shortened, and the new segment will cover the span until the existing segment's end ** - likewise an omitted start point will cause the new segment to be expanded towards lower ORD ** values until matching the end of the preceding segment ** - if both values are omitted, the new segment will replace the last segment of the given axis. ** The new segment may span an arbitrary range within the domain and may thus possibly supersede ** several existing segments, which are then removed by the `DELETE` operation. ** ** @see SplitSplice_test ** @see steam::fixture::Segmentation::splitSplice */ #ifndef LIB_SPLIT_SPLICE_H #define LIB_SPLIT_SPLICE_H #include "lib/error.hpp" #include "lib/meta/function.hpp" #include "lib/time/timevalue.hpp" #include "lib/nocopy.hpp" #include namespace lib { namespace error = lumiera::error; namespace splitsplice {/// Implementation of [»SplitSplice« algorithm](\ref splite-splice.hpp) /** * Descriptor and working context to split/splice in a new Interval. * The »Split-Splice« algorithm works on a seamless segmentation of * an ordered working axis, represented as sequence of intervals. * The purpose is to integrate a new Segment / interval, thereby * truncating / splitting / filling adjacent intervals to fit */ template class Algo : util::NonCopyable { /* ======= elementary operations ======= */ ASSERT_VALID_SIGNATURE (START, ORD(POS)) ASSERT_VALID_SIGNATURE (AFTER, ORD(POS)) ASSERT_VALID_SIGNATURE (CREATE, POS(POS,ORD,ORD)) ASSERT_VALID_SIGNATURE (EMPTY, POS(POS,ORD,ORD)) ASSERT_VALID_SIGNATURE (CLONE, POS(POS,ORD,ORD,POS)) ASSERT_VALID_SIGNATURE (DELETE, POS(POS,POS)) START getStart; AFTER getAfter; CREATE createSeg; EMPTY emptySeg; CLONE cloneSeg; DELETE discard; using OptORD = std::optional; const ORD AXIS_END; /* ======= working state ======= */ POS pred_, succ_; struct SegBounds { // can be assigned in one step (ORD may be immutable) ORD start, after; } b_; enum Verb { NIL , DROP , TRUNC , INS_NOP , SEAMLESS }; Verb opPred_ = NIL, opSucc_ = NIL; public: /** * Setup for a single SplitSplice-operation to insert a new segment \a start to \a after. * @param startAll (forward) iterator pointing at the overall Segmentation begin * @param afterAll (forward) iterator indicating point-after-end of Segmentation * @param start (optional) specification of new segment's start point * @param after (optional) specification of new segment's end point */ Algo (START fun_getStart ,AFTER fun_getAfter ,CREATE fun_createSeg ,EMPTY fun_emptySeg ,CLONE fun_cloneSeg ,DELETE fun_discard ,const ORD axisEnd ,POS startAll, POS afterAll ,OptORD start, OptORD after) : getStart{fun_getStart} , getAfter{fun_getAfter} , createSeg{fun_createSeg} , emptySeg{fun_emptySeg} , cloneSeg{fun_cloneSeg} , discard {fun_discard} , AXIS_END{axisEnd} , pred_{} , succ_{} , b_{establishSplitPoint (startAll,afterAll, start,after)} { // Postcondition: ordered start and end times ENSURE (pred_ != afterAll); ENSURE (succ_ != afterAll); ENSURE (b_.start < b_.after); ENSURE (getStart(pred_) <= b_.start); ENSURE (b_.start <= getStart(succ_) or pred_ == succ_); } /** * Stage-1 and Stage-2 of the algorithm determine the insert point * and establish the actual start and end point of the new segment * @return the definitive start and after ORD values, based on context */ SegBounds establishSplitPoint (POS startAll, POS afterAll ,OptORD start, OptORD after) { // nominal break point ORD sep = start? *start : after? *after : AXIS_END; // find largest Predecessor with start before separator for (succ_ = startAll, pred_ = afterAll ;succ_ != afterAll and getStart(succ_) < sep ;++succ_) { pred_ = succ_; } REQUIRE (pred_ != succ_, "non-empty segmentation required"); if (succ_ == afterAll) succ_=pred_; if (pred_ == afterAll) pred_=succ_; // separator touches bounds // Stage-2 : establish start and end point of new segment ORD startSeg = start? *start : getAfter(pred_) < sep? getAfter(pred_) : getStart(pred_); ORD afterSeg = after? *after : getStart(succ_) > sep? getStart(succ_) : getAfter(succ_); ENSURE (startSeg != afterSeg); if (startSeg < afterSeg) return {startSeg,afterSeg}; else return {afterSeg,startSeg}; } /** * Stage-3 of the algorithm works out the precise relation of the * predecessor and successor segments to determine necessary adjustments * @remark results in definition of operation verbs #opPred_ and #opSucc_. */ void determineRelations() { ORD startPred = getStart (pred_), afterPred = getAfter (pred_); if (startPred < b_.start) { if (afterPred < b_.start) opPred_ = INS_NOP; else if (afterPred == b_.start) opPred_ = SEAMLESS; else { opPred_ = TRUNC; if (afterPred > b_.after) { // predecessor actually spans the new segment // thus use it also as successor and truncate both (=SPLIT) succ_ = pred_; opSucc_ = TRUNC; return; } } } else { REQUIRE (startPred == b_.start, "predecessor does not precede start point"); opPred_ = DROP; if (b_.after < afterPred ) { // predecessor coincides with start of new segment // thus use it rather as successor and truncate at start succ_ = pred_; opSucc_ = TRUNC; return; } } ORD startSucc = getStart (succ_); if (startSucc < b_.after) { while (getAfter(succ_) < b_.after) ++succ_; ASSERT (getStart(succ_) < b_.after // in case we dropped a successor completely spanned, ,"seamless segmentation"); // even the next one must start within the new segment if (b_.after == getAfter(succ_)) opSucc_ = DROP; else if (b_.after < getAfter(succ_)) opSucc_ = TRUNC; } else { if (b_.after == startSucc) opSucc_ = SEAMLESS; else opSucc_ = INS_NOP; } } /** * Stage-4 of the algorithm performs the actual insert and deleting of segments * @return `(s,n,e)` to indicate where changes happened * - s the first changed element * - n the new main segment (may be identical to s) * - e the first unaltered element after the changed range (may be `end()`) */ std::array performSplitSplice() { POS refPred = pred_, refSucc = succ_; REQUIRE (opPred_ != NIL and opSucc_ != NIL); // deletions are done by skipping the complete range around the insertion point; // thus to retain a predecessor or successor, this range has to be reduced if (opPred_ == INS_NOP or opPred_ == SEAMLESS) ++pred_; if (opSucc_ == DROP or opSucc_ == TRUNC) ++succ_; // insert the new elements /before/ the range to be dropped, i.e. at pred_ POS insPos = pred_; POS n = createSeg (insPos, b_.start, b_.after); POS s = n; // // possibly adapt the predecessor if (opPred_ == INS_NOP) s = emptySeg (n, getAfter(refPred), b_.start); else if (opPred_ == TRUNC) s = cloneSeg (n, getStart(refPred), b_.start, refPred); // // possibly adapt the successor if (opSucc_ == INS_NOP) emptySeg (insPos, b_.after, getStart(refSucc)); else if (opSucc_ == TRUNC) cloneSeg (insPos, b_.after, getAfter(refSucc), refSucc); // finally discard superseded segments POS e = discard (insPos, succ_); // indicate the range where changes happened return {s,n,e}; } }; }//namespace splitsplace } // namespace lib #endif /*LIB_SPLIT_SPLICE_H*/