From 90c0f43cfda28853e3d858f3e7068cbe1204a1c7 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 2 Sep 2018 22:34:30 +0200 Subject: [PATCH] TreeExplorer: code all the combination cases ...this solution works, but has a shortcoming: the type of the passed lambdas is effectively pinned to conform with the signature of the first lambda used initially when building the filter. Well, this is the standard use case, but it kind of turns all the tricky warpping and re-binding into a nonsense excercise; in this form the filter can only be used in the monadic case (value -> bool). Especially this rules out all the advanced usages, where the filter collaborates with the internals of the source. --- src/lib/iter-tree-explorer.hpp | 132 +++++++++++++++++++++- tests/library/iter-tree-explorer-test.cpp | 31 +++++ wiki/thinkPad.ichthyo.mm | 39 ++++++- 3 files changed, 196 insertions(+), 6 deletions(-) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 621f7b5be..2c70fdff4 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -725,7 +725,7 @@ namespace lib { /** - * @internal extension to the Expander decorator to perform expansion delayed on next iteration. + * @internal extension to the Expander decorator to perform expansion delayed on next iteration. */ template class ScheduledExpander @@ -932,6 +932,17 @@ namespace lib { + /** + * @internal Special variant of the \ref Filter Decorator to allow for dynamic remoulding. + * This covers a rather specific use case, where we want to remould or even exchange the + * Filter predicate in the middle of an ongoing iteration. Such a remoulding can be achieved + * by binding the existing (opaque) filter predicate into a new combined lambda, thereby + * capturing it _by value._ After building, this remoulded version can be assigned to the + * original filter functor, under the assumption that both are roughly compatible. Moreover, + * since we wrap the actual lambda into an adapter, allowing for generic lambdas to be used + * as filter predicates, this setup allows for a lot of leeway regarding the concrete predicates. + * @note whenever the filter is remoulded, the invariant is immediately [re-established](\ref Filter::pullFilter() ) + */ template class MutableFilter : public Filter @@ -943,6 +954,7 @@ namespace lib { public: /* === API to Remould the Filter condition underway === */ + /** remould existing predicate to require in addition the given clause to hold */ template void andFilter (COND&& conjunctiveClause) @@ -958,8 +970,101 @@ namespace lib { }); } + /** remould existing predicate to require in addition the negation of the given clause to hold */ + template + void + andNotFilter (COND&& conjunctiveClause) + { + remouldFilter (forward (conjunctiveClause) + ,[](auto first, auto chain) + { + return [=](auto val) + { + return first(val) + and not chain(val); + }; + }); + } + + /** remould existing predicate to require either the old _OR_ the given new clause to hold */ + template + void + orFilter (COND&& disjunctiveClause) + { + remouldFilter (forward (disjunctiveClause) + ,[](auto first, auto chain) + { + return [=](auto val) + { + return first(val) + or chain(val); + }; + }); + } + + /** remould existing predicate to require either the old _OR_ the negation of a new clause to hold */ + template + void + orNotFilter (COND&& disjunctiveClause) + { + remouldFilter (forward (disjunctiveClause) + ,[](auto first, auto chain) + { + return [=](auto val) + { + return first(val) + or not chain(val); + }; + }); + } + + /** remould existing predicate to negate the meaning of the existing clause */ + void + flipFilter() + { + auto dummy = [](auto){ return false; }; + remouldFilter (dummy + ,[](auto currentFilter, auto) + { + return [=](auto val) + { + return not currentFilter(val); + }; + }); + } + + /** replace the existing predicate with the given, entirely different predicate */ + template + void + setNewFilter (COND&& entirelyDifferentPredicate) + { + remouldFilter (forward (entirelyDifferentPredicate) + ,[](auto, auto chain) + { + return [=](auto val) + { + return chain(val); + }; + }); + } + private: + /** @internal boilerplate to remould the filter predicate in-place + * @param additionalClause additional functor object to combine + * @param buildCombinedClause a _generic lambda_ (important!) to define + * how exactly the old and the new predicate are to be combined + * @note the actual combination logic is handed in as generic lambda, which + * essentially is a template class, and this allows to bind to any kind + * of function objects or lambdas. This combination closure requires a + * specific setup: when invoked with the existing and the new functor + * as argument, it needs to build a new _likewise generic_ lambda + * to perform the combined evaluation. + * @warning the handed-in lambda `buildCombinedClause` must capture its + * arguments, the existing functors _by value._ This is the key piece + * in the puzzle, since it effectively moves the existing functor into + * a new heap allocated storage. + */ template void remouldFilter (COND&& additionalClause, COMB buildCombinedClause) @@ -971,15 +1076,17 @@ namespace lib { using FilterPredicate = typename _Base::FilterPredicate; using ChainPredicate = typename _ChainTraits::Functor; - FilterPredicate& firstClause = Filter::predicate_; - ChainPredicate chainClause{forward (additionalClause)}; + FilterPredicate& firstClause = _Base::predicate_; // pick up the existing filter predicate + ChainPredicate chainClause{forward (additionalClause)}; // wrap the extension predicate in a similar way _Base::predicate_ = FilterPredicate{buildCombinedClause (firstClause, chainClause)}; - _Base::pullFilter(); + _Base::pullFilter(); // pull to re-establish the Invariant } }; + + /** * Interface to indicate and expose the ability for _child expansion_. * This interface is used when packaging a TreeExplorer pipeline opaquely into IterSource. @@ -1222,7 +1329,7 @@ namespace lib { } - /** extension functionality to be used on top of expand(), to perform expansion on next iteration. + /** extension functionality to be used on top of expand(), to perform expansion on next iteration. * When configured, an expandChildren() call will not happen immediately, but rather in place of * the next iteration step. Basically child expansion _is kind of a special iteration step,_ and * thus all we need to do is add another layer with a boolean state flag, which catches the @@ -1277,6 +1384,21 @@ namespace lib { } + /** attach a special filter adapter, allowing to change the filter predicate while iterating. + * While otherwise this filter layer behaves exactly like the [standard version](\ref #filter), + * it exposes a special API to augment or even completely switch the filter predicate while + * in the middle of iterator evaluation. Of course, the underlying iterator is not re-evaluated + * from start (our iterators can not be reset), and so the new filter logic takes effect starting + * from the current element. Whenever the filter is remoulded, it is immediately re-evaluated, + * possibly causing the underlying iterator to be pulled until an element matching the condition + * is found. + * @see MutableFilter::andFilter + * @see MutableFilter::andNotFilter + * @see MutableFilter::orFilter + * @see MutableFilter::orNotFilter + * @see MutableFilter::flipFilter + * @see MutableFilter::setNewFilter + */ template auto mutableFilter (FUN&& filterPredicate) diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index ea4bda8b5..efbce194c 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -867,6 +867,37 @@ namespace test{ CHECK (18 == *seq); ++seq; CHECK (16 == *seq); + + seq.andFilter (takeTrd); + CHECK (12 == *seq); + + seq.flipFilter(); + CHECK (11 == *seq); // not divisible (by 2 AND by 3) + ++seq; + CHECK (10 == *seq); + + seq.setNewFilter (takeTrd); + CHECK ( 9 == *seq); + ++seq; + CHECK ( 6 == *seq); + + seq.orNotFilter (takeEve); + CHECK ( 6 == *seq); + ++seq; + CHECK ( 5 == *seq); + ++seq; + CHECK ( 3 == *seq); + +// string buff{"."}; +// seq.andNotFilter ([&](CountDown& core) +// { +// buff += util::toString(core.p) + "."; +// --core.p; +// return core.p % 2; +// }); +// cout << "URGS: "<<*seq<< " ..."< + + + + + + + + + + + +

+ ...für das dort hineingereichte Funktor-Objekt wird der Argument-Accessor ausgewählt (Metaprogrammierung). +

+

+ Er ist dann im Typ des Wrappers == _Traits::Functor codiert. +

+

+ +

+

+ Wir können zwar den im Wrapper enthaltenen Funktor neu zuweisen (in gewissen Grenzen), +

+

+ aber er wird stets den zu Beginn gewählten Argument-Accessor nehmen. +

+

+ Typischerweise wird dieser ja sogar beim Aufruf des getemplateteten Funtions-Operators geInlined +

+ + +
+
+ + + +
- +