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 02139, 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())
|
2018-09-11 04:25:08 +02:00
|
|
|
this-> filter() = move (nextStep (*this)); // apply first step immediately
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
stepChain_.emplace_back (move (nextStep)); // append all further steps into the chain...
|
|
|
|
|
this->iterNext(); // then establish invariant:
|
|
|
|
|
} // expand to leaf and forward to first match
|
2018-09-11 04:04:32 +02:00
|
|
|
}
|
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,
|
2018-09-12 04:17:49 +02:00
|
|
|
* passing through the reset of the
|
|
|
|
|
* source sequence unaltered
|
2018-09-07 16:17:35 +02:00
|
|
|
*/
|
2018-09-04 02:02:26 +02:00
|
|
|
IterChainSearch&&
|
|
|
|
|
clearFilter()
|
|
|
|
|
{
|
2018-09-07 16:17:35 +02:00
|
|
|
stepChain_.clear();
|
2018-09-12 04:17:49 +02:00
|
|
|
_Base::rootCurrent();
|
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*/
|