refactor IterExplorer to allow for more flexible strategy definition
This commit is contained in:
parent
dc3ebd4a8f
commit
da4a343e9e
2 changed files with 131 additions and 42 deletions
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
/** @file iter-explorer.hpp
|
||||
** Helper template(s) for establishing various evaluation strategies for hierarchical data structures.
|
||||
** Based on the <b>Lumiera Forward Iterators</b> concept and using the basic IterAdaptor templates,
|
||||
** Based on the <b>Lumiera Forward Iterator</b> concept and using the basic IterAdaptor templates,
|
||||
** these components allow to implement typical evaluation strategies, like e.g. depth-first or
|
||||
** breadth-first exploration of a hierarchical structure. Since the access to this structure is
|
||||
** abstracted through the underlying iterator, what we effectively get is a functional datastructure.
|
||||
|
|
@ -32,10 +32,10 @@
|
|||
** \par Iterators as Monad
|
||||
** The fundamental idea behind the implementation technique used here is the \em Monad pattern
|
||||
** known from functional programming. A Monad is a (abstract) container created by using some specific functions.
|
||||
** This is an quite abstract concept with a wide variety of applications (things like IO state, parsers, combinators,
|
||||
** calculations with exception handling but also simple data structures like lists or trees). The key point with
|
||||
** any monad is the ability to \em bind a function into the monad; this function will work on the \em internals
|
||||
** of the monad and produce a modified new monad instance. In the simple case of a list, "binding" a function
|
||||
** This is an rather abstract concept with a wide variety of applications (things like IO state, parsers, combinators,
|
||||
** calculations with exception handling but also simple data structures like lists or trees). The key point with any
|
||||
** monad is the ability to \em bind a function into the monad; this function will work on the \em internals of the
|
||||
** monad and produce a modified new monad instance. In the simple case of a list, "binding" a function
|
||||
** basically means to map the function onto the elements in the list.
|
||||
**
|
||||
** \par Rationale
|
||||
|
|
@ -168,6 +168,12 @@ namespace lib {
|
|||
* independent, new result sequence based on this first element).
|
||||
* Afterwards, the source is \em advanced and then \em copied
|
||||
* into the result iterator.
|
||||
* @param SRC the source sequence or iterator to wrap
|
||||
* @param _COM_ "Combinator" strategy template. When binding (\c >>= ) a function,
|
||||
* an instance of that strategy becomes the new SRC for the created new
|
||||
* IterExplorer. This instantiation of the strategy gets as type parameters
|
||||
* - this IterExplorer's instance type
|
||||
* - the type of the function bound with \c >>=
|
||||
*/
|
||||
template<class SRC
|
||||
,template<class,class> class _COM_ = iter_explorer::DefaultCombinator
|
||||
|
|
@ -187,7 +193,7 @@ namespace lib {
|
|||
IterExplorer() { }
|
||||
|
||||
|
||||
/** wrap an iterator like state representation
|
||||
/** wrap an iterator-like state representation
|
||||
* to build it into a monad. The resulting entity
|
||||
* is both an iterator yielding the elements generated
|
||||
* by the core, and it provides the (monad) bind operator.
|
||||
|
|
@ -247,29 +253,64 @@ namespace lib {
|
|||
namespace iter_explorer { ///< predefined policies and configurations
|
||||
|
||||
using util::unConst;
|
||||
|
||||
|
||||
/**
|
||||
* a generic "Combinator strategy" for IterExplorer.
|
||||
* This fallback solution doesn't assume anything beyond the source
|
||||
* and the intermediary result(s) being a Lumiera Forward Iterators.
|
||||
* @note the implementation stores the functor into a std::function object,
|
||||
* which might cause heap allocations, depending on given function.
|
||||
* Besides, the implementation holds one instance of the (intermediary)
|
||||
* result iterator (yielded by invoking the function) and a copy of the
|
||||
* original IterExplorer source sequence, to get the further elements
|
||||
* when the initial results are exhausted.
|
||||
* Building block: evaluating source elements.
|
||||
* This strategy will be tied into a "Combinator"
|
||||
* to hold the actual functor bound into the enclosing
|
||||
* IterExplorer monad to work on the contained elements.
|
||||
*/
|
||||
template<class SRC, class FUN>
|
||||
class DefaultCombinator
|
||||
template<class SIG>
|
||||
struct ExploreByFunction
|
||||
: function<SIG>
|
||||
{
|
||||
typedef typename _Fun<FUN>::Ret ResultIter;
|
||||
typedef typename SRC::value_type SrcElement;
|
||||
typedef function<ResultIter(SrcElement)> Explorer;
|
||||
template<typename FUN>
|
||||
ExploreByFunction(FUN explorationFunctionDefinition)
|
||||
: function<SIG>(explorationFunctionDefinition)
|
||||
{ }
|
||||
|
||||
|
||||
SRC srcSeq_;
|
||||
ExploreByFunction() { } ///< by default initialised to bottom function
|
||||
};
|
||||
|
||||
/**
|
||||
* Support for a special use case: an Iterator of Iterators, joining results.
|
||||
* In this case, already the source produces a sequence of Iterators, which
|
||||
* just need to be passed through to the output buffer unaltered. Using this
|
||||
* within the DefaultCombinator strategy creates a combined, flattened iterator
|
||||
* of all the source iterator's contents.
|
||||
*/
|
||||
template<class SIG>
|
||||
struct UnalteredPassThrough;
|
||||
|
||||
template<class IT>
|
||||
struct UnalteredPassThrough<IT(IT)>
|
||||
{
|
||||
IT operator() (IT elm) const { return elm; }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Building block: evaluate and combine a sequence of iterators.
|
||||
* This implementation helper provides two kinds of "buffers" (actually implemented
|
||||
* as iterators): A result buffer (iterator) which holds a sequence of already prepared
|
||||
* result elements, which can be retrieved through iteration right away. And a supply buffer
|
||||
* (iterator) holding raw source elements. When the result buffer is exhausted, the next source
|
||||
* element will be pulled from there and fed through the "evaluation strategy", which typically
|
||||
* is a function processing the source element and producing a new result buffer (iterator).
|
||||
*/
|
||||
template<class SRC, class FUN
|
||||
,template<class> class _EXP_ = ExploreByFunction ///< Strategy: how to store and evaluate the function to apply on each element
|
||||
>
|
||||
class CombinedIteratorEvaluation
|
||||
{
|
||||
typedef typename _Fun<FUN>::Ret ResultIter;
|
||||
typedef typename SRC::value_type SrcElement;
|
||||
typedef _EXP_<ResultIter(SrcElement)> Explorer;
|
||||
|
||||
|
||||
SRC srcSeq_;
|
||||
ResultIter results_;
|
||||
Explorer explorer_;
|
||||
Explorer explorer_;
|
||||
|
||||
public:
|
||||
typedef typename ResultIter::value_type value_type;
|
||||
|
|
@ -277,9 +318,9 @@ namespace lib {
|
|||
typedef typename ResultIter::pointer pointer;
|
||||
|
||||
|
||||
DefaultCombinator() { }
|
||||
CombinedIteratorEvaluation() { }
|
||||
|
||||
DefaultCombinator(FUN explorerFunction)
|
||||
CombinedIteratorEvaluation(FUN explorerFunction)
|
||||
: srcSeq_()
|
||||
, results_()
|
||||
, explorer_(explorerFunction)
|
||||
|
|
@ -289,13 +330,13 @@ namespace lib {
|
|||
|
||||
|
||||
void
|
||||
startWith (ResultIter firstExplorationResult)
|
||||
setResultSequence (ResultIter firstExplorationResult)
|
||||
{
|
||||
results_ = firstExplorationResult;
|
||||
}
|
||||
|
||||
void
|
||||
followUp (SRC & followUpSourceElements)
|
||||
setSourceSequence (SRC & followUpSourceElements)
|
||||
{
|
||||
REQUIRE (explorer_);
|
||||
srcSeq_ = followUpSourceElements;
|
||||
|
|
@ -316,24 +357,72 @@ namespace lib {
|
|||
/* === Iteration control API for IterStateWrapper== */
|
||||
|
||||
friend bool
|
||||
checkPoint (DefaultCombinator const& seq)
|
||||
checkPoint (CombinedIteratorEvaluation const& seq)
|
||||
{
|
||||
return unConst(seq).findNextResultElement();
|
||||
}
|
||||
|
||||
friend reference
|
||||
yield (DefaultCombinator const& seq)
|
||||
yield (CombinedIteratorEvaluation const& seq)
|
||||
{
|
||||
return *(seq.results_);
|
||||
}
|
||||
|
||||
friend void
|
||||
iterNext (DefaultCombinator & seq)
|
||||
iterNext (CombinedIteratorEvaluation & seq)
|
||||
{
|
||||
++(seq.results_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* a generic "Combinator strategy" for IterExplorer.
|
||||
* This fallback solution doesn't assume anything beyond the source
|
||||
* and the intermediary result(s) being a Lumiera Forward Iterators.
|
||||
* @note the implementation stores the functor into a std::function object,
|
||||
* which might cause heap allocations, depending on given function.
|
||||
* Besides, the implementation holds one instance of the (intermediary)
|
||||
* result iterator (yielded by invoking the function) and a copy of the
|
||||
* original IterExplorer source sequence, to get the further elements
|
||||
* when the initial results are exhausted.
|
||||
*/
|
||||
template<class SRC, class FUN>
|
||||
class DefaultCombinator
|
||||
: public CombinedIteratorEvaluation<SRC,FUN>
|
||||
{
|
||||
typedef typename _Fun<FUN>::Ret ResultIter;
|
||||
|
||||
public:
|
||||
DefaultCombinator() { }
|
||||
|
||||
DefaultCombinator(FUN explorerFunction)
|
||||
: CombinedIteratorEvaluation<SRC,FUN>(explorerFunction)
|
||||
{ }
|
||||
|
||||
|
||||
void
|
||||
startWith (ResultIter firstExplorationResult)
|
||||
{
|
||||
this->setResultSequence (firstExplorationResult);
|
||||
}
|
||||
|
||||
void
|
||||
followUp (SRC & followUpSourceElements)
|
||||
{
|
||||
this->setSourceSequence (followUpSourceElements);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Special configuration for combining / flattening the results
|
||||
* of a sequence of iterators
|
||||
*/
|
||||
template<class SEQ>
|
||||
class ChainedIters
|
||||
{
|
||||
/////////////TODO unimplemented
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper template to bootstrap a chain of IterExplorers.
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ namespace test{
|
|||
|
||||
/**
|
||||
* This iteration state type describes
|
||||
* a sequence of numbers still to be delivered.
|
||||
* a sequence of numbers yet to be delivered.
|
||||
*/
|
||||
class State
|
||||
{
|
||||
|
|
@ -385,10 +385,11 @@ namespace test{
|
|||
* of a tree like system, built on top of the IterExplorer monad.
|
||||
*
|
||||
* \par Test data structure
|
||||
* We built a functional datastructure here, on the fly, while exploring it.
|
||||
* We build a functional datastructure here, on the fly, while exploring it.
|
||||
* The \c exploreChildren(m) function generates this tree like datastructure:
|
||||
* For a given node with number value i, it returns the children (2*i-1) and 2*i
|
||||
* -- but only up to the given maximum value m.
|
||||
*
|
||||
* If we start such a tree structure with a root node 1, and maximum = 8, this yields:
|
||||
* \code
|
||||
* ( 1 )
|
||||
|
|
@ -400,9 +401,9 @@ namespace test{
|
|||
*
|
||||
* \par How the exploration works
|
||||
* We use a pre defined Template \link DepthFirstExplorer \endlink, which is built on top of IterExplorer.
|
||||
* It contains the depth-first exploration strategy hardwired. Actually this effect is achieved by
|
||||
* defining a specific way how to \em combine the results of a \em exploration -- the latter being
|
||||
* the function which generates the data structure. To yield a depth-first exploration, all we have to do
|
||||
* It contains the depth-first exploration strategy in a hardwired fashion. Actually this effect is achieved
|
||||
* by defining a specific way how to \em combine the results of an \em exploration -- the latter being the
|
||||
* function which generates the data structure. To yield a depth-first exploration, all we have to do
|
||||
* is to delve down immediately into the children, right after visiting the node itself.
|
||||
*
|
||||
* Now, when creating such a DepthFirstExplorer by wrapping a given source iterator, the result is again
|
||||
|
|
@ -421,8 +422,8 @@ namespace test{
|
|||
typedef DepthFirstExplorer<NumberSequence> DepthFirst;
|
||||
DepthFirst root (seq(1));
|
||||
|
||||
NumberSequence explorationResult = root >>= exploreChildren(8);
|
||||
CHECK (materialise(explorationResult) == "1-1-1-1-2-2-3-4-2-3-5-6-4-7-8");
|
||||
string explorationResult = materialise(root >>= exploreChildren(8));
|
||||
CHECK (explorationResult == "1-1-1-1-2-2-3-4-2-3-5-6-4-7-8");
|
||||
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #892
|
||||
}
|
||||
|
||||
|
|
@ -441,8 +442,8 @@ namespace test{
|
|||
typedef BreadthFirstExplorer<NumberSequence> BreadthFirst;
|
||||
BreadthFirst root (seq(1));
|
||||
|
||||
NumberSequence explorationResult = root >>= exploreChildren(8);
|
||||
CHECK (materialise(explorationResult) == "1-1-2-1-2-3-4-1-2-3-4-5-6-7-8");
|
||||
string explorationResult = materialise(root >>= exploreChildren(8));
|
||||
CHECK (explorationResult == "1-1-2-1-2-3-4-1-2-3-4-5-6-7-8");
|
||||
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #892
|
||||
}
|
||||
|
||||
|
|
@ -504,10 +505,9 @@ namespace test{
|
|||
{
|
||||
return seq(0,top);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
LAUNCHER (IterExplorer_test, "unit common");
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue