* Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
332 lines
14 KiB
C++
332 lines
14 KiB
C++
/*
|
||
SPLIT-SPLICE.hpp - Algorithm to integrate a new interval into an axis-segmentation.
|
||
|
||
Copyright (C)
|
||
2023, Hermann Vosseler <Ichthyostega@web.de>
|
||
|
||
**Lumiera** 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. See the file COPYING for further details.
|
||
|
||
*/
|
||
|
||
|
||
/** @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 <array>
|
||
|
||
|
||
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 ORD ///< order value for the segmentation
|
||
,class POS ///< iterator to work with elements of the segmentation
|
||
,class START ///< function to access the start value for a segment
|
||
,class AFTER ///< function to access the after-end value for a segment
|
||
,class CREATE ///< function to create a new segment: `createSeg(pos, start,after)`
|
||
,class EMPTY ///< function to create empty segment: `emptySeg (pos, start,after)`
|
||
,class CLONE ///< function to clone/modify segment: `cloneSeg (pos, start,after, src)`
|
||
,class DELETE ///< function to discard a segment: `discard (start,after)`
|
||
>
|
||
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<ORD>;
|
||
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<POS, 3>
|
||
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*/
|