/* ITER-CHAIN-SEARCH.hpp - chained search with backtracking based on (bidirectional) iterator Copyright (C) Lumiera.org 2018, 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 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. ** ** @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" #include "lib/iter-tree-explorer.hpp" #include "lib/meta/util.hpp" //#include //#include #include #include #include namespace lib { namespace iter { using std::move; using std::forward; using std::string; using lib::meta::disable_if; namespace { // type construction helpers... template auto buildSearchFilter (SRC&& dataSource) { return treeExplore (forward (dataSource)) .mutableFilter(); } template auto buildExplorer (SRC&& dataSource, FUN&& expandFunctor) { return buildSearchFilter (forward (dataSource)) .expand (forward (expandFunctor)) .expandAll(); } /** * @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. */ template struct _IterChainSetup { using Filter = decltype( buildSearchFilter(std::declval()).asIterator() ); using StepFunctor = std::function; using Pipeline = decltype( buildExplorer (std::declval(), std::declval()) ); static Pipeline configurePipeline (SRC&& dataSource, StepFunctor step) { return buildExplorer(forward (dataSource), move (step)); } }; }//(End)type construction helpers /** * Iterator based linear search mechanism, with the ability to perform consecutive search with backtracking. * 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. * 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. */ template class IterChainSearch : public _IterChainSetup::Pipeline { using _Trait = _IterChainSetup; using _Base = typename _Trait::Pipeline; using Value = typename _Base::value_type; using Filter = typename _Trait::Filter; using Step = typename _Trait::StepFunctor; /** @internal access embedded filter sub-Pipeline */ Filter& filter() { return *this; } /** Storage for a sequence of filter configuration functors */ std::vector stepChain_; 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. */ template explicit IterChainSearch (SEQ&& srcData) : _Base{_Trait::configurePipeline (forward (srcData) ,[this](Filter const& curr){ return configureFilterChain(curr); })} , stepChain_{} { // mark initial pristine state _Base::disableFilter(); } // inherited default ctor and standard copy operations using _Base::_Base; /** 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 disable_if, IterChainSearch&& > search (FUN&& configureSearchStep) { if (not this->empty()) { Step nextStep{forward (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 } 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. */ IterChainSearch&& search (Value target) { 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 }); return move(*this); } /** drop all search condition frames. * @remark the filter chain becomes empty, * passing through the unaltered * source sequence */ IterChainSearch&& clearFilter() { //////////////////////////////////////////////////////TODO logically broken. We need also to get rid of the current expansions, while retaining the current position stepChain_.clear(); _Base::disableFilter(); return move(*this); } private: Filter configureFilterChain (Filter const& currentFilterState) { uint depth = this->depth(); if (depth < stepChain_.size()) return stepChain_[depth](currentFilterState); // augmented copy else return Filter{}; // empty filter indicates recursion end } }; /* ==== 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). * @param srcData either a »Lumiera Forward Iterator«, a _reference_ to a STL * container, or a [»State Core«](\ref lib::IterStateWrapper) object. */ template inline auto chainSearch (SRC&& srcData) { return IterChainSearch{forward (srcData)}; } }} // namespace lib::iter #endif /*SRC_LIB_ITER_CHAIN_SEARCH_H*/