From ab2a6b2fce50af031118957de8eb2a43dca8d6f6 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 14 May 2012 05:24:16 +0200 Subject: [PATCH] WIP brainstorming about a monadic iterator The idea is to avoid building a data structure for intermediary results, while still being able to process a variably sized and arbitrary shaped set of source data --- src/lib/iter-explorer.hpp | 86 +++++++++ tests/lib/iter-explorer-test.cpp | 311 +++++++++++++++++++++++++++++++ 2 files changed, 397 insertions(+) create mode 100644 src/lib/iter-explorer.hpp create mode 100644 tests/lib/iter-explorer-test.cpp diff --git a/src/lib/iter-explorer.hpp b/src/lib/iter-explorer.hpp new file mode 100644 index 000000000..aa27c363b --- /dev/null +++ b/src/lib/iter-explorer.hpp @@ -0,0 +1,86 @@ +/* + ITER-EXPLORER.hpp - building blocks for iterator evaluation strategies + + Copyright (C) Lumiera.org + 2012, Hermann Vosseler + + 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 iter-explorer.hpp + ** Helper template(s) for establishing various evaluation strategies for hierarchical data structures. + ** Based on the Lumiera Forward Iterators concept and using the basic IterAdaptor templates, + ** these components allow to implement typical evaluation strategies, like e.g. depth-first or + ** breadth-first exploration of a hierarchical structure. Since the access to this structure is + ** abstracted through the underlying iterator, what we effectively get is a functional datastructure. + ** The implementation is based on the IterableStateWrapper, which is one of the basic helper templates + ** provided by iter-adapter.hpp. + ** + ** @see IterExplorer_test.cpp + ** @see iter-adapter.hpp + ** @see itertools.hpp + ** @see IterSource (completely opaque iterator) + ** + */ + + +#ifndef LIB_ITER_EXPLORER_H +#define LIB_ITER_EXPLORER_H + + +#include "lib/error.hpp" +//#include "lib/bool-checkable.hpp" +#include "lib/iter-adapter.hpp" + +//#include + + +namespace lib { + + + namespace { // internal helpers + } + + + + /** + * Adapter for building.... + * TODO write type comment + */ + template + class IterExplorer +// : public lib::BoolCheckable > + { +// CON source_; +// mutable POS pos_; + + public: +// typedef typename iter::TypeBinding::pointer pointer; +// typedef typename iter::TypeBinding::reference reference; +// typedef typename iter::TypeBinding::value_type value_type; + + + protected: /* === xxx === */ + + /** */ + + private: + }; + + + +} // namespace lib +#endif diff --git a/tests/lib/iter-explorer-test.cpp b/tests/lib/iter-explorer-test.cpp new file mode 100644 index 000000000..f47f99000 --- /dev/null +++ b/tests/lib/iter-explorer-test.cpp @@ -0,0 +1,311 @@ +/* + IterExplorer(Test) - verify evaluation patterns built using iterators + + Copyright (C) Lumiera.org + 2012, Hermann Vosseler + + 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. + +* *****************************************************/ + + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "lib/util.hpp" +#include "lib/util-foreach.hpp" + +#include "lib/iter-explorer.hpp" +#include "lib/linked-elements.hpp" + +#include +#include +#include + + + +namespace lib { +namespace test{ + + using ::Test; +// using boost::lexical_cast; +// using util::for_each; +// using util::isnil; + using util::isSameObject; +// using std::vector; +// using std::cout; +// using std::endl; + using lib::LinkedElements; + using lumiera::error::LUMIERA_ERROR_ITER_EXHAUST; + + + namespace { // test dummy source iterator + + uint NUM_ELMS = 10; + + /** */ + class NumberSequence + { + }; + + } // (END) impl test dummy container + + + + + + + + /********************************************************************* + * @test use a simple source iterator yielding numbers + * to build various functional evaluation structures, + * based on the IterExplorer template. + * + * \par Explanation + * Both this test and the IterExplorer template might be bewildering + * and cryptic, unless you know the Monad design pattern. Monads are + * heavily used in functional programming, actually they originate + * from Category Theory. Basically, Monad is a pattern where we + * combine several computation steps in a specific way; but instead + * of intermingling the individual computation steps and their + * combination, the goal is to separate and isolate the mechanics + * of combination, so we can focus on the actual computation steps: + * The mechanics of combination are embedded into the Monad type, + * which acts as a kind of container, holding elements to be processed. + * The actual processing steps are then fed to the monad as parameters. + * + * Using the monad pattern is well suited when both the mechanics of + * combination and the individual computation steps tend to be complex. + * In such a situation, it is beneficial to develop and test both + * in isolation. The IterExplorer template applies this pattern + * to the task of processing a source sequence. Typically we use + * this in situations where we can't effort building elaborate + * data structures in (global) memory, but rather strive at + * doing everything on-the-fly. A typical example is the + * processing of a variably sized data set without + * using heap memory for intermediary results. + * + * @see IterExplorer + * @see IterAdapter + */ + class IterExplorer_test : public Test + { + + virtual void + run (Arg arg) + { + verifyChainedIterators (); + verifyComparisons (); + } + + + + /** @test a simple and practical helper built on top of IterExplorer. + * The resulting iterator \em combines and \em flattens a sequence + * of source iterators, resulting in a simple sequence accessible + * again as iterator. Here we verify the convenience / default + * implementation; it uses a STL container behind the scenes + * to keep track of all the added source iterators + */ + void + verifyChainedIterators () + { + typedef ChainedIters Chain; + + Chain ci = iter::chain (seq(5),seq(7),seq(9)); + + CHECK (!isnil (ci)); + pullOut (ci); + CHECK ( isnil (ci)); + VERIFY_ERROR (ITER_EXHAUST, *ci ); + VERIFY_ERROR (ITER_EXHAUST, ++ci ); + + CHECK (isnil(Chain())); + CHECK (iter::chain (NIL_Sequence)); + + // Iterator chaining "flattens" one level of indirection + NumberSequence s9 = seq(9); + ci = iter::chain (s9); + + for ( ; s9 && ci; ++s9, ++ci ) + CHECK (*s9 == *ci); + + CHECK (isnil(s9)); + CHECK (isnil(ci)); + + // Note: Iterator chain is created based on (shallow) copy + // of the source sequences. In case these have an independent + // per-instance state (like e.g. NumberSequence used for this test), + // then the created chain is independent from the source iterators. + s9 = seq(9); + ci = iter::chain (s9); + CHECK (0 == *s9); + CHECK (0 == *ci); + + pullOut (ci); + CHECK (isnil(ci)); + CHECK (0 == *s9); + pullOut (s9); + CHECK (isnil(s9)); + } + + + /** @test variation of the iterator chaining facility. + * This is the "raw" version without any convenience shortcuts. + * The source iterators are given as iterator yielding other iterators. + */ + void + verifyRawChainedIterators () + { + typedef LinkedElements IterContainer; + typedef IterContainer::const_iterator IterIter; + + typedef ChainedIters Chain; + + NumberSequence s5 (1,5); + NumberSequence s7 (5,8); + NumberSequence s9 (8,10); + + CHECK (1 == *s5); + CHECK (5 == *s7); + CHECK (8 == *s9); + + IterContainer srcIters; + srcIters.push (s9); + srcIters.push (s7); + srcIters.push (s5); + + IterIter iti(srcIters.begin()); + CHECK (!isnil (iti)); + CHECK (isSameObject (s5, *iti)); + + Chain chain(iti); + CHECK (!isnil (iti)); + CHECK (1 == *chain); + + ++chain; + CHECK (2 == *chain); + CHECK (2 == *s5); + CHECK (5 == *s7); + CHECK (8 == *s9); + + ++++chain; + CHECK (4 == *chain); + CHECK (4 == *s5); + CHECK (5 == *s7); + CHECK (8 == *s9); + + ++chain; + CHECK (5 == *chain); + CHECK (isnil(s5)); + CHECK (5 == *s7); + CHECK (8 == *s9); + + ++chain; + CHECK (6 == *chain); + CHECK (isnil(s5)); + CHECK (6 == *s7); + CHECK (8 == *s9); + + ++++chain; + CHECK (8 == *chain); + CHECK (isnil(s5)); + CHECK (isnil(s7)); + CHECK (8 == *s9); + + ++chain; + CHECK (9 == *chain); + CHECK (isnil(s5)); + CHECK (isnil(s7)); + CHECK (9 == *s9); + + ++chain; + CHECK (isnil(chain)); + CHECK (isnil(s5)); + CHECK (isnil(s7)); + CHECK (isnil(s9)); + } + + + + /** @test a depth-first visiting and exploration scheme + * of a tree like system, built on top of the IterExplorer monad. + * + * \par Test data structure + * We built a functional datastructure here, on the fly, while exploring it. + * The \c exploreChildren(m) function generates this tree like datastructure: + * For a given node with number value i, it returns the children (2*i-1) and 2*i + * -- but only up to the given maximum value m. + * If we start such a tree structure with a root node 1, and maximum = 8, this yields: + * \code + * ( 1 ) + * ( 1 2 ) + * ( 1 2 3 4 ) + * (1 2 3 4 5 6 7 8) + * \endcode + * This tree has no meaning in itself, beyond being an easy testbed for tree exploration schemes. + * + * \par How the exploration works + * We use a pre defined Template \link DepthFirstExplorer \endlink, which is built on top of IterExplorer. + * It contains the depth-first exploration strategy hardwired. Actually this effect is achieved by + * defining a specific way how to \em combine the results of a \em exploration -- the latter being + * the function which generates the data structure. To yield a depth-first exploration, all we have to do + * is to delve down immediately into the children, right after visiting the node itself. + * + * Now, when creating such a DepthFirstExplorer by wrapping a given source iterator, the result is again + * an iterator, but a specific iterator which at the same time is a Monad: It supports the \c >>= operation + * (also known as \em bind operator or \em flatMap operator). This operator takes as second argument a + * function, which in our case is the function to generate or explore the data structure. + * + * The result of applying this \c >>= operation is a \em transformed version of the source iterator, + * i.e. it is again an iterator, which yields the results of the exploration function, combined together + * in the order as defined by the built-in exploration strategy (here: depth first) + */ + void + verifyDepthFirstExploration () + { + typedef DepthFirstExplorer DepthFirst; + DepthFirst root (seq(1)); + + NumberSequence explorationResult = root >>= exploreChildren(8); + CHECK (materialise(explorationResult) == "1-1-1-1-2-2-3-4-2-3-5-6-4-7-8"); + } + + + + /** @test a breadth-first visiting and exploration scheme + * of a tree like system, built on top of the IterExplorer monad; + * here, an internal queue is used to explore the hierarchy in layers. + * The (functional) datastructure is the same, just we're visiting it + * differently here (in rows or layers). + */ + void + verifyBreadthFirstExploration () + { + typedef BreadthFirstExplorer BreadthFirst; + BreadthFirst root (seq(1)); + + NumberSequence explorationResult = root >>= exploreChildren(8); + CHECK (materialise(explorationResult) == "1-1-2-1-2-3-4-1-2-3-4-5-6-7-8"); + } + + + }; + + LAUNCHER (IterExplorer_test, "unit common"); + + +}} // namespace lib::test +