lumiera_/src/lib/itertools.hpp
Ichthyostega 94cec423d0 Job-Planning: switch to processing references
...which uncovers further deeply nested problems,
especially when referring to non-copyable types.

Thus need to construct a common type that can be used
both to refer to the source elements and the expanded elements,
and use this common type as result type and also attempt to
produce better diagnostic messages on type mismatch....
2023-05-23 01:08:05 +02:00

874 lines
23 KiB
C++

/*
ITERTOOLS.hpp - collection of tools for building and combining iterators
Copyright (C) Lumiera.org
2009, 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.
*/
/** @file itertools.hpp
** Helpers for working with iterators based on the pipeline model.
** Iterators abstract from the underlying data container and provide
** the contained data as a source to pull values from. Based on this
** model, we can build pipelines, with filters, valves, junction points
** and transforming facilities. The templates in this header enable such,
** based on the Lumiera Forward Iterator concept. They build on generic
** programming techniques, thus are intended to be combined at compile time
** using definitive type information. Contrast this to an iterator model
** as in Java's Collections, where Iterator is an Interface based on
** virtual functions. Thus, the basic problem to overcome is the lack
** of a single common interface, which could serve as foundation for
** type inference. As a solution, we use a "onion" approach, where a
** generic base gets configured with an active core, implementing
** the filtering or processing functionality, while the base class
** (IterTool) exposes the operations necessary to comply to the
** Forward Iterator Concept.
**
** \par filtering Iterator
** The FilterIter template can be used to build a filter into a pipeline,
** as it forwards only those elements from its source iterator, which pass
** the predicate evaluation. Anything acceptable as ctor parameter for a
** std::function object can be passed in as predicate, but of course the
** signature must be sensible. Please note, that -- depending on the
** predicate -- already the ctor or even a simple `bool` test might
** pull and exhaust the source iterator completely, in an attempt
** to find the first element passing the predicate test.
**
** \par extensible Filter
** Based on the FilterIter, this facility allows to elaborate the filter
** function while in the middle of iteration. The new augmented filter
** will be in effect starting with the current element, which might even
** be filtered away now due to a more restrictive condition. However,
** since this is still an iterator, any "past" elements are already
** extracted and gone and can thus not be subject to changed filtering.
** The ExtensibleFilterIter template provides several _builder functions_
** to elaborate the initial filter condition, like adding conjunctive or
** disjunctive clauses, flip the filter's meaning or even replace it
** altogether by a completely different filter function.
**
** \par processing Iterator
** the TransformIter template can be used as processing (or transforming)
** step within the pipeline. It is created with a functor, which, when
** pulling elements, is invoked for each element pulled from the
** source iterator. The signature of the functor must match the
** desired value (output) type.
**
** @see iter-adapter.hpp
** @see itertools-test.cpp
** @see event-log.hpp
*/
#ifndef LIB_ITERTOOLS_H
#define LIB_ITERTOOLS_H
#include "lib/iter-adapter.hpp"
#include "lib/meta/value-type-binding.hpp"
#include "lib/meta/function.hpp"
#include "lib/meta/trait.hpp"
#include "lib/wrapper.hpp"
#include "lib/util.hpp"
#include <functional>
#include <utility>
namespace lib {
using std::forward;
using std::function;
using util::unConst;
using lib::meta::ValueTypeBinding;
/**
* A neutral \em identity-function core,
* also serving as point-of reference how any
* core is intended to work. Any core is intended
* to serve as inner part of an iterator tool template.
* - it provides the trait typedefs
* - it abstracts the "source"
* - it abstracts the local operation to be performed
* - the ctor of the core sets up the configuration.
* @note cores should be copyable without much overhead
*/
template<class IT>
struct IdentityCore
{
IT source_;
IdentityCore (IT&& orig)
: source_{forward<IT>(orig)}
{ }
IdentityCore (IT const& orig)
: source_{orig}
{ }
IT&
pipe ()
{
return source_;
}
IT const&
pipe () const
{
return source_;
}
void
advance ()
{
++source_;
}
bool
evaluate () const
{
return bool(source_);
}
typedef typename IT::pointer pointer;
typedef typename IT::reference reference;
typedef typename IT::value_type value_type;
};
/**
* Standard functionality to build up any iterator tool.
* IterTool exposes the frontend functions necessary to
* comply to the Lumiera Forward Iterator concept.
* The protected part provides the _iteration control_
* building blocks to drive the processing/filter logic,
* which is implemented in the specific core for each tool.
*/
template<class CORE>
class IterTool
{
protected: /* == iteration control == */
CORE core_;
bool
hasData() const
{
return core_.evaluate()
|| unConst(this)->iterate();
} // to skip irrelevant results doesn't count as "mutation"
bool
iterate ()
{
if (!core_.pipe()) return false;
do core_.advance();
while (core_.pipe() && !core_.evaluate());
return bool{core_.pipe()};
}
void
_maybe_throw() const
{
if (!isValid())
_throwIterExhausted();
}
public:
typedef typename CORE::pointer pointer;
typedef typename CORE::reference reference;
typedef typename CORE::value_type value_type;
IterTool (CORE&& setup)
: core_{std::move(setup)}
{
hasData();
}
explicit
operator bool() const
{
return isValid();
}
/* === lumiera forward iterator concept === */
reference
operator*() const
{
_maybe_throw();
return *core_.pipe();
}
pointer
operator->() const
{
_maybe_throw();
return & *core_.pipe();
}
IterTool&
operator++()
{
_maybe_throw();
iterate();
return *this;
}
bool
isValid () const
{
return hasData();
}
bool
empty () const
{
return not isValid();
}
};
template<class CX>
inline bool
operator== (IterTool<CX> const& it1, IterTool<CX> const& it2)
{
return (!it1 && !it2 )
|| ( it1 && it2 && (*it1) == (*it2) )
;
}
template<class CX>
inline bool
operator!= (IterTool<CX> const& ito1, IterTool<CX> const& ito2)
{
return not (ito1 == ito2);
}
/**
* Implementation of the filter logic.
* This core stores a function object instance,
* passing each pulled source element to this
* predicate function for evaluation.
* @note predicate is evaluated <i>at most once</i>
* for each value yielded by the source
*/
template<class IT>
struct FilterCore
: IdentityCore<IT>
{
using Raw = IdentityCore<IT>;
using Val = typename IT::reference;
function<bool(Val)> predicate_;
bool
evaluate () const
{
return Raw::pipe()
&& currVal_isOK();
}
mutable bool cached_;
mutable bool isOK_;
bool
currVal_isOK () const ///< @return (maybe cached) result of filter predicate
{
return (cached_ && isOK_)
|| (cached_ = true
&&(isOK_ = predicate_(*Raw::pipe())));
}
void
advance ()
{
cached_ = false;
Raw::advance();
}
template<typename PRED>
FilterCore (IT&& source, PRED prediDef)
: Raw{forward<IT>(source)}
, predicate_(prediDef) // induces a signature check
, cached_(false) // not yet cached
, isOK_(false) // not yet relevant
{ }
template<typename PRED>
FilterCore (IT const& source, PRED prediDef)
: Raw{source}
, predicate_(prediDef)
, cached_(false)
, isOK_(false)
{ }
};
/**
* Iterator tool filtering pulled data according to a predicate
*/
template<class IT>
class FilterIter
: public IterTool<FilterCore<IT>>
{
typedef FilterCore<IT> _Filter;
typedef IterTool<_Filter> _Impl;
public:
static bool acceptAll(typename _Filter::Val) { return true; }
FilterIter ()
: _Impl{FilterCore<IT>(IT(), acceptAll)}
{ }
template<typename PRED>
FilterIter (IT const& src, PRED filterPredicate)
: _Impl{_Filter(src, filterPredicate)}
{ }
template<typename PRED>
FilterIter (IT&& src, PRED filterPredicate)
: _Impl{_Filter(forward<IT>(src), filterPredicate)}
{ }
ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (FilterIter)
};
/** Build a FilterIter: convenience free function shortcut,
* picking up the involved types automatically.
* @param filterPredicate to be invoked for each source element
* @return Iterator filtering contents of the source
*/
template<class IT, typename PRED>
inline auto
filterIterator (IT const& src, PRED filterPredicate)
{
return FilterIter<IT>{src, filterPredicate};
}
template<class IT, typename PRED>
inline auto
filterIterator (IT&& src, PRED filterPredicate)
{
using SrcIT = typename std::remove_reference<IT>::type;
return FilterIter<SrcIT>{forward<IT>(src), filterPredicate};
}
/**
* Additional capabilities for FilterIter,
* allowing to extend the filter condition underway.
* This wrapper enables remoulding of the filer functor
* while in the middle of iteration. When the filter is
* modified, current head of iteration gets re-evaluated
* and possible fast-forwarded to the next element
* satisfying the now extended filter condition.
* @note changing the condition modifies a given iterator in place.
* Superficially this might look as if the storage remains
* the same, but in fact we're adding a lambda closure,
* which the runtime usually allocates on the heap,
* holding the previous functor and a second functor
* for the added clause.
* @warning the addition of disjunctive and negated clauses might
* actually weaken the filter condition. Yet still there is
* _no reset of the source iterator,_ i.e. we don't
* re-evaluate from start, but just from current head.
* Which means we might miss elements in the already consumed
* part of the source sequence, which theoretically would
* pass the now altered filter condition.
* @see IterTools_test::verify_filterExtension
*/
template<class IT>
class ExtensibleFilterIter
: public FilterIter<IT>
{
using _Filter = FilterCore<IT>;
using Val = typename _Filter::Val;
void
reEvaluate()
{
this->core_.cached_ = false;
this->hasData(); // re-evaluate head element
}
public:
ExtensibleFilterIter() { }
template<typename PRED>
ExtensibleFilterIter (IT&& src, PRED initialFilterPredicate)
: FilterIter<IT>{forward<IT>(src), initialFilterPredicate}
{ }
template<typename PRED>
ExtensibleFilterIter (IT const& src, PRED initialFilterPredicate)
: FilterIter<IT>{src, initialFilterPredicate}
{ }
ExtensibleFilterIter (IT&& src)
: ExtensibleFilterIter{forward<IT>(src), FilterIter<IT>::acceptAll}
{ }
// standard copy operations acceptable
/** access the unfiltered source iterator
* in current state */
IT&
underlying()
{
return this->core_.source_;
}
template<typename COND>
ExtensibleFilterIter&
andFilter (COND conjunctiveClause)
{
function<bool(Val)>& filter = this->core_.predicate_;
filter = [=](Val val)
{
return filter(val)
and conjunctiveClause(val);
};
reEvaluate();
return *this;
}
template<typename COND>
ExtensibleFilterIter&
andNotFilter (COND conjunctiveClause)
{
function<bool(Val)>& filter = this->core_.predicate_;
filter = [=](Val val)
{
return filter(val)
and not conjunctiveClause(val);
};
reEvaluate();
return *this;
}
template<typename COND>
ExtensibleFilterIter&
orFilter (COND disjunctiveClause)
{
function<bool(Val)>& filter = this->core_.predicate_;
filter = [=](Val val)
{
return filter(val)
or disjunctiveClause(val);
};
reEvaluate();
return *this;
}
template<typename COND>
ExtensibleFilterIter&
orNotFilter (COND disjunctiveClause)
{
function<bool(Val)>& filter = this->core_.predicate_;
filter = [=](Val val)
{
return filter(val)
or not disjunctiveClause(val);
};
reEvaluate();
return *this;
}
template<typename COND>
ExtensibleFilterIter&
setNewFilter (COND entirelyDifferentPredicate)
{
this->core_.predicate_ = entirelyDifferentPredicate;
reEvaluate();
return *this;
}
ExtensibleFilterIter&
flipFilter ()
{
function<bool(Val)>& filter = this->core_.predicate_;
filter = [=](Val val)
{
return not filter(val);
};
reEvaluate();
return *this;
}
};
/**
* Helper: predicate returning `true`
* whenever the argument value changes
* during a sequence of invocations.
*/
template<typename VAL>
class SkipRepetition
{
typedef wrapper::ItemWrapper<VAL> Item;
Item prev_;
public:
bool
operator() (VAL const& elm)
{
if (prev_ &&
(*prev_ == elm))
return false;
// element differs from predecessor
prev_ = elm;
return true;
}
typedef bool result_type;
};
/**
* Implementation of a _singleton value_ holder,
* which discards the contained value once "iterated"
*/
template<typename VAL>
class SingleValCore
{
typedef wrapper::ItemWrapper<VAL> Item;
Item theValue_;
public:
SingleValCore() { } ///< passive and empty
SingleValCore (VAL&& something)
: theValue_{forward<VAL> (something)}
{ }
Item const&
pipe () const
{
return theValue_;
}
void
advance ()
{
theValue_.reset();
}
bool
evaluate () const
{
return theValue_.isValid();
}
typedef std::remove_reference_t<VAL> * pointer;
typedef std::remove_reference_t<VAL> & reference;
typedef std::remove_reference_t<VAL> value_type;
};
/**
* Pseudo-Iterator to yield just a single value.
* When incremented, the value is destroyed and
* the Iterator transitions to _exhausted state_.
* @remark as such might look nonsensical, but proves
* useful when a function yields an iterator, while
* producing an explicit value in some special case.
* @tparam VAL anything, value or reference to store
*/
template<class VAL>
class SingleValIter
: public IterTool<SingleValCore<VAL>>
{
using _ValHolder = SingleValCore<VAL>;
using _IteratorImpl = IterTool<_ValHolder> ;
public:
SingleValIter ()
: _IteratorImpl{_ValHolder{}}
{ }
SingleValIter (VAL&& something)
: _IteratorImpl{_ValHolder{forward<VAL>(something)}}
{ }
ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (SingleValIter)
};
/** Build a SingleValIter: convenience free function shortcut,
* to pick up just any value and wrap it as Lumiera Forward Iterator.
* @return Iterator to yield the value once
* @warning be sure to understand that we literally pick up and wrap anything
* provided as argument. If you pass a reference, we wrap a reference.
* If you want to wrap a copy, you have to do the copy yourself inline
*/
template<class VAL>
inline auto
singleValIterator (VAL&& something)
{
return SingleValIter<VAL>{forward<VAL>(something)};
}
/** not-anything-at-all iterator */
template<class VAL>
inline auto
nilIterator()
{
return SingleValIter<VAL>();
}
/**
* Implementation of custom processing logic.
* This core stores a function object instance
* to treat each source element pulled.
*/
template<class IT, class VAL>
class TransformingCore
{
typedef typename IT::reference InType;
typedef wrapper::ItemWrapper<VAL> Item;
function<VAL(InType)> trafo_;
IT source_;
Item treated_;
void
processItem ()
{
if (source_)
treated_ = trafo_(*source_);
else
treated_.reset();
}
public:
TransformingCore () ///< deactivated core
: trafo_()
, source_()
, treated_()
{ }
template<typename FUN>
TransformingCore (IT&& orig, FUN processor)
: trafo_(processor) // induces a signature check
, source_(forward<IT> (orig))
{
processItem();
}
template<typename FUN>
TransformingCore (IT const& orig, FUN processor)
: trafo_(processor) // induces a signature check
, source_(orig)
{
processItem();
}
Item const&
pipe () const
{
return treated_;
}
void
advance ()
{
++source_;
processItem();
}
bool
evaluate () const
{
return bool(source_);
}
using pointer = typename ValueTypeBinding<VAL>::pointer;
using reference = typename ValueTypeBinding<VAL>::reference;
using value_type = typename ValueTypeBinding<VAL>::value_type;
};
/**
* Iterator tool treating pulled data by a custom transformation (function)
* @tparam IT source iterator
* @tparam VAL result (output) type
*/
template<class IT, class VAL>
class TransformIter
: public IterTool<TransformingCore<IT,VAL>>
{
using _Trafo = TransformingCore<IT,VAL>;
using _IteratorImpl = IterTool<_Trafo> ;
public:
TransformIter ()
: _IteratorImpl(_Trafo())
{ }
template<typename FUN>
TransformIter (IT&& src, FUN trafoFunc)
: _IteratorImpl{_Trafo(forward<IT>(src), trafoFunc)}
{ }
template<typename FUN>
TransformIter (IT const& src, FUN trafoFunc)
: _IteratorImpl{_Trafo(src, trafoFunc)}
{ }
ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (TransformIter)
};
/** Build a TransformIter: convenience free function shortcut,
* picking up the involved types automatically.
* @tparam processingFunc to be invoked for each source element
* @return Iterator processing the source feed
*/
template<class IT, typename FUN>
inline auto
transformIterator (IT const& src, FUN processingFunc)
{
using OutVal = typename lib::meta::_Fun<FUN>::Ret;
return TransformIter<IT,OutVal>{src,processingFunc};
}
template<class IT, typename FUN>
inline auto
transformIterator (IT&& src, FUN processingFunc)
{
using SrcIT = typename std::remove_reference<IT>::type;
using OutVal = typename lib::meta::_Fun<FUN>::Ret;
return TransformIter<SrcIT,OutVal>{forward<IT>(src), processingFunc};
}
/* === utility functions === */
template<class IT, class CON>
inline void
append_all (IT iter, CON& container)
{
for ( ; iter; ++iter )
container.push_back (*iter);
}
template<class IT>
inline typename IT::value_type
pull_last (IT iter)
{
using Val = typename IT::value_type;
using Item = wrapper::ItemWrapper<Val>;
Item lastElm;
while (iter)
{
lastElm = *iter;
++iter;
}
if (lastElm)
return *lastElm;
else
throw lumiera::error::State ("attempt to retrieve the last element "
"of an exhausted or empty iterator"
,lumiera::error::LUMIERA_ERROR_ITER_EXHAUST);
}
/** filters away repeated values
* emitted by source iterator */
template<class IT>
inline auto
filterRepetitions (IT const& source)
{
using Val = typename meta::ValueTypeBinding<IT>::value_type;
return filterIterator (source, SkipRepetition<Val>());
}
template<class IT>
inline auto
filterRepetitions (IT&& source)
{
using Val = typename meta::ValueTypeBinding<IT>::value_type;
return filterIterator (forward<IT>(source), SkipRepetition<Val>() );
}
} // namespace lib
#endif