Lumiera  0.pre.03
»edit your freedom«
iter-explorer-test.cpp
Go to the documentation of this file.
1 /*
2  IterExplorer(Test) - verify tree expanding and backtracking iterator
3 
4  Copyright (C) Lumiera.org
5  2017, Hermann Vosseler <Ichthyostega@web.de>
6 
7  This program is free software; you can redistribute it and/or
8  modify it under the terms of the GNU General Public License as
9  published by the Free Software Foundation; either version 2 of
10  the License, or (at your option) any later version.
11 
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to the Free Software
19  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 
21 * *****************************************************/
22 
53 #include "lib/test/run.hpp"
54 #include "lib/test/test-helper.hpp"
55 #include "lib/iter-adapter-stl.hpp"
56 #include "lib/format-string.hpp"
57 #include "lib/format-cout.hpp"
58 #include "lib/format-util.hpp"
59 #include "lib/itertools.hpp"
60 #include "lib/util.hpp"
61 
62 #include "lib/iter-explorer.hpp"
63 #include "lib/meta/trait.hpp"
64 
65 #include <utility>
66 #include <vector>
67 #include <limits>
68 #include <string>
69 #include <tuple>
70 #include <cmath>
71 
72 
73 namespace lib {
74 namespace test{
75 
76  using ::Test;
77  using util::_Fmt;
78  using util::isnil;
79  using util::isSameObject;
81  using LERR_(ITER_EXHAUST);
82  using std::vector;
83  using std::string;
84 
85 
86  namespace { // test substrate: simple number sequence iterator
87 
92  struct CountDown
93  {
94  uint p,e;
95 
96  CountDown(uint start =0, uint end =0)
97  : p(start)
98  , e(end)
99  { }
100 
101  bool
102  checkPoint () const
103  {
104  return p > e;
105  }
106 
107  uint&
108  yield () const
109  {
110  return util::unConst (checkPoint()? p : e);
111  }
112 
113  void
114  iterNext ()
115  {
116  if (not checkPoint()) return;
117  --p;
118  }
119 
120  bool
121  operator== (CountDown const& o) const
122  {
123  return e == o.e
124  and p == o.p;
125  }
126  };
127 
128 
135  : public IterStateWrapper<uint, CountDown>
136  {
137 
138  public:
139  explicit
140  NumberSequence(uint start = 0)
142  { }
143  NumberSequence(uint start, uint end)
145  { }
146  };
147 
148 
149 
154  class RandomSeq
155  {
156  size_t lim_;
157  size_t cnt_;
158  char letter_;
159 
160  static char
161  rndLetter()
162  {
163  return 'A' + rand() % 26;
164  }
165 
166  public:
167  RandomSeq(int len =0)
168  : lim_{len>=0? len : std::numeric_limits<size_t>::max()}
169  , cnt_{0}
170  , letter_{rndLetter()}
171  { }
172 
173  bool
174  checkPoint () const
175  {
176  return cnt_ < lim_;
177  }
178 
179  char&
180  yield () const
181  {
182  return unConst(this)->letter_;
183  }
184 
185  void
186  iterNext ()
187  {
188  ASSERT (checkPoint());
189  ++cnt_;
190  letter_ = rndLetter();
191  }
192  };
193 
194 
196  template<class II>
197  inline string
198  materialise (II&& ii)
199  {
200  return util::join (std::forward<II> (ii), "-");
201  }
202 
204  template<class II>
205  inline void
206  pullOut (II & ii)
207  {
208  while (ii)
209  {
210  cout << *ii;
211  if (++ii) cout << "-";
212  }
213  cout << endl;
214  }
215 
216  } // (END) test helpers
217 
218 
219 
220 
221 
222 
223 
224  /*******************************************************************/
270  class IterExplorer_test : public Test
271  {
272 
273  virtual void
274  run (Arg)
275  {
276  verify_wrappedState();
277  verify_wrappedIterator();
278 
279  verify_expandOperation();
280  verify_expand_rootCurrent();
281  verify_transformOperation();
282  verify_elementGroupingOperation();
283  verify_aggregatingGroupItration();
284  verify_combinedExpandTransform();
285  verify_customProcessingLayer();
286  verify_scheduledExpansion();
287  verify_untilStopTrigger();
288  verify_FilterIterator();
289  verify_FilterChanges();
290  verify_asIterSource();
291  verify_IterSource();
292  verify_reduceVal();
293  verify_effuse();
294 
295  verify_depthFirstExploration();
296  demonstrate_LayeredEvaluation();
297  }
298 
299 
300 
304  void
306  {
307  auto ii = explore (CountDown{5,0});
308  CHECK (!isnil (ii));
309  CHECK (5 == *ii);
310  ++ii;
311  CHECK (4 == *ii);
312  pullOut(ii);
313  CHECK ( isnil (ii));
314  CHECK (!ii);
315 
316  VERIFY_ERROR (ITER_EXHAUST, *ii );
317  VERIFY_ERROR (ITER_EXHAUST, ++ii );
318 
319  ii = explore (CountDown{5});
320  CHECK (materialise(ii) == "5-4-3-2-1");
321  ii = explore (CountDown{7,4});
322  CHECK (materialise(ii) == "7-6-5");
323  ii = explore (CountDown{});
324  CHECK ( isnil (ii));
325  CHECK (!ii);
326  }
327 
328 
330  void
332  {
333  vector<int> numz{1,-2,3,-5,8,-13};
334  auto ii = eachElm(numz);
335  CHECK (!isnil (ii));
336  CHECK (1 == *ii);
337  ++ii;
338  CHECK (-2 == *ii);
339 
340  auto jj = explore(ii);
341  CHECK (!isnil (jj));
342  CHECK (-2 == *jj);
343  ++jj;
344  CHECK (3 == *jj);
345 
346  // we passed a LValue-Ref, thus a copy was made
347  CHECK (-2 == *ii);
348 
349  CHECK (materialise(ii) == "-2-3--5-8--13");
350  CHECK (materialise(jj) == "3--5-8--13");
351 
352  // can even adapt STL container automatically
353  auto kk = explore(numz);
354  CHECK (!isnil (kk));
355  CHECK (1 == *kk);
356  CHECK (materialise(kk) == "1--2-3--5-8--13");
357  }
358 
359 
360 
395  void
397  {
398  /* == "monadic flatMap" == */
399 
400  verify_treeExpandingIterator(
401  explore(CountDown{5})
402  .expand([](uint j){ return CountDown{j-1}; }) // expand-functor: Val > StateCore
403  );
404 
405  verify_treeExpandingIterator(
406  explore(CountDown{5})
407  .expand([](uint j){ return NumberSequence{j-1}; }) // expand-functor: Val > Iter
408  ); // NOTE: different Iterator type than the source!
409 
410  // lambda with side-effect and return type different from source iter
411  vector<vector<uint>> childBuffer;
412  auto expandIntoChildBuffer = [&](uint j) -> vector<uint>&
413  {
414  childBuffer.emplace_back();
415  vector<uint>& childNumbz = childBuffer.back();
416  for (size_t i=0; i<j-1; ++i)
417  childNumbz.push_back(j-1 - i);
418  return childNumbz;
419  };
420 
421  verify_treeExpandingIterator(
422  explore(CountDown{5})
423  .expand(expandIntoChildBuffer) // expand-functor: Val > STL-container&
424  );
425 
426  // test routine called the expansion functor five times
427  CHECK (5 == childBuffer.size());
428 
429 
430 
431  /* == "state manipulation" use cases == */
432 
433  verify_treeExpandingIterator(
434  explore(CountDown{5})
435  .expand([](CountDown const& core){ return CountDown{ core.yield() - 1}; }) // expand-functor: StateCore const& -> StateCore
436  );
437 
438  verify_treeExpandingIterator(
439  explore(CountDown{5})
440  .expand([](CountDown core){ return NumberSequence{ core.yield() - 1}; }) // expand-functor: StateCore -> Iter
441  );
442 
443  verify_treeExpandingIterator(
444  explore(CountDown{5})
445  .expand([](auto & it){ return CountDown{ *it - 1}; }) // generic Lambda: Iter& -> StateCore
446  );
447 
448  verify_treeExpandingIterator(
449  explore(CountDown{5})
450  .expand([](auto it){ return decltype(it){ *it - 1}; }) // generic Lambda: Iter -> Iter
451  );
452  }
453 
454 
455  template<class EXP>
456  void
457  verify_treeExpandingIterator (EXP ii)
458  {
459  CHECK (!isnil (ii));
460  CHECK (5 == *ii);
461  ++ii;
462  CHECK (4 == *ii);
463 
464  CHECK (0 == ii.depth());
465  ii.expandChildren();
466  CHECK (3 == *ii);
467  CHECK (1 == ii.depth());
468  ++ii;
469  CHECK (2 == *ii);
470  CHECK (1 == ii.depth());
471  ii.expandChildren();
472  CHECK (1 == *ii);
473  CHECK (2 == ii.depth());
474  ++ii;
475  CHECK (1 == *ii);
476  CHECK (1 == ii.depth());
477  ++ii;
478  CHECK (3 == *ii);
479  CHECK (0 == ii.depth());
480  CHECK (materialise(ii) == "3-2-1");
481  ii.expandChildren();
482  CHECK (1 == ii.depth());
483  CHECK (materialise(ii) == "2-1-2-1");
484  ++++ii;
485  CHECK (0 == ii.depth());
486  CHECK (materialise(ii) == "2-1");
487  ii.expandChildren();
488  CHECK (1 == ii.depth());
489  CHECK (materialise(ii) == "1-1");
490  ++ii;
491  CHECK (0 == ii.depth());
492  CHECK (1 == *ii);
493  CHECK (materialise(ii) == "1");
494  ii.expandChildren();
495  CHECK (isnil (ii));
496  VERIFY_ERROR (ITER_EXHAUST, *ii );
497  VERIFY_ERROR (ITER_EXHAUST, ++ii );
498  }
499 
500 
508  void
510  {
511  auto tree = explore(CountDown{25})
512  .expand([](uint j){ return CountDown{j-1}; });
513 
514  CHECK (materialise(tree) == "25-24-23-22-21-20-19-18-17-16-15-14-13-12-11-10-9-8-7-6-5-4-3-2-1");
515 
516  CHECK (0 == tree.depth());
517  CHECK (25 == *tree);
518  ++tree;
519  ++tree;
520  ++tree;
521  ++tree;
522  CHECK (21 == *tree);
523  tree.expandChildren();
524  CHECK (1 == tree.depth());
525  ++tree;
526  ++tree;
527  ++tree;
528  ++tree;
529  ++tree;
530  CHECK (15 == *tree);
531  tree.expandChildren();
532  ++tree;
533  ++tree;
534  CHECK (2 == tree.depth());
535  CHECK (materialise(tree) == "12-11-10-9-8-7-6-5-4-3-2-1-" // this is the level-2 child sequence
536  "14-13-12-11-10-9-8-7-6-5-4-3-2-1-" // ...returning to the rest of the level-1 sequence
537  "20-19-18-17-16-15-14-13-12-11-10-9-8-7-6-5-4-3-2-1"); // ...followed by the rest of the original root sequence
538  CHECK (12 == *tree);
539 
540  tree.rootCurrent();
541  CHECK (12 == *tree);
542  CHECK (materialise(tree) == "12-11-10-9-8-7-6-5-4-3-2-1"); // note: level-2 continues unaltered, but level-1 and the original root are gone.
543  CHECK (0 == tree.depth());
544  }
545 
546 
547 
563  void
565  {
566  auto multiply = [](int v){ return 2*v; }; // functional map: value -> value
567 
568  _Fmt embrace{"≺%s≻"};
569  auto formatify = [&](auto it){ return string{embrace % *it}; }; // generic lambda: assumed to take an Iterator&
570 
571 
572  auto ii = explore(CountDown{7,4})
573  .transform(multiply)
574  ;
575 
576  CHECK (14 == *ii);
577  CHECK (14 == *ii);
578  ++ii;
579  CHECK (12 == *ii);
580  ++ii;
581  CHECK (10 == *ii);
582  ++ii;
583  CHECK (isnil (ii));
584  VERIFY_ERROR (ITER_EXHAUST, *ii );
585  VERIFY_ERROR (ITER_EXHAUST, ++ii );
586 
587 
588 
589  // demonstrate chaining of several transformation layers
590  vector<int64_t> numz{1,-2,3,-5,8,-13};
591 
592  CHECK ("≺1≻-≺-2≻-≺3≻-≺-5≻-≺8≻-≺-13≻" == materialise (explore(numz)
593  .transform(formatify)) );
594 
595  CHECK ("≺2≻-≺-4≻-≺6≻-≺-10≻-≺16≻-≺-26≻" == materialise (explore(numz)
596  .transform(multiply)
597  .transform(formatify)) );
598 
599  CHECK ("≺≺4≻≻-≺≺-8≻≻-≺≺12≻≻-≺≺-20≻≻-≺≺32≻≻-≺≺-52≻≻" == materialise (explore(numz)
600  .transform(multiply)
601  .transform(multiply)
602  .transform(formatify)
603  .transform(formatify)) );
604 
605 
606  // demonstrate the functor is evaluated only once per step
607  int fact = 3;
608 
609  auto jj = explore (CountDown{4})
610  .transform([&](int v)
611  {
612  v *=fact;
613  fact *= -2;
614  return v;
615  });
616  CHECK (3*4 == *jj);
617  CHECK (fact == -2*3);
618 
619  CHECK (3*4 == *jj);
620  CHECK (3*4 == *jj);
621 
622  ++jj;
623  CHECK (fact == -2*3); // NOTE : functor is evaluated on first demand
624  CHECK (-2*3*3 == *jj); // ...which happens on yield (access the iterator value)
625  CHECK (fact == 2*2*3); // and this also causes the side-effect
626  CHECK (-2*3*3 == *jj);
627  CHECK (-2*3*3 == *jj);
628  CHECK (fact == 2*2*3); // no further evaluation and thus no further side-effect
629 
630  ++jj;
631  CHECK (2*2*3*2 == *jj);
632  CHECK (fact == -2*2*2*3);
633 
634  fact = -23;
635  CHECK (2*2*3*2 == *jj);
636 
637  ++jj;
638  CHECK (fact == -23);
639  CHECK (-23*1 == *jj);
640  CHECK (fact == 2*23);
641 
642  ++jj;
643  CHECK (isnil (jj));
644  CHECK (fact == 2*23);
645 
646  VERIFY_ERROR (ITER_EXHAUST, *ii );
647  CHECK (fact == 2*23); // exhaustion detected on source and thus no further evaluation
648 
649 
650 
651  // demonstrate a transformer accessing the source state core...
652  // should not be relevant in practice, but works due to the generic adapters
653  auto kk = explore (CountDown{9,4})
654  .transform([](CountDown& core)
655  {
656  uint delta = core.p - core.e;
657  if (delta % 2 == 0)
658  --core.p; // EVIL EVIL
659  return delta;
660  });
661 
662  CHECK (5 == *kk); // the delta between 9 (start) and 4 (end)
663  ++kk;
664  CHECK (4 == *kk); // Core manipulated by SIDE-EFFECT at this point...
665  CHECK (4 == *kk); // ...but not yet obvious, since the result is cached
666  ++kk;
667  CHECK (2 == *kk); // Surprise -- someone ate my numberz...
668  ++kk;
669  CHECK (isnil (kk));
670  }
671 
672 
673 
683  void
685  {
686  auto showGroup = [](auto it){ return "["+util::join(*it)+"]"; };
687  CHECK (materialise (
688  explore(CountDown{10})
689  .grouped<3>()
690  .transform(showGroup)
691  )
692  == "[10, 9, 8]-[7, 6, 5]-[4, 3, 2]"_expect);
693 
694 
695  auto ii = explore(CountDown{23})
696  .grouped<5>();
697  CHECK(ii);
698  CHECK(ii.getGroupedElms());
699  CHECK(not ii.getRestElms());
700  CHECK (materialise(ii.getGroupedElms()) == "23-22-21-20-19"_expect);
701 
702  CHECK ( test::showType<decltype(*ii)>()== "array<unsigned int, 5ul>&"_expect);
703 
704  uint s = *(ii.getGroupedElms());
705  for ( ; ii; ++ii)
706  {
707  auto grp = *ii;
708  CHECK (5 == grp.size());
709  auto& [a,b,c,d,e] = grp;
710  CHECK (a == s);
711  CHECK (b == a-1);
712  CHECK (c == a-2);
713  CHECK (d == a-3);
714  CHECK (e == a-4);
715  CHECK (not ii.getRestElms());
716  s -= 5;
717  }
718  CHECK (s < 5);
719  CHECK (s == 3);
720 
721  CHECK (not ii);
722  CHECK(ii.getGroupedElms());
723  CHECK(ii.getRestElms());
724  CHECK (materialise(ii.getGroupedElms()) == "3-2-1"_expect);
725  CHECK (materialise(ii.getRestElms()) == "3-2-1"_expect);
726 
727 
728  auto iii = explore(CountDown{4})
729  .grouped<5>();
730  CHECK (not iii);
731  CHECK (materialise(iii.getRestElms()) == "4-3-2-1"_expect);
732  }
733 
734 
744  void
746  {
747  CHECK (materialise (
748  explore(CountDown{10})
749  .groupedBy(std::ilogbf)
750  )
751  == "27-22-5-1"_expect); // 10+9+8|7+6+5+4|3+2|1
752 
753  CHECK (materialise (
754  explore(CountDown{10})
755  .transform(util::toString<uint>)
756  .groupedBy([](auto& it) { return std::ilogbf (it.p); }) // note trickery: takes not the value, rather the iterator and
757  ) // accesses internals of CountDown, bypassing the transform layer above
758  == "1098-7654-32-1"_expect); // `+` does string concatenation
759 
760 
761  auto showGroup = [](auto it){ return "["+util::join(*it)+"]"; };
762  // elaborate form with custom aggregation...
763  CHECK (materialise (
764  explore(CountDown{10})
765  .groupedBy(std::ilogbf
766  ,[](vector<uint>& accum, uint val)
767  {
768  accum.push_back (val);
769  })
770  .transform(showGroup)
771  )
772  == "[10, 9, 8]-[7, 6, 5, 4]-[3, 2]-[1]"_expect);
773  }
774 
775 
798  void
800  {
801  auto ii = explore(CountDown{5})
802  .expand([](uint j){ return CountDown{j-1}; })
803  .transform([](int v){ return 2*v; })
804  ;
805 
806  CHECK ("int" == meta::typeStr(*ii)); // result type is what the last transformer yields
807  CHECK (10 == *ii);
808  ++ii;
809  CHECK (8 == *ii);
810  ii.expandChildren();
811  CHECK ("6-4-2-6-4-2" == materialise(ii) );
812 
813 
814  // the following contrived example demonstrates
815  // how intermediary processing steps may interact
816 
817  CHECK (materialise (
818  explore(CountDown{5})
819  .expand([](uint j){ return CountDown{j-1}; })
820  .transform([](int v){ return 2*v; })
821  .transform([](auto& it)
822  {
823  auto elm = *it;
824  if (elm == 6)
825  {
826  it.expandChildren(); // NOTE at that point we're forced to decide if
827  elm = *it * 10; // we want to return the parent or the 1st child
828  }
829  return elm;
830  })
831  .transform([](float f){ return 0.055 + f/2; })
832  )
833  == "5.055-4.055-20.055-1.055-2.055-1.055" );
834  }
835 
836 
842  template<class SRC>
844  : public SRC
845  {
846  using SRC::SRC;
847 
848  void
849  iterNext()
850  {
851  ++(*this);
852  if (*this)
853  ++(*this);
854  }
855  };
856 
862  void
864  {
865  CHECK (materialise(
866  explore(CountDown{7})
867  .processingLayer<MagicTestRubbish>()
868  )
869  == "7-5-3-1");
870 
871  CHECK (materialise(
872  explore(CountDown{7})
873  .transform([](uint v){ return 2*v; })
874  .processingLayer<MagicTestRubbish>()
875  .filter([](int v){ return v % 3; })
876  )
877  == "14-10-2");
878  }
879 
880 
881 
891  void
893  {
894  auto ii = explore(CountDown{6})
895  .expand([](uint j){ return CountDown{j-2}; })
896  .expandOnIteration();
897 
898  CHECK (!isnil (ii));
899  CHECK (6 == *ii);
900  ++ii;
901  CHECK (5 == *ii);
902  CHECK (ii.depth() == 0);
903 
904  ii.expandChildren();
905  CHECK (5 == *ii);
906  CHECK (ii.depth() == 0);
907  ++ii;
908  CHECK (3 == *ii);
909  CHECK (ii.depth() == 1);
910 
911  ii.expandChildren();
912  ii.expandChildren();
913  CHECK (ii.depth() == 1);
914  CHECK (3 == *ii);
915  ++ii;
916  CHECK (1 == *ii);
917  CHECK (ii.depth() == 2);
918  ++ii;
919  CHECK (2 == *ii);
920  CHECK (ii.depth() == 1);
921 
922  ii.expandChildren();
923  ++ii;
924  CHECK (1 == *ii);
925  CHECK (ii.depth() == 1);
926  ++ii;
927  CHECK (4 == *ii);
928  CHECK (ii.depth() == 0);
929  ++ii;
930  CHECK (3 == *ii);
931  ++ii;
932  CHECK (2 == *ii);
933  ++ii;
934  CHECK (1 == *ii);
935  ++ii;
936  CHECK (isnil (ii));
937  }
938 
939 
940 
947  void
949  {
950  CHECK (materialise (
951  explore (CountDown{10})
952  .iterUntil([](uint j){ return j < 5; })
953  )
954  == "10-9-8-7-6-5"_expect);
955 
956  CHECK (materialise (
957  explore (CountDown{10})
958  .iterWhile([](uint j){ return j > 5; })
959  )
960  == "10-9-8-7-6"_expect);
961 
962  CHECK (materialise (
963  explore (CountDown{10})
964  .iterWhile([](int j){ return j > -5; })
965  )
966  == "10-9-8-7-6-5-4-3-2-1"_expect);
967 
968  CHECK (materialise (
969  explore (CountDown{10})
970  .iterWhile([](uint j){ return j > 25; })
971  )
972  == ""_expect);
973  }
974 
975 
976 
983  void
985  {
986  // canonical example, using a clean side-effect free predicate based on element values
987  CHECK (materialise (
988  explore(CountDown{10})
989  .filter([](uint j){ return j % 2; })
990  )
991  == "9-7-5-3-1"_expect);
992 
993 
994  // Filter may lead to consuming util exhaustion...
995  auto ii = explore(CountDown{10})
996  .filter([](int j){ return j > 9; });
997 
998  CHECK (not isnil (ii));
999  CHECK (10 == *ii);
1000  ++ ii;
1001  CHECK (isnil (ii));
1002  VERIFY_ERROR (ITER_EXHAUST, ++ii );
1003 
1004 
1005  // none of the source elements can be approved here...
1006  auto jj = explore(CountDown{5})
1007  .filter([](int j){ return j > 9; });
1008 
1009  CHECK (isnil (jj));
1010 
1011 
1012 
1013  // a tricky example, where the predicate takes the source core as argument;
1014  // since the source core is embedded as baseclass, it can thus "undermine"
1015  // and bypass the layers configured in between; here the transformer changes
1016  // uint to float, but the filter interacts directly with the core and thus
1017  // judges based on the original values
1018  CHECK (materialise (
1019  explore(CountDown{10,4})
1020  .transform([](float f){ return 0.55 + 2*f; })
1021  .filter([](CountDown& core){ return core.p % 2; })
1022  )
1023  == "18.55-14.55-10.55"_expect);
1024 
1025 
1026 
1027  // contrived example to verify interplay of filtering and child expansion;
1028  // especially note that the filter is re-evaluated after expansion happened.
1029  CHECK (materialise (
1030  explore(CountDown{10})
1031  .expand([](uint i){ return CountDown{i%4==0? i-1 : 0}; }) // generate subtree at 8 and 4 ==> 10-9-8-7-6-5-4-3-2-1-3-2-1-7-6-5-4-3-2-1-3-2-1
1032  .filter([](uint i){ return i%2 == 0; })
1033  .expandAll() // Note: sends the expandChildren down through the filter
1034  )
1035  == "10-8-6-4-2-2-6-4-2-2"_expect);
1036 
1037 
1038 
1039  // another convoluted example to demonstrate
1040  // - a filter predicate with side-effect
1041  // - and moreover the predicate is a generic lambda
1042  // - accepting the iterator to trigger child expansion
1043  // - which also causes re-evaluation of the preceding transformer
1044  bool toggle = false;
1045  auto kk = explore(CountDown{10,5})
1046  .expand([](uint j){ return CountDown{j-1}; })
1047  .transform([](int v){ return 2*v; })
1048  .filter([&](auto& it)
1049  {
1050  if (*it == 16)
1051  {
1052  it.expandChildren();
1053  toggle = true;
1054  }
1055  return toggle;
1056  });
1057 
1058  CHECK (materialise(kk)
1059  == "14-12-10-8-6-4-2-14-12"_expect);
1060  // Explanation:
1061  // The source starts at 10, but since the toggle is false,
1062  // none of the initial values makes it though to the result.
1063  // The interspersed transformer doubles the source values, and
1064  // thus at source == 8 the trigger value (16) is hit. Thus the
1065  // filter now flips the context-bound toggle (side-effect) and
1066  // then expands children, which consumes current source value 8
1067  // to replace it with the sequence 7,6,5,4,3,2,1, followed by
1068  // the rest of the original sequence, 7,6 (which stops above 5).
1069 
1070  CHECK (materialise(kk.filter([](long i){ return i % 7; }))
1071  == "12-10-8-6-4-2-12"_expect);
1072  // Explanation:
1073  // Since the original IterExplorer was assigned to variable kk,
1074  // the materialise()-Function got a lvalue-ref and thus made a copy
1075  // of the whole compound. For that reason, the original state within
1076  // kk still rests at 7 -- because the filter evaluates eagerly, the
1077  // source was pulled right at construction until we reached the first
1078  // value to yield, which is the first child (7,....) within the
1079  // expanded sequence. But now, in the second call to materialise(),
1080  // we don't just copy, rather we add another filter layer on top,
1081  // which happens to filter away this first result (== 2*7), and
1082  // also the first element of the original sequence after the
1083  // expanded children
1084 
1085  // WARNING: kk is now defunct, since we moved it into the builder expression
1086  // and then moved the resulting extended iterator into materialise!
1087  }
1088 
1089 
1090 
1092  void
1094  {
1095  auto seq = explore(CountDown{20})
1096  .mutableFilter();
1097 
1098  auto takeEve = [](uint i){ return i%2 == 0; };
1099  auto takeTrd = [](uint i){ return i%3 == 0; };
1100 
1101  CHECK (20 == *seq);
1102  ++seq;
1103  CHECK (19 == *seq);
1104  CHECK (19 == *seq);
1105 
1106  seq.andFilter (takeEve);
1107  CHECK (18 == *seq);
1108  ++seq;
1109  CHECK (16 == *seq);
1110 
1111  seq.andFilter (takeTrd);
1112  CHECK (12 == *seq); // is divisible (by 2 AND by 3)
1113 
1114  seq.flipFilter();
1115  CHECK (11 == *seq); // not divisible (by 2 AND by 3)
1116  ++seq;
1117  CHECK (10 == *seq);
1118 
1119  seq.setNewFilter (takeTrd);
1120  CHECK ( 9 == *seq);
1121  ++seq;
1122  CHECK ( 6 == *seq);
1123 
1124  seq.orNotFilter (takeEve);
1125  CHECK ( 6 == *seq);
1126  ++seq;
1127  CHECK ( 5 == *seq); // disjunctive condition actually weakens the filter
1128  ++seq;
1129  CHECK ( 3 == *seq);
1130 
1131  // NOTE: arbitrary functors can be used/combined,
1132  // since they are adapted individually.
1133  // To demonstrate this, we use a functor accessing and
1134  // manipulating the state core by side effect...
1135  string buff{"."};
1136  seq.andNotFilter ([&](CountDown& core)
1137  {
1138  buff += util::toString(core.p) + ".";
1139  --core.p; // manipulate state core
1140  return core.p % 2; // return a number, not bool
1141  });
1142 
1143  CHECK ( 2 == *seq); // value in the core has been manipulated
1144  CHECK (".3." == buff); // the filter has been invoked once, and saw core == 3
1145 
1146  ++seq; // core == 2 is filtered by the existing other filter (== not take even)
1147  CHECK (".3.1." == buff); // the filter has been invoked again, and saw core == 1
1148  CHECK (0 == seq.p); // ...which he manipulated, so that core == 0
1149  CHECK (isnil (seq)); // .....and thus iteration end is detected
1150  VERIFY_ERROR (ITER_EXHAUST, *seq );
1151 
1152 
1153  // verify enabling and disabling...
1154  seq = explore(CountDown{10})
1155  .mutableFilter(takeTrd);
1156 
1157  CHECK (9 == *seq);
1158  seq.disableFilter();
1159  CHECK (9 == *seq);
1160  ++seq;
1161  CHECK (8 == *seq);
1162  seq.andNotFilter (takeEve);
1163  CHECK (7 == *seq);
1164  ++seq;
1165  CHECK (5 == *seq);
1166  seq.disableFilter();
1167  CHECK (5 == *seq);
1168  ++seq;
1169  CHECK (4 == *seq);
1170  ++seq;
1171  CHECK (3 == *seq);
1172  seq.flipFilter(); // everything rejected
1173  CHECK (isnil (seq));
1174  }
1175 
1176 
1177 
1178 
1181  void
1183  {
1184  auto accumulated = explore(CountDown{30})
1185  .transform([](int i){ return i-1; }) // note: implicitly converts uint -> int
1186  .resultSum();
1187 
1188  using Res = decltype(accumulated);
1189  CHECK (lib::test::showType<Res>() == "int"_expect);
1190 
1191  auto expectedSum = [](auto N){ return N*(N+1) / 2; };
1192  CHECK (accumulated == expectedSum(29));
1193 
1194  // In the general case an accessor and a junctor can be given...
1195  CHECK (explore(CountDown{10})
1196  .reduce([](int i){ return i - 0.5; } // accessor: produce a double
1197  ,[](string accu, float val)
1198  {
1199  return accu+">"+util::toString(val); // junctor: convert to String and combine with separator char
1200  }
1201  , string{">-"} // seedVal: starting point for the reduction; also defines result type
1202  )
1203  == ">->9.5>8.5>7.5>6.5>5.5>4.5>3.5>2.5>1.5>0.5"_expect);
1204 
1205  // If only the accessor is given, values are combined by std::plus...
1206  CHECK (explore(CountDown{9})
1207  .reduce([](auto it) -> string
1208  {
1209  return _Fmt{"○%s●"} % *it; // accessor: format into a string
1210  })
1211  == "○9●○8●○7●○6●○5●○4●○3●○2●○1●"_expect);
1212 
1213  // a predefined IDENTITY accessor takes values from the pipeline as-is
1214  CHECK (explore(CountDown{9})
1215  .reduce(iter_explorer::IDENTITY, std::minus<int>(), expectedSum(9))
1216  == 0);
1217  }
1218 
1219 
1220 
1223  void
1225  {
1226  auto solidified = explore(CountDown{20})
1227  .filter ([](uint i){ return i % 2; })
1228  .transform([](uint i){ return 0.5*i; })
1229  .effuse();
1230 
1231  using Res = decltype(solidified);
1232  CHECK (lib::test::showType<Res>() == "vector<double>"_expect);
1233  CHECK (util::join(solidified, "|") == "9.5|8.5|7.5|6.5|5.5|4.5|3.5|2.5|1.5|0.5"_expect);
1234  }
1235 
1236 
1237 
1238 
1260  void
1262  {
1263  IterSource<uint>::iterator sequence; // note `sequence` is polymorphic
1264  CHECK (isnil (sequence));
1265 
1266  sequence = explore(CountDown{20,10})
1267  .filter([](uint i){ return i % 2; })
1268  .asIterSource(); // note this terminal builder function
1269  // moves the whole pipeline onto the heap
1270  CHECK (not isnil (sequence));
1271  CHECK (19 == *sequence);
1272 
1273 
1274  // use one sequence as source to build another one
1275  sequence = explore(sequence)
1276  .transform([](uint i){ return i*2; })
1277  .asIterSource();
1278 
1279  CHECK (38 == *sequence);
1280  CHECK ("38-34-30-26-22" == materialise(sequence));
1281 
1282  // WARNING pitfall: `sequence` is a copyable iterator front-end
1283  // but holds onto the actual pipeline by shared-ptr
1284  // Thus, even while materialise() creates a copy,
1285  // the iteration state gets shared....
1286  CHECK (22 == *sequence);
1287  ++sequence; // ...and even worse, iteration end is only detected after increment
1288  CHECK (isnil (sequence));
1289 
1290 
1291  // extended API to invoke child expansion opaquely
1292  IterExploreSource<char> exploreIter;
1293  CHECK (isnil (exploreIter));
1294 
1295  exploreIter = explore(CountDown{20,10})
1296  .filter([](uint i){ return i % 2; })
1297  .transform([](uint i){ return i*2; })
1298  .filter([](int i){ return i>25; })
1299  .expand([](uint i){ return CountDown{i-10, 20}; })
1300  .transform([](uint u) -> char { return '@'+u-20; })
1301  .asIterSource();
1302 
1303 
1304  CHECK ('R' == *exploreIter); // 38-20 + '@'
1305  ++exploreIter;
1306  CHECK ('N' == *exploreIter); // 34-20 + '@'
1307 
1308  exploreIter.expandChildren(); // expand consumes the current element (34)
1309  // and injects the sequence (24...20[ instead
1310  CHECK ('D' == *exploreIter); // 34-10 == 24 and 'D' == 24-20 + '@'
1311 
1312  CHECK ("D-C-B-A-J-F" == materialise(exploreIter));
1313  } // note how the remainder of the original sequence is picked up with 'J'...
1314 
1315 
1316 
1317 
1329  void
1331  {
1332  class PrivateSource
1333  : public IterSource<uint>
1334  {
1335  public:
1336  virtual PrivateSource* expandChildren() const =0;
1337  };
1338 
1339  class VerySpecificIter
1340  : public WrappedLumieraIter<NumberSequence
1341  , PrivateSource >
1342  {
1343  public:
1344  VerySpecificIter(uint start)
1345  : WrappedLumieraIter(NumberSequence{start})
1346  { }
1347 
1348  virtual PrivateSource*
1349  expandChildren() const override
1350  {
1351  return new VerySpecificIter{*wrappedIter() - 2};
1352  }
1353 
1354  uint
1355  currentVal() const
1356  {
1357  return *wrappedIter();
1358  }
1359  };
1360 
1361 
1362  // simple standard case: create a new heap allocated IterSource implementation.
1363  // IterExplorer will take ownership (by smart-ptr) and build a Lumiera Iterator front-End
1364  CHECK ("7-6-5-4-3-2-1" == materialise (
1365  explore (new VerySpecificIter{7})));
1366 
1367 
1368  // missing source detected
1369  PrivateSource* niente = nullptr;
1370  CHECK (isnil (explore (niente)));
1371 
1372 
1373  // attach to an IterSource living here in local scope...
1374  VerySpecificIter vsit{5};
1375 
1376  // ...and build a child expansion on top, which calls through the PrivateSource-API
1377  // Effectively this means we do not know the concrete type of the "expanded children" iterator,
1378  // only that it adheres to the same IterSource sub-interface as used on the base iterator.
1379  auto ii = explore(vsit)
1380  .expand ([](PrivateSource& source){ return source.expandChildren(); });
1381 
1382  CHECK (not isnil (ii));
1383  CHECK (5 == *ii);
1384  CHECK (5 == vsit.currentVal());
1385  ++ii;
1386  CHECK (4 == *ii);
1387  CHECK (4 == vsit.currentVal());
1388 
1389  CHECK (0 == ii.depth());
1390  ii.expandChildren(); // note: calls through source's VTable to invoke VerySpecificIter::expandChildren()
1391  CHECK (1 == ii.depth());
1392 
1393  CHECK (2 == *ii);
1394  ++ii;
1395  CHECK (1 == *ii);
1396 
1397  CHECK (4 == vsit.currentVal()); // note: as long as expanded children are alive, the source pipeline is not pulled further
1398  CHECK (1 == ii.depth());
1399  ++ii;
1400  CHECK (0 == ii.depth()); // ... but now the children were exhausted and thus also the source advanced
1401  CHECK (3 == *ii);
1402  CHECK (3 == vsit.currentVal());
1403  ++ii;
1404  CHECK (2 == *ii);
1405  CHECK (2 == vsit.currentVal());
1406  ++ii;
1407  CHECK (1 == *ii);
1408  CHECK (1 == vsit.currentVal());
1409  ++ii;
1410  CHECK (isnil (ii));
1411  }
1412 
1413 
1414 
1432  void
1434  {
1435  CHECK (materialise(
1436  explore(CountDown{4})
1437  .expand([](uint j){ return CountDown{j-1}; })
1438  .expandAll()
1439  .transform([](int i){ return i*10; })
1440  )
1441  == "40-30-20-10-10-20-10-10-30-20-10-10-20-10-10");
1442 
1443 
1444  using std::get;
1445  using Tu2 = std::tuple<uint, uint>;
1446  auto summingExpander = [](Tu2 const& tup)
1447  {
1448  uint val = get<0>(tup);
1449  uint sum = get<1>(tup);
1450  return val? singleValIterator (Tu2{val-1, sum+val})
1451  : SingleValIter<Tu2>();
1452  };
1453 
1454  CHECK (materialise(
1455  explore(CountDown{4})
1456  .transform([](uint i){ return Tu2{i,0}; })
1457  .expand(summingExpander)
1458  .expandAll()
1459  .transform([](Tu2 res){ return get<1>(res); })
1460  )
1461  == "0-4-7-9-10-0-3-5-6-0-2-3-0-1");
1462  }
1463 
1464 
1465 
1489  void
1491  {
1492  // Layer-1: the search space with "hidden" implementation
1493  using DataSrc = IterExploreSource<char>;
1494  DataSrc searchSpace = explore(RandomSeq{-1})
1495  .expand([](char){ return RandomSeq{15}; })
1496  .asIterSource();
1497 
1498  // Layer-2: State for search algorithm
1499  struct State
1500  {
1501  DataSrc& src;
1502  string& toFind;
1503  vector<uint> protocol;
1504 
1505  State(DataSrc& s, string& t)
1506  : src{s}
1507  , toFind{t}
1508  , protocol{0}
1509  { }
1510 
1511  bool
1512  checkPoint() const
1513  {
1514  return bool{src};
1515  }
1516 
1517  State&
1518  yield() const
1519  {
1520  return *unConst(this);
1521  }
1522 
1523  void
1524  iterNext()
1525  {
1526  ++src;
1527  protocol.resize (1+src.depth());
1528  ++protocol.back();
1529  }
1530 
1531  void
1532  expandChildren()
1533  {
1534  src.expandChildren();
1535  protocol.resize (1+src.depth());
1536  }
1537 
1538  bool
1539  isMatch() const
1540  {
1541  ASSERT (src.depth() < toFind.size());
1542  return *src == toFind[src.depth()];
1543  }
1544  };
1545 
1546 
1547  // Layer-3: Evaluation pipeline to drive search
1548  string toFind = util::join (explore (RandomSeq{5}), "");
1549  cout << "Search in random tree: toFind = "<<toFind<<endl;
1550 
1551  auto theSearch = explore (State{searchSpace, toFind})
1552  .filter([](auto& it)
1553  {
1554  while (it->src.depth() < it->toFind.size() - 1
1555  and it->isMatch())
1556  it->expandChildren();
1557 
1558  return it->isMatch();
1559  });
1560 
1561 
1562  // perform the search over a random tree...
1563  CHECK (not isnil(theSearch));
1564  cout << "Protocol of the search: " << materialise(theSearch->protocol) <<endl;
1565  }
1566  };
1567 
1568 
1569 
1570  LAUNCHER (IterExplorer_test, "unit common");
1571 
1572 
1573 }} // namespace lib::test
1574 
This iteration _"state core" type_ describes a descending sequence of numbers yet to be delivered...
string materialise(II &&ii)
Diagnostic helper: join all the elements from a copy of the iterator.
Automatically use custom string conversion in C++ stream output.
auto explore(IT &&srcSeq)
start building a IterExplorer by suitably wrapping the given iterable source.
bool filter(Placement< DummyMO > const &candidate)
a filter predicate to pick some objects from a resultset.
Definition: run.hpp:49
demo of a custom processing layer interacting directly with the iteration mechanism.
ostringstream protocol
used to verify the test function calls
Front-end for printf-style string template interpolation.
bool operator==(PtrDerefIter< I1 > const &il, PtrDerefIter< I2 > const &ir)
Supporting equality comparisons...
#define VERIFY_ERROR(ERROR_ID, ERRONEOUS_STATEMENT)
Macro to verify that a statement indeed raises an exception.
A straight descending number sequence as basic test iterator.
Iterator front-end to manage and operate a IterExplorer pipeline opaquely.
A front-end for using printf-style formatting.
Implementation namespace for support and library code.
Iteration source interface to abstract a data source, which then can be accessed through IterAdapter ...
Definition: iter-source.hpp:88
Simple test class runner.
int reduce(Gtk::Button &icon)
attempt to reduce space consumption
Another Lumiera Forward Iterator building block, based on incorporating a state type right into the i...
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
A collection of frequently used helper functions to support unit testing.
_SeqT< CON >::Range eachElm(CON &coll)
auto singleValIterator(VAL &&something)
Build a SingleValIter: convenience free function shortcut, to pick up just any value and wrap it as L...
Definition: itertools.hpp:664
Helpers for type detection, type rewriting and metaprogramming.
Collection of small helpers and convenience shortcuts for diagnostics & formatting.
void pullOut(II &ii)
Diagnostic helper: "squeeze out" the given iterator until exhaustion.
Helpers for working with iterators based on the pipeline model.
Pseudo-Iterator to yield just a single value.
Definition: itertools.hpp:635
Building tree expanding and backtracking evaluations within hierarchical scopes.
Preconfigured adapters for some STL container standard usage situations.
Standard implementation of the IterSource interface: a wrapped "Lumiera Forward Iterator".
Another iteration _"state core"_ to produce a sequence of random numbers.
bool isSameObject(A const &a, B const &b)
compare plain object identity, bypassing any custom comparison operators.
Definition: util.hpp:372