LUMIERA.clone/tests/lib/iter-explorer-test.cpp
Ichthyostega 378ebe21f0 Fix naming of Iteration control API functions (closes #410)
comes in handy now, since IterStateWrapper uses a similar API
2012-10-10 05:20:15 +02:00

444 lines
15 KiB
C++

/*
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 <sstream>
#include <vector>
namespace lib {
namespace iter {
template<>
struct TypeBinding<uint>
{
typedef uint value_type;
typedef uint const& reference;
typedef uint const* pointer;
};
}
namespace test{
using ::Test;
// using boost::lexical_cast;
// using util::for_each;
// using util::isnil;
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 State
{
uint p,e;
public:
State(uint start, uint end)
: p(start)
, e(end)
{ }
friend bool
checkPoint (State const& st)
{
return st.p < st.e;
}
friend uint&
yield (State const& st)
{
return util::unConst(checkPoint(st)? st.p : st.e);
}
friend void
iterNext (State & st)
{
if (!checkPoint(st)) return;
++st.p;
}
};
/** */
class NumberSequence
: public IterStateWrapper<uint, State>
{
public:
explicit
NumberSequence(uint end = 0)
: IterStateWrapper<uint,State> (State(0,end))
{ }
NumberSequence(uint start, uint end)
: IterStateWrapper<uint,State> (State(start,end))
{ }
};
inline NumberSequence
seq (uint end)
{
return NumberSequence(end);
}
inline NumberSequence
seq (uint start, uint end)
{
return NumberSequence(start, end);
}
NumberSequence NIL_Sequence;
/** Diagnostic helper: "squeeze out" the given iterator
* and join all yielded elements into a string
*/
template<class II>
inline string
materialise (II & ii)
{
std::ostringstream buff;
while (ii)
{
buff << *ii;
if (++ii) buff << "-";
}
return buff.str();
}
} // (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)
{
verifyStateAdapter();
UNIMPLEMENTED ("IterExplorer Monad");
verifyChainedIterators();
verifyRawChainedIterators();
verifyDepthFirstExploration();
verifyBreadthFirstExploration();
}
/** @test all of the following IterExplorer flavours are built on top
* of a special iterator adapter, centred at the notion of an iterable
* state element type. The actual iterator just embodies one element
* of this state representation, and typically there is not an hidden
* back-link to some kind of container in charge of the elements yielded
*/
void
verifyStateAdapter ()
{
NumberSequence ii = seq(5);
CHECK (!isnil (ii));
CHECK (0 == *ii);
CHECK (materialise(ii) == "0-1-2-3-4");
CHECK ( isnil (ii));
CHECK (!ii);
VERIFY_ERROR (ITER_EXHAUST, *ii );
VERIFY_ERROR (ITER_EXHAUST, ++ii );
ii = seq(5,8);
CHECK (materialise(ii) == "5-6-7");
ii = NIL_Sequence;
CHECK ( isnil (ii));
CHECK (!ii);
}
/** @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 ()
{
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #892
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));
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #892
}
/** @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 ()
{
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #892
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));
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #892
}
/** @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 ()
{
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #892
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");
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #892
}
/** @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 ()
{
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #892
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");
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #892
}
};
LAUNCHER (IterExplorer_test, "unit common");
}} // namespace lib::test