diff --git a/src/lib/iter-adapter.hpp b/src/lib/iter-adapter.hpp index 0fadc0a5f..7015f7be2 100644 --- a/src/lib/iter-adapter.hpp +++ b/src/lib/iter-adapter.hpp @@ -326,7 +326,7 @@ namespace lib { * -# \c yield realises the given state, yielding an element of result type `T&` * @tparam T nominal result type (maybe const, but without reference). * The resulting iterator will yield a reference to this type T - * @tparam ST type of the "state core", defaults to T. + * @tparam ST type of the »state core«, defaults to T. * The resulting iterator will hold an instance of ST, which thus * needs to be copyable and default constructible to the extent * this is required for the iterator as such. @@ -447,6 +447,53 @@ namespace lib { + /** + * Adapter to dress up an existing »Lumiera Forward Iterator« as »state core«. + * This building block achieves the complement of \ref IterStateWrapper by providing + * the API functions expected by the latter's _state protocol;_ a combination of + * IterStateCore and IterStateWrapper layered on top behaves identical to the + * original iterator. This can be used to change some aspects of the behaviour. + * @remark directly layered by inheritance, thus public functions of the + * wrapped iterator remain visible (contrary to IterStateWrapper) + */ + template + class IterStateCore + : public IT + { + static_assert (lib::meta::can_IterForEach::value + ,"Lumiera Iterator required as source"); + protected: + IT& + srcIter() const + { + return unConst(*this); + } + + public: + using IT::IT; + + /* === state protocol API for IterStateWrapper === */ + bool + checkPoint() const + { + return bool(srcIter()); + } + + typename IT::reference + yield() const + { + return *srcIter(); + } + + void + iterNext() + { + ++ srcIter(); + } + }; + + + diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 1ff10179d..e672d0f9b 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -1213,6 +1213,51 @@ namespace lib { ); // wrap the extension predicate in a similar way _Filter::pullFilter(); // then pull to re-establish the Invariant } + }; /////////////////////////////////////TICKET #1305 shouldn't we use std::move(_Filter::predicate_) ??? + + + + /** + * @internal Decorator for TreeExplorer to cut iteration once a predicate ceases to be true. + * Similar to Filter, the given functor is adapted as appropriate, yet is required to yield + * a bool convertible result. The functor will be evaluated whenever the »exhausted« state + * of the resulting iterator is checked, on each access and before iteration; this evaluation + * is not cached (and thus could also detect ongoing state changes by side-effect). + * @note usually an _exhausted iterator will be abandoned_ — however, since the test is + * not cached, the iterator might become active again, if for some reason the + * condition becomes true again (e.g. as result of `expandChildern()`) + */ + template + class StopTrigger + : public IterStateCore + { + static_assert(can_IterForEach::value, "Lumiera Iterator required as source"); + + using Core = IterStateCore; + using Cond = function; + + Cond whileCondition_; + + public: + + StopTrigger() =default; + // inherited default copy operations + + template + StopTrigger (SRC&& dataSrc, FUN&& condition) + : Core{move (dataSrc)} + , whileCondition_{_FunTraits::adaptFunctor (forward (condition))} + { } + + + /** adapt the iteration control API for IterableDecorator: + * check the stop condition first and block eventually */ + bool + checkPoint() const + { + return Core::checkPoint() + and whileCondition_(Core::srcIter()); + } }; @@ -1511,6 +1556,44 @@ namespace lib { } + /** adapt this TreeExplorer to iterate only as long as a condition holds true. + * @return processing pipeline with attached [stop condition](\ref iter_explorer::StopTrigger) + */ + template + auto + iterWhile (FUN&& whileCond) + { + iter_explorer::static_assert_isPredicate(); + + using ResCore = iter_explorer::StopTrigger; + using ResIter = typename _DecoratorTraits::SrcIter; + + return TreeExplorer (ResCore {move(*this), forward(whileCond)}); + } + + + /** adapt this TreeExplorer to iterate until a condition becomes first true. + * @return processing pipeline with attached [stop condition](\ref iter_explorer::StopTrigger) + */ + template + auto + iterUntil (FUN&& untilCond) + { + iter_explorer::static_assert_isPredicate(); + + using ResCore = iter_explorer::StopTrigger; + using ResIter = typename _DecoratorTraits::SrcIter; + using ArgType = typename iter_explorer::_FunTraits::Arg; + + return TreeExplorer (ResCore { move(*this) + ,[whileCond = forward(untilCond)](ArgType val) + { + return not whileCond(val); + } + }); + } + + /** adapt this TreeExplorer to filter results, by invoking the given functor to approve them. * The previously created source layers will be "pulled" to fast-forward immediately to the * next element confirmed this way by the bound functor. If none of the source elements diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index a9cdfca95..65f95de16 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -281,6 +281,7 @@ namespace test{ verify_combinedExpandTransform(); verify_customProcessingLayer(); verify_scheduledExpansion(); + verify_untilStopTrigger(); verify_FilterIterator(); verify_FilterChanges(); verify_asIterSource(); @@ -829,6 +830,42 @@ namespace test{ + /** @test control end of iteration by a stop condition predicate. + * When decorating the pipeline with this adapter, iteration end depends not only on + * the source iterator, but also on the end condition; once the condition flips, the + * overall pipeline iterator is exhausted and can never be re-activated again (unless + * some special trickery is done by conspiring with the data source) + */ + void + verify_untilStopTrigger() + { + CHECK (materialise ( + treeExplore(CountDown{10}) + .iterUntil([](uint j){ return j < 5; }) + ) + == "10-9-8-7-6-5"_expect); + + CHECK (materialise ( + treeExplore(CountDown{10}) + .iterWhile([](uint j){ return j > 5; }) + ) + == "10-9-8-7-6"_expect); + + CHECK (materialise ( + treeExplore(CountDown{10}) + .iterWhile([](int j){ return j > -5; }) + ) + == "10-9-8-7-6-5-4-3-2-1"_expect); + + CHECK (materialise ( + treeExplore(CountDown{10}) + .iterWhile([](uint j){ return j > 25; }) + ) + == ""_expect); + } + + + /** @test add a filtering predicate into the pipeline. * As in all the previously demonstrated cases, also the _filtering_ is added as decorator, * wrapping the source and all previously attached decoration layers. And in a similar way, @@ -843,7 +880,7 @@ namespace test{ treeExplore(CountDown{10}) .filter([](uint j){ return j % 2; }) ) - == "9-7-5-3-1"); + == "9-7-5-3-1"_expect); // Filter may lead to consuming util exhaustion... @@ -875,7 +912,7 @@ namespace test{ .transform([](float f){ return 0.55 + 2*f; }) .filter([](CountDown& core){ return core.p % 2; }) ) - == "18.55-14.55-10.55"); + == "18.55-14.55-10.55"_expect); @@ -887,7 +924,7 @@ namespace test{ .filter([](uint i){ return i%2 == 0; }) .expandAll() // Note: sends the expandChildren down through the filter ) - == "10-8-6-4-2-2-6-4-2-2"); + == "10-8-6-4-2-2-6-4-2-2"_expect); @@ -911,7 +948,7 @@ namespace test{ }); CHECK (materialise(kk) - == "14-12-10-8-6-4-2-14-12"); + == "14-12-10-8-6-4-2-14-12"_expect); // Explanation: // The source starts at 10, but since the toggle is false, // none of the initial values makes it though to the result. @@ -923,7 +960,7 @@ namespace test{ // the rest of the original sequence, 7,6 (which stops above 5). CHECK (materialise(kk.filter([](long i){ return i % 7; })) - == "12-10-8-6-4-2-12"); + == "12-10-8-6-4-2-12"_expect); // Explanation: // Since the original TreeExplorer was assigned to variable kk, // the materialise()-Function got a lvalue-ref and thus made a copy diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 62b2a464a..82846a6c9 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -56926,6 +56926,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +

+ das heißt, diese Funktion darf es nur einmal geben, und das muß dann »die« einschlägige Implementierung sein, ohne Wenn und Aber +

+ +
+
+
+
+ + + + + + + + + + + +

+ also... +

+
    +
  • + nimmt einen Lumiera Forward-Iterator +
  • +
  • + verpackt ihn als »state-core« +
  • +
+ +
+
+ + + +
+
+ + + + + + +

+ optional die Möglichkeit für einen getriggerten Stop vorsehen +

+ +
+ + + + + + + +

+ denn der Standard-Fall ist die unendliche Sequenz — und diese darf keinerlei unnötigen Ballast haben; weder einen Funktor (storage!), noch einen letztlich unnötigen Prädikat-Aufruf (denn sie läuft „für immer“) +

+ +
+ +
+ + + + + + + + + + + + + + +
    +
  • + Hilfsfunktionen fehlen (z.B. Adaptieren des Funktors an StateCore) +
  • +
  • + TreeExplorer sollte itertools nicht includieren (gegenwärtig tut er's aber nur wegen IterSource, und das könnte man refactorn) +
  • +
+ +
+
+ + + +
+ + + + + + + + + + +
+
@@ -73242,6 +73371,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + @@ -73259,11 +73392,15 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + + + - @@ -73328,6 +73465,35 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + +

+ ...denn eine »state core« wird automatisch von TreeExplorer erkannt und adaptiert... +

+ +
+ +
+ + + + + + +

+ ...denn TreeExplorer hat die Eigenschaft, von der »state-core« zu erben, und damit ihr public-API nach außen durchzureichen — im Besonderen auch für obere Layer in der Pipeline +

+ +
+ +
+
@@ -73476,7 +73642,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -73491,12 +73657,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + + @@ -73515,6 +73682,19 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + +

+ die aktuelle Frame-Nummer ist nun nur noch ein lokales Detail in der Planning-Pipeline; für den re-Trigger-Job genügt eine Zeitangabe, aus der dann eine realTime-Deadline abgeleitet wird +

+ +
+ +