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
This commit is contained in:
parent
fa15a38016
commit
ab2a6b2fce
2 changed files with 397 additions and 0 deletions
86
src/lib/iter-explorer.hpp
Normal file
86
src/lib/iter-explorer.hpp
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
ITER-EXPLORER.hpp - building blocks for iterator evaluation strategies
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2012, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
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 <b>Lumiera Forward Iterators</b> 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 <boost/type_traits/remove_const.hpp>
|
||||
|
||||
|
||||
namespace lib {
|
||||
|
||||
|
||||
namespace { // internal helpers
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Adapter for building....
|
||||
* TODO write type comment
|
||||
*/
|
||||
template<class POS, class CON>
|
||||
class IterExplorer
|
||||
// : public lib::BoolCheckable<IterAdapter<POS,CON> >
|
||||
{
|
||||
// CON source_;
|
||||
// mutable POS pos_;
|
||||
|
||||
public:
|
||||
// typedef typename iter::TypeBinding<POS>::pointer pointer;
|
||||
// typedef typename iter::TypeBinding<POS>::reference reference;
|
||||
// typedef typename iter::TypeBinding<POS>::value_type value_type;
|
||||
|
||||
|
||||
protected: /* === xxx === */
|
||||
|
||||
/** */
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace lib
|
||||
#endif
|
||||
311
tests/lib/iter-explorer-test.cpp
Normal file
311
tests/lib/iter-explorer-test.cpp
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
/*
|
||||
IterExplorer(Test) - verify evaluation patterns built using iterators
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2012, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
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 <boost/lexical_cast.hpp>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
|
||||
|
||||
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<NumberSequence> 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<NumberSequence, linked_elements::NoOwnership> IterContainer;
|
||||
typedef IterContainer::const_iterator IterIter;
|
||||
|
||||
typedef ChainedIters<IterIter> 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<NumberSequence> 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<NumberSequence> 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
|
||||
|
||||
Loading…
Reference in a new issue