2018-09-04 02:02:26 +02:00
/*
ITER - CHAIN - SEARCH . hpp - chained search with backtracking based on ( bidirectional ) iterator
Copyright ( C ) Lumiera . org
2018 , Hermann Vosseler < Ichthyostega @ web . de >
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 0213 9 , USA .
*/
2018-09-09 23:44:32 +02:00
/** @file iter-chain-search.hpp
* * Evaluation mechanism to apply a sequence of conditions onto a linear search .
* * This search algorithm is implemented on top of a tree expanding ( monadic ) filter pipeline ,
* * to allow for backtracking . The intention is not to combine the individual conditions , but
* * rather to apply them one by one . After finding a match for the first condition , we ' ll search
* * for the next condition _starting at the position of the previous match_ . In the most general
* * case , this immediate progression down the search chain might be too greedy ; it could be that
* * we don ' t find a match for the next condition , but if we backtrack and first search further
* * on the previous condition , continuing with the search from that further position might
* * then lead to a match . Basically we have to try all combinations of all possible local
* * matches , to find a solution to satisfy the whole chain of conditions .
2018-09-04 02:02:26 +02:00
* *
* * @ see IterCursor_test
* * @ see iter - adapter . hpp
* * @ see [ usage example ] ( event - log . hpp )
* *
*/
# ifndef SRC_LIB_ITER_CHAIN_SEARCH_H
# define SRC_LIB_ITER_CHAIN_SEARCH_H
# include "lib/error.hpp"
2018-09-05 06:27:36 +02:00
# include "lib/iter-tree-explorer.hpp"
2018-09-07 16:17:35 +02:00
# include "lib/meta/util.hpp"
2018-09-04 02:02:26 +02:00
//#include <type_traits>
//#include <utility>
# include <utility>
2018-09-07 15:23:46 +02:00
# include <vector>
2018-09-04 02:02:26 +02:00
# include <string>
namespace lib {
2018-09-07 01:56:41 +02:00
namespace iter {
2018-09-04 02:02:26 +02:00
using std : : move ;
2018-09-05 06:27:36 +02:00
using std : : forward ;
2018-09-04 02:02:26 +02:00
using std : : string ;
2018-09-07 16:17:35 +02:00
using lib : : meta : : disable_if ;
2018-09-07 01:56:41 +02:00
2018-09-07 15:23:46 +02:00
namespace { // type construction helpers...
2018-09-04 02:02:26 +02:00
2018-09-07 01:56:41 +02:00
template < class SRC >
auto
buildSearchFilter ( SRC & & dataSource )
{
return treeExplore ( forward < SRC > ( dataSource ) )
. mutableFilter ( ) ;
}
template < class SRC , class FUN >
auto
buildExplorer ( SRC & & dataSource , FUN & & expandFunctor )
{
return buildSearchFilter ( forward < SRC > ( dataSource ) )
. expand ( forward < FUN > ( expandFunctor ) )
. expandAll ( ) ;
2018-09-05 06:27:36 +02:00
}
2018-09-04 02:02:26 +02:00
/**
2018-09-07 03:02:41 +02:00
* @ internal helper to rebind on inferred types .
* @ remark we use the TreeExplorer framework to assemble the processing pipeline
* from suitable building blocks configured with some lambdas . However , we
* also want to _inherit_ from this filter pipeline , so to expose the typical
* iterator operations without much ado . Thus we use some ( static ) helper function
* templates , instantiate them with the actual source data iterator and pick up
* the inferred type .
2018-09-04 02:02:26 +02:00
*/
2018-09-05 06:27:36 +02:00
template < class SRC >
2018-09-07 03:02:41 +02:00
struct _IterChainSetup
2018-09-04 02:02:26 +02:00
{
2018-09-07 20:44:25 +02:00
using Filter = decltype ( buildSearchFilter ( std : : declval < SRC > ( ) ) . asIterator ( ) ) ;
using StepFunctor = std : : function < Filter ( Filter const & ) > ;
2018-09-09 20:44:32 +02:00
2018-09-07 00:45:04 +02:00
using Pipeline = decltype ( buildExplorer ( std : : declval < SRC > ( ) , std : : declval < StepFunctor > ( ) ) ) ;
2018-09-07 20:44:25 +02:00
static Pipeline
configurePipeline ( SRC & & dataSource , StepFunctor step )
{
return buildExplorer ( forward < SRC > ( dataSource ) , move ( step ) ) ;
}
2018-09-04 02:02:26 +02:00
} ;
2018-09-07 15:23:46 +02:00
} //(End)type construction helpers
2018-09-07 01:56:41 +02:00
2018-09-04 02:02:26 +02:00
/**
* Iterator based linear search mechanism , with the ability to perform consecutive search with backtracking .
2018-09-07 15:23:46 +02:00
* The IterChainSearch can be configured with a sequence of search goals ( filter conditions ) , and will apply
* these in succession on the underlying iterator . It will search _by linear search_ for the first hit of the
* first condition , and then continue to search _from there_ matching on the second condition , and so on .
2018-09-04 02:02:26 +02:00
* After the first combination of matches is exhausted , the search will backtrack and try to evaluate
* the next combination , leading to a tree of search solutions .
*/
2018-09-05 06:27:36 +02:00
template < class SRC >
2018-09-04 02:02:26 +02:00
class IterChainSearch
2018-09-07 01:56:41 +02:00
: public _IterChainSetup < SRC > : : Pipeline
2018-09-04 02:02:26 +02:00
{
2018-09-07 03:02:41 +02:00
using _Trait = _IterChainSetup < SRC > ;
using _Base = typename _Trait : : Pipeline ;
2018-09-07 16:17:35 +02:00
using Value = typename _Base : : value_type ;
2018-09-07 03:02:41 +02:00
using Filter = typename _Trait : : Filter ;
2018-09-09 20:44:32 +02:00
using Step = typename _Trait : : StepFunctor ;
2018-09-04 02:02:26 +02:00
2018-09-09 23:44:32 +02:00
/** @internal access embedded filter sub-Pipeline */
Filter & filter ( ) { return * this ; }
2018-09-04 02:02:26 +02:00
2018-09-09 23:44:32 +02:00
/** Storage for a sequence of filter configuration functors */
std : : vector < Step > stepChain_ ;
2018-09-07 15:23:46 +02:00
public :
/** Build a chain-search mechanism based on the given source data sequence.
* @ remark iterators will be copied or moved as appropriate , while from a STL compliant
* container just a pair of ( ` begin ( ) ` , ` end ( ) ` ) iterators is retrieved ; the latter
* is also the reason why a rvalue reference to STL container is rejected , since the
* container needs to reside elsewhere ; only the iterator is wrapped here .
*/
2018-09-07 03:02:41 +02:00
template < class SEQ >
explicit
IterChainSearch ( SEQ & & srcData )
2018-09-07 20:44:25 +02:00
: _Base { _Trait : : configurePipeline ( forward < SEQ > ( srcData )
, [ this ] ( Filter const & curr ) { return configureFilterChain ( curr ) ; } ) }
2018-09-09 23:44:32 +02:00
, stepChain_ { }
{ // mark initial pristine state
_Base : : disableFilter ( ) ;
}
2018-09-04 02:02:26 +02:00
2018-09-07 15:23:46 +02:00
// inherited default ctor and standard copy operations
using _Base : : _Base ;
2018-09-04 02:02:26 +02:00
2018-09-07 16:17:35 +02:00
/** configure additional chained search condition.
* @ param a functor ` Filter const & - > filter ` , which takes a current filter configuration ,
* returning a copy from this configuration , possibly configured differently .
* @ note the given functor , lambda or function reference will be wrapped and adapted
* to conform to the required function signature . When using a generic lambda ,
* the argument type ` Filter const & ` is assumed
* @ remarks the additional chained search condition given here will be applied _after_
* matching all other conditions already in the filter chain . Each such condition
* is used to _filter_ the underlying source iterator , i . e . pull it until finding
* and element to match the condition . Basically these conditions are _not_ used in
* conjunction , but rather one after another . But since each such step in the chain
* is defined by a functor , which gets the previous filter configuration as argument ,
* it is _possible_ to build a step which _extends_ or sharpens the preceding condition .
*/
template < typename FUN >
disable_if < is_convertible < FUN , Value > ,
IterChainSearch & & >
search ( FUN & & configureSearchStep )
{
2018-09-11 04:04:32 +02:00
if ( not this - > empty ( ) )
{
Step nextStep { forward < FUN > ( configureSearchStep ) } ;
if ( _Base : : isDisabled ( ) )
this - > filter ( ) = move ( nextStep ( * this ) ) ; // immediately apply first step
else //
stepChain_ . emplace_back ( move ( nextStep ) ) ; // append all further steps into the chain...
// then establish invariant:
this - > iterNext ( ) ; // expand to leaf and forward to first match
}
2018-09-07 16:17:35 +02:00
return move ( * this ) ;
}
/** attach additional direct search for a given value.
* After successfully searching for all the conditions currently in the filter chain ,
* the underlying iterator will finally be pulled until matching the given target value .
*/
2018-09-04 02:02:26 +02:00
IterChainSearch & &
2018-09-07 16:17:35 +02:00
search ( Value target )
2018-09-04 02:02:26 +02:00
{
2018-09-07 16:17:35 +02:00
search ( [ = ] ( Filter filter ) // note: filter taken by value
{
filter . setNewFilter ( [ target ] ( Value const & currVal ) { return currVal = = target ; } ) ;
return filter ; // return copy of the original state with changed filter
} ) ;
2018-09-04 02:02:26 +02:00
return move ( * this ) ;
}
2018-09-07 16:17:35 +02:00
/** drop all search condition frames.
* @ remark the filter chain becomes empty ,
* passing through the unaltered
* source sequence
*/
2018-09-04 02:02:26 +02:00
IterChainSearch & &
clearFilter ( )
{
2018-09-07 21:12:30 +02:00
//////////////////////////////////////////////////////TODO logically broken. We need also to get rid of the current expansions, while retaining the current position
2018-09-07 16:17:35 +02:00
stepChain_ . clear ( ) ;
2018-09-09 23:44:32 +02:00
_Base : : disableFilter ( ) ;
2018-09-04 02:02:26 +02:00
return move ( * this ) ;
}
2018-09-07 03:02:41 +02:00
private :
Filter
configureFilterChain ( Filter const & currentFilterState )
2018-09-04 02:02:26 +02:00
{
2018-09-07 21:12:30 +02:00
uint depth = this - > depth ( ) ;
2018-09-07 16:17:35 +02:00
if ( depth < stepChain_ . size ( ) )
return stepChain_ [ depth ] ( currentFilterState ) ; // augmented copy
else
return Filter { } ; // empty filter indicates recursion end
2018-09-04 02:02:26 +02:00
}
} ;
/* ==== convenient builder free function ==== */
/** setup a chain search configuration by suitably wrapping the given container.
* @ return a TreeEplorer , which is an Iterator to yield all the source elements ,
* but may also be used to build an processing pipeline .
* @ warning if you capture the result of this call by an auto variable ,
* be sure to understand that invoking any further builder operation on
* TreeExplorer will invalidate that variable ( by moving it into the
* augmented iterator returned from such builder call ) .
2018-09-07 15:23:46 +02:00
* @ param srcData either a » Lumiera Forward Iterator « , a _reference_ to a STL
* container , or a [ » State Core « ] ( \ ref lib : : IterStateWrapper ) object .
2018-09-04 02:02:26 +02:00
*/
2018-09-05 06:27:36 +02:00
template < class SRC >
2018-09-04 02:02:26 +02:00
inline auto
2018-09-05 06:27:36 +02:00
chainSearch ( SRC & & srcData )
2018-09-04 02:02:26 +02:00
{
2018-09-07 03:02:41 +02:00
return IterChainSearch < SRC > { forward < SRC > ( srcData ) } ;
2018-09-04 02:02:26 +02:00
}
2018-09-07 01:56:41 +02:00
} } // namespace lib::iter
2018-09-04 02:02:26 +02:00
# endif /*SRC_LIB_ITER_CHAIN_SEARCH_H*/