diff --git a/research/try.cpp b/research/try.cpp index cea4acedb..3dd947e0f 100644 --- a/research/try.cpp +++ b/research/try.cpp @@ -48,28 +48,11 @@ // 08/22 - techniques to supply additional feature selectors to a constructor call // 10/23 - search for ways to detect signatures of member functions and functors uniformly // 11/23 - prototype for a builder-DSL to configure a functor to draw and map random values +// 11/23 - prototype for grouping from iterator /** @file try.cpp - * Prototyping to find a suitable DSL to configure drawing of random numbers and mapping results. - * The underlying implementation shall be extracted from (and later used by) TestChainLoad; the - * random numbers will be derived from node hash values and must be mapped to yield parameters - * limited to a very small value range. While numerically simple, this turns out to be rather - * error-prone, hence the desire to put a DSL in front. The challenge however arises from - * the additional requirement to support various usage patters, all with minimal specs. - * - * The following code lays out the ground structure, while treating Spec as a distinct - * type, which is then mixed into Draw. This logical separation basically was led me to the - * final solution: Draw both _is_ a function and _embodies_ the implementation of this function. - * This somewhat surprising layout is what enables use as a DSL builder, because it allows both - * to have the _builder use_ and the _converter use_ in the same class, even allowing to _define_ - * a Draw by giving a function which _produces_ a (dynamically parametrised) Draw. - * - * In this prototype, all of the functor adaptation is also part of the Draw template; for the - * real implementation this will have to be supplied at usage site through a traits template, - * otherwise it would not be possible to integrate seamlessly with custom data sources (as - * happens in the intended use case, where actually a Node is the data source) - * @note transformed into a generic library component for usage by vault::gear::TestChainLoad + * Investigate how best to integrate a grouping device into the iterator pipeline framework. */ typedef unsigned int uint; @@ -78,239 +61,186 @@ typedef unsigned int uint; #include "lib/format-cout.hpp" #include "lib/test/test-helper.hpp" #include "lib/test/diagnostic-output.hpp" +#include "lib/format-util.hpp" #include "lib/util.hpp" -#include "lib/meta/function.hpp" -#include +#include "lib/iter-explorer.hpp" +#include "lib/test/test-coll.hpp" #include +#include -using lib::meta::_Fun; -using std::function; using std::forward; using std::move; -template -struct Limited - { - static constexpr T minVal() { return T(0); } - static constexpr T maxVal() { return max; } +namespace lib { + namespace iter_explorer { - T val; - - template - Limited (X raw) - : val(util::limited (X(minVal()), raw, X(maxVal()))) - { } - }; - -template -struct Spec - { - using Lim = Limited; - static constexpr double CAP_EPSILON = 0.0001; + template + class Groupy + : public SRC + { + static_assert(can_IterForEach::value, "Lumiera Iterator required as source"); - double probability{0}; - T maxResult{Lim::maxVal()}; - - Spec() = default; - - explicit - Spec (double p) : probability{p}{ } - - Lim - limited (double val) - { - if (probability == 0.0 or val == 0.0) - return Lim{0}; - double q = (1.0 - probability); - val -= q; - val /= probability; - val *= maxResult; - val += 1 + CAP_EPSILON; - return Lim{val}; - } - - }; - -template -struct Draw - : Spec - , function(size_t)> - { - using Spc = Spec; - using Lim = typename Spc::Lim; - using Fun = function; - - Draw() - : Spc{} - , Fun{[this](size_t hash){ return Spc::limited (asRand (hash)); }} - { } - - template - Draw(FUN&& fun) - : Spc{1.0} - , Fun{adaptOut(adaptIn(std::forward (fun)))} - { } - - - Draw&& - probability (double p) - { - Spc::probability = p; - return move (*this); - } - - Draw&& - maxVal (uint m) - { - Spc::maxResult = m; - return move (*this); - } - - template - Draw&& - mapping (FUN&& fun) - { - Fun(*this) = adaptOut(adaptIn(std::forward (fun))); - return move (*this); - } - - - - double - asRand (size_t hash) - { - return double(hash % 256)/256; - } - - /** - * @internal helper to expose the signature `size_t(size_t)` - * by wrapping a given lambda or functor. - */ - template - struct Adaptor - { - static_assert (not sizeof(SIG), "Unable to adapt given functor."); - }; - - template - struct Adaptor - { - template - static decltype(auto) - build (FUN&& fun) + protected: + using Group = std::array; + using Iter = typename Group::iterator; + union Buffer { - return std::forward(fun); + char storage[sizeof(Group)]; + Group group; + + Iter begin() { return group.begin();} + Iter end() { return group.end(); } + }; + Buffer buff_; + uint pos_{0}; + + + public: + using value_type = Group; + using reference = Group&; + using pointer = Group*; + + Groupy() =default; + // inherited default copy operations + + Groupy (SRC&& dataSrc) + : SRC{move (dataSrc)} + { + pullGroup(); // initially pull to establish the invariant + } + + + /** + * Iterate over the Elements in the current group. + * @return a Lumiera Forward Iterator with value type RES + */ + auto + getGroupedElms() + { + ENSURE (buff_.begin()+pos_ <= buff_.end()); + // Array iterators are actually pointers + return RangeIter{buff_.begin(), buff_.begin()+pos_}; + } + + /** + * Retrieve the tail elements produced by the source, + * which did not suffice to fill a full group. + * @remark getRest() is NIL during regular iteration, but + * possibly yields elements when checkPoint() = false; + */ + auto + getRestElms() + { + return checkPoint()? RangeIter() + : getGroupedElms(); + } + + /** refresh state when other layers manipulate the source sequence. + * @note possibly pulls to re-establish the invariant */ + void + expandChildren() + { + SRC::expandChildren(); + pullGroup(); + } + + public: /* === Iteration control API for IterableDecorator === */ + + bool + checkPoint() const + { + return pos_ == grp; + } + + reference + yield() const + { + return unConst(buff_).group; + } + + void + iterNext() + { + pullGroup(); + } + + + protected: + SRC& + srcIter() const + { + return unConst(*this); + } + + /** @note establishes the invariant: + * source has been consumed to fill a group */ + void + pullGroup () + { + for (pos_=0 + ; pos_ - struct Adaptor - { - template - static auto - build (FUN&& fun) - { - return [functor=std::forward(fun)] - (size_t) - { - return functor(); - }; - } - }; + template + auto + groupy (IT&& src) + { + using Value = typename meta::ValueTypeBinding::value_type; + using ResCore = Groupy; + using ResIter = typename _DecoratorTraits::SrcIter; + + return IterExplorer (ResCore {move(src)}); + } + }//iter_explorer +}//lib + +using lib::test::getTestSeq_int; +using lib::test::VecI; + + /** Diagnostic helper: join all the elements from a _copy_ of the iterator */ + template + inline string + materialise (II&& ii) + { + return util::join (std::forward (ii), "-"); + } + +template +void +test() + { + VecI vec1 = getTestSeq_int (num); + cout <<"---"< - decltype(auto) - adaptIn (FUN&& fun) - { - static_assert (lib::meta::_Fun(), "Need something function-like."); - static_assert (lib::meta::_Fun::ARITY <= 1, "Function with zero or one argument expected."); - - using Sig = typename lib::meta::_Fun::Sig; - - return Adaptor::build (forward (fun)); - } - - template - decltype(auto) - adaptOut (FUN&& fun) - { - static_assert (lib::meta::_Fun(), "Need something function-like."); - static_assert (lib::meta::_Fun::ARITY ==1, "Function with exactly one argument required."); - - using Res = typename lib::meta::_Fun::Ret; - - if constexpr (std::is_same_v) - return std::forward(fun); - else - if constexpr (std::is_same_v) - return [functor=std::forward(fun), this] - (size_t rawHash) - { - size_t hash = functor(rawHash); - double randomNum = asRand (hash); - return Spc::limited (randomNum); - }; - else - if constexpr (std::is_same_v) - return [functor=std::forward(fun), this] - (size_t rawHash) - { - double randomNum = functor(rawHash); - return Spc::limited (randomNum); - }; - else - if constexpr (std::is_same_v) - return [functor=std::forward(fun), this] - (size_t rawHash) - { - Draw parametricDraw = functor(rawHash); - return parametricDraw (rawHash); - }; - else - static_assert (not sizeof(Res), "unable to adapt / handle result type"); - NOTREACHED("Handle based on return type"); - } - - }; + auto it = lib::explore(vec1); + auto groupie = lib::explore(lib::iter_explorer::groupy (move(it))) + .transform([](auto it){ return "["+util::join(*it)+"]"; }); + + for ( ;groupie; ++groupie) + cout << *groupie<<"-"; + + CHECK (not groupie); + CHECK (groupie.getGroupedElms()); + CHECK (groupie.getRestElms()); + for (auto r = groupie.getRestElms() ;r; ++r) + cout << *r<<"+"; + cout << endl; + } int main (int, char**) { - using D = Draw; - using L = typename D::Lim; - using S = typename D::Spc; - D draw; -SHOW_EXPR(draw) -SHOW_EXPR(draw(5).val) - - draw = D{[](size_t i){ return 0.75; }}; -SHOW_EXPR(draw(5).val) - - draw = D{}.probability(0.25).maxVal(5); -SHOW_EXPR(draw(256-65).val) -SHOW_EXPR(draw(256-64).val) -SHOW_EXPR(draw(256-64+ 1).val) -SHOW_EXPR(draw(256-64+10).val) -SHOW_EXPR(draw(256-64+11).val) -SHOW_EXPR(draw(256-64+12).val) -SHOW_EXPR(draw(256-64+13).val) -SHOW_EXPR(draw(256-64+23).val) -SHOW_EXPR(draw(256-64+24).val) -SHOW_EXPR(draw(256-64+25).val) -SHOW_EXPR(draw(256-64+26).val) -SHOW_EXPR(draw(256-64+36).val) -SHOW_EXPR(draw(256-64+37).val) -SHOW_EXPR(draw(256-64+38).val) -SHOW_EXPR(draw(256-64+39).val) -SHOW_EXPR(draw(256-64+49).val) -SHOW_EXPR(draw(256-64+50).val) -SHOW_EXPR(draw(256-64+51).val) -SHOW_EXPR(draw(256-64+52).val) -SHOW_EXPR(draw(256-64+62).val) -SHOW_EXPR(draw(256-64+63).val) -SHOW_EXPR(draw(256).val) + test<10,3>(); + test<13,5>(); + test<55,23>(); + test<23,55>(); cout << "\n.gulp.\n"; return 0; diff --git a/src/lib/iter-explorer.hpp b/src/lib/iter-explorer.hpp index e0f6b65ef..025b385db 100644 --- a/src/lib/iter-explorer.hpp +++ b/src/lib/iter-explorer.hpp @@ -849,6 +849,128 @@ namespace lib { + /** + * @internal Decorator for IterExplorer to group consecutive elements into fixed sized chunks. + * One group of elements is always prepared eagerly, and then the next one on iteration. + * The group is packaged into a std::array, returning a _reference_ into the internal buffer. + * If there are leftover elements at the end of the source sequence, which are not sufficient + * to fill a full group, these can be retrieved through the special API getRestElms(), which + * returns an iterator. + */ + template + class Grouping + : public SRC + { + static_assert(can_IterForEach::value, "Lumiera Iterator required as source"); + + protected: + using Group = std::array; + using Iter = typename Group::iterator; + struct Buffer + { + char storage[sizeof(Group)]; + + Group& group() { return reinterpret_cast (storage); } + + Iter begin() { return group().begin();} + Iter end() { return group().end(); } + }; + Buffer buff_; + uint pos_{0}; + + + public: + using value_type = Group; + using reference = Group&; + using pointer = Group*; + + Grouping() =default; + // inherited default copy operations + + Grouping (SRC&& dataSrc) + : SRC{move (dataSrc)} + { + pullGroup(); // initially pull to establish the invariant + } + + + /** + * Iterate over the Elements in the current group. + * @return a Lumiera Forward Iterator with value type RES + */ + auto + getGroupedElms() + { + ENSURE (buff_.begin()+pos_ <= buff_.end()); + // Array iterators are actually pointers + return RangeIter{buff_.begin(), buff_.begin()+pos_}; + } + + /** + * Retrieve the tail elements produced by the source, + * which did not suffice to fill a full group. + * @remark getRest() is NIL during regular iteration, but + * possibly yields elements when checkPoint() = false; + */ + auto + getRestElms() + { + return checkPoint()? RangeIter() + : getGroupedElms(); + } + + /** refresh state when other layers manipulate the source sequence. + * @note possibly pulls to re-establish the invariant */ + void + expandChildren() + { + SRC::expandChildren(); + pullGroup(); + } + + public: /* === Iteration control API for IterableDecorator === */ + + bool + checkPoint() const + { + return pos_ == grp; + } + + reference + yield() const + { + return unConst(buff_).group(); + } + + void + iterNext() + { + pullGroup(); + } + + + protected: + SRC& + srcIter() const + { + return unConst(*this); + } + + /** @note establishes the invariant: + * source has been consumed to fill a group */ + void + pullGroup () + { + for (pos_=0 + ; pos_ + auto + grouped() + { + using Value = typename meta::ValueTypeBinding::value_type; + using ResCore = iter_explorer::Grouping; + using ResIter = typename _DecoratorTraits::SrcIter; + + return IterExplorer (ResCore {move(*this)}); + } + + /** adapt this IterExplorer to iterate only as long as a condition holds true. * @return processing pipeline with attached [stop condition](\ref iter_explorer::StopTrigger) */ @@ -1577,6 +1719,25 @@ namespace lib { + /** preconfigured transformer to pass pointers down the pipeline */ + auto + asPtr() + { + using Val = typename meta::ValueTypeBinding::value_type; + static_assert (not std::is_pointer_v); + return IterExplorer::transform ([](Val& ref){ return &ref; }); + } + + /** preconfigured transformer to dereference pointers into references */ + auto + derefPtr() + { + using Ptr = typename meta::ValueTypeBinding::value_type; + return IterExplorer::transform ([](Ptr ptr){ return *ptr; }); + } + + + /** _terminal builder_ to package the processing pipeline as IterSource. * Invoking this function moves the whole iterator compound, as assembled by the preceding * builder calls, into heap allocated memory and returns an [iterator front-end](\ref IterExploreSource). diff --git a/tests/library/iter-explorer-test.cpp b/tests/library/iter-explorer-test.cpp index 8a596ed61..fed9c5a01 100644 --- a/tests/library/iter-explorer-test.cpp +++ b/tests/library/iter-explorer-test.cpp @@ -278,6 +278,7 @@ namespace test{ verify_expandOperation(); verify_expand_rootCurrent(); verify_transformOperation(); + verify_elementGroupingOperation(); verify_combinedExpandTransform(); verify_customProcessingLayer(); verify_scheduledExpansion(); @@ -667,6 +668,67 @@ namespace test{ } + /** @test package elements from the source pipeline into fixed-sized groups. + * These groups are implemented as std::array and initialised with the values + * yielded consecutively from the underlying source pipeline. The main iterator + * then yields a reference to this data (which can be unpacked conveniently + * by a structured binding, or processed as a STL container. + * Moreover, there is a secondary interface, allowing to iterate over the + * values stored in this group; this is also exposed for the rest, which + * did not suffice to fill a full group. + */ + void + verify_elementGroupingOperation() + { + auto showGroup = [](auto it){ return "["+util::join(*it)+"]"; }; + CHECK (materialise ( + explore(CountDown{10}) + .grouped<3>() + .transform(showGroup) + ) + == "[10, 9, 8]-[7, 6, 5]-[4, 3, 2]"_expect); + + + auto ii = explore(CountDown{23}) + .grouped<5>(); + CHECK(ii); + CHECK(ii.getGroupedElms()); + CHECK(not ii.getRestElms()); + CHECK (materialise(ii.getGroupedElms()) == "23-22-21-20-19"_expect); + + CHECK ( test::showType()== "array&"_expect); + + uint s = *(ii.getGroupedElms()); + for ( ; ii; ++ii) + { + auto grp = *ii; + CHECK (5 == grp.size()); + auto& [a,b,c,d,e] = grp; + CHECK (a == s); + CHECK (b == a-1); + CHECK (c == a-2); + CHECK (d == a-3); + CHECK (e == a-4); + CHECK (not ii.getRestElms()); + s -= 5; + } + CHECK (s < 5); + CHECK (s == 3); + + CHECK (not ii); + CHECK(ii.getGroupedElms()); + CHECK(ii.getRestElms()); + CHECK (materialise(ii.getGroupedElms()) == "3-2-1"_expect); + CHECK (materialise(ii.getRestElms()) == "3-2-1"_expect); + + + auto iii = explore(CountDown{4}) + .grouped<5>(); + CHECK (not iii); + CHECK (materialise(iii.getRestElms()) == "4-3-2-1"_expect); + } + + /** @test combine the recursion into children with a tail mapping operation. * Wile basically this is just the layering structure of IterExplorer put into action, * you should note one specific twist: the iter_explorer::Expander::expandChildren() call diff --git a/tests/vault/gear/test-chain-load-test.cpp b/tests/vault/gear/test-chain-load-test.cpp index 5fe80fd67..81dbbc9ba 100644 --- a/tests/vault/gear/test-chain-load-test.cpp +++ b/tests/vault/gear/test-chain-load-test.cpp @@ -48,6 +48,19 @@ namespace vault{ namespace gear { namespace test { + namespace { // shorthands and parameters for test... + + /** shorthand for specific parameters employed by the following tests */ + using ChainLoad32 = TestChainLoad<32,16>; + using Node = ChainLoad32::Node; + auto isStartNode = [](Node& n){ return isStart(n); }; + auto isInnerNode = [](Node& n){ return isInner(n); }; + auto isExitNode = [](Node& n){ return isExit(n); }; + + }//(End)test definitions + + + /*****************************************************************//** * @test verify a tool to generate synthetic load for Scheduler tests. @@ -126,7 +139,7 @@ namespace test { CHECK (n0.hash == 0); n0.calculate(); // but now hash calculation combines predecessors - CHECK (n0.hash == 6050854883719206282u); + CHECK (n0.hash == 0x53F8F4753B85558A); Node n00; // another Node... n00.addPred(n2) // just adding the predecessors in reversed order @@ -134,8 +147,8 @@ namespace test { CHECK (n00.hash == 0); n00.calculate(); // ==> hash is different, since it depends on order - CHECK (n00.hash == 17052526497278249714u); - CHECK (n0.hash == 6050854883719206282u); + CHECK (n00.hash == 0xECA6BE804934CAF2); + CHECK (n0.hash == 0x53F8F4753B85558A); CHECK (isSameObject (*n1.succ[0], n0)); CHECK (isSameObject (*n1.succ[1], n00)); @@ -146,9 +159,9 @@ namespace test { CHECK (isSameObject (*n0.pred[0], n1)); CHECK (isSameObject (*n0.pred[1], n2)); - CHECK (n00.hash == 17052526497278249714u); + CHECK (n00.hash == 0xECA6BE804934CAF2); n00.calculate(); // calculation is NOT idempotent (inherently statefull) - CHECK (n00.hash == 13151338213516862912u); + CHECK (n00.hash == 0xB682F06D29B165C0); CHECK (isnil (n0.succ)); // number of predecessors or successors properly accounted for CHECK (isnil (n00.succ)); @@ -175,12 +188,12 @@ namespace test { void verify_Topology() { - auto graph = TestChainLoad<32>{} + auto graph = ChainLoad32{} .buildToplolgy(); CHECK (graph.topLevel() == 31); CHECK (graph.getSeed() == 0); - CHECK (graph.getHash() == 6692160254289221734u); + CHECK (graph.getHash() == 0x5CDF544B70E59866); auto* node = & *graph.allNodes(); CHECK (node->hash == graph.getSeed()); @@ -210,7 +223,7 @@ namespace test { CHECK (steps == 31); CHECK (steps == graph.topLevel()); CHECK (node->hash == graph.getHash()); - CHECK (node->hash == 6692160254289221734u); + CHECK (node->hash == 0x5CDF544B70E59866); } // hash of the graph is hash of last node @@ -221,7 +234,7 @@ namespace test { void control_Topology() { - auto graph = TestChainLoad<32>{}; + ChainLoad32 graph; graph.expansionRule(graph.rule().probability(0.8).maxVal(1)) .pruningRule(graph.rule().probability(0.6)) @@ -231,32 +244,88 @@ namespace test { - /** @test set and propagate seed values and recalculate all node hashes - * @todo WIP 11/23 🔁 define ⟶ implement + /** @test set and propagate seed values and recalculate all node hashes. + * @remark This test uses parameter rules with some expansion and a + * pruning rule with 60% probability. This setup is known to + * create a sequence of tiny isolated trees with 4 nodes each; + * there are 8 such groups, each with a fork and two exit nodes; + * the last group is wired differently however, because there the + * limiting-mechanism of the topology generation activates to ensure + * that the last node is an exit node. The following code traverses + * all nodes grouped into 4-node clusters to verify this regular + * pattern and the calculated hashes. + * @todo WIP 11/23 ✔ define ⟶ ✔ implement */ void reseed_recalculate() { - auto graph = TestChainLoad<32>{}; + ChainLoad32 graph; graph.expansionRule(graph.rule().probability(0.8).maxVal(1)) .pruningRule(graph.rule().probability(0.6)) .buildToplolgy(); - using Node = TestChainLoad<32>::Node; - auto isStartNode = [](Node& n){ return isStart(n); }; - auto isExitNode = [](Node& n){ return isExit(n); }; - CHECK (8 == graph.allNodes().filter(isStartNode).count()); CHECK (15 == graph.allNodes().filter(isExitNode).count()); - CHECK (graph.getHash() == 14172386810742845390u); + CHECK (graph.getHash() == 0xC4AE6EB741C22FCE); + graph.allNodePtr().grouped<4>() + .foreach([&](auto group) + { // verify wiring pattern + // and the resulting exit hashes + auto& [a,b,c,d] = *group; + CHECK (isStart(a)); + CHECK (isInner(b)); + if (b->succ.size() == 2) + { + CHECK (isExit(c)); + CHECK (isExit(d)); + CHECK (c->hash == 0xAEDC04CFA2E5B999); + CHECK (d->hash == 0xAEDC04CFA2E5B999); + } + else + { // the last chunk is wired differently + CHECK (b->succ.size() == 1); + CHECK (b->succ[0] == c); + CHECK (isInner(c)); + CHECK (isExit(d)); + CHECK (graph.nodeID(d) == 31); + CHECK (d->hash == graph.getHash()); + } // this is the global exit node + }); + graph.setSeed(55).clearNodeHashes(); CHECK (graph.getSeed() == 55); CHECK (graph.getHash() == 0); + graph.allNodePtr().grouped<4>() + .foreach([&](auto group) + { // verify hashes have been reset + auto& [a,b,c,d] = *group; + CHECK (a->hash == 55); + CHECK (b->hash == 0); + CHECK (b->hash == 0); + CHECK (b->hash == 0); + }); graph.recalculate(); - CHECK (graph.getHash() == 6093128458724583708u); + CHECK (graph.getHash() == 0x548F240CE91A291C); + graph.allNodePtr().grouped<4>() + .foreach([&](auto group) + { // verify hashes were recalculated + // based on the new seed + auto& [a,b,c,d] = *group; + CHECK (a->hash == 55); + if (b->succ.size() == 2) + { + CHECK (c->hash == 0x7887993B0ED41395); + CHECK (d->hash == 0x7887993B0ED41395); + } + else + { + CHECK (graph.nodeID(d) == 31); + CHECK (d->hash == graph.getHash()); + } + }); } diff --git a/tests/vault/gear/test-chain-load.hpp b/tests/vault/gear/test-chain-load.hpp index e1ab3e753..bf0da4bb9 100644 --- a/tests/vault/gear/test-chain-load.hpp +++ b/tests/vault/gear/test-chain-load.hpp @@ -239,10 +239,12 @@ namespace test { } friend bool isStart (Node const& n) { return isnil (n.pred); }; - friend bool isStart (Node const* n) { return n and isnil (n->pred); }; - friend bool isExit (Node const& n) { return isnil (n.succ); }; - friend bool isExit (Node const* n) { return n and isnil (n->succ); }; + friend bool isInner (Node const& n) { return not (isStart(n) or isExit(n)); } + + friend bool isStart (Node const* n) { return n and isStart (*n); }; + friend bool isExit (Node const* n) { return n and isExit (*n); }; + friend bool isInner (Node const* n) { return n and isInner (*n); }; }; @@ -285,6 +287,16 @@ namespace test { { return lib::explore (*nodes_); } + auto + allNodePtr() + { + return allNodes().asPtr(); + } + + /** @return the node's index number, based on its storage location */ + size_t nodeID(Node const* n){ return size_t(n - &nodes_->front()); }; + size_t nodeID(Node const& n){ return nodeID (&n); }; + /* ===== topology control ===== */ @@ -477,8 +489,6 @@ namespace test { Code TOP {"shape=box, style=rounded"}; Code DEFAULT{}; - auto nodeID = [&](Node& n){ return size_t(&n - &nodes_->front()); }; - // prepare time-level zero size_t level(0); auto timeLevel = scope(level).rank("min "); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 6ca44d4be..1747f8b0b 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -99444,8 +99444,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -99460,13 +99460,87 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - - + + + + + + + + + + + + + + + +

+ sollte vergleichsweise einfach zu implementieren sein +

+ + +
+ + + + + +

+ ...will sagen, ich muß da nicht lang überlegen und kann es mehr oder weniger runterschreiben (auch wenn's ein paar Stunden braucht) +

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

+ ...es wäre zwar nicht notwendig, da in der Node-struct lauter pointer und einfache Werte gespeichert werden; aber eine Node-Kopie wäre ein hochgradig inkonsistentes Gebilde, da dann die Rück-Referenzen nicht mehr stimmen würden. +

+ + +
+
+ + + + + + +

+ ...das folgt aus dem Design von IterExplorer insgesamt. Es wäre hochgradig gefährlich, wenn man in der Gruppe Referenzen speichern würde. Wenn überhaupt, dann muß das explizit so angefordert werden, indem man in der Pipeline mit Pointern arbeitet. +

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