From 1c72cbb5997ea65294f32b3b6c2e2f0ec29530d8 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 2 Nov 2009 06:06:43 +0100 Subject: [PATCH] Iterator tools: (1) a filtering iterator --- src/lib/iter-adapter.hpp | 14 +- src/lib/itertools.hpp | 320 ++++++++++++++++++++++++++++++++ tests/40components.tests | 11 ++ tests/lib/iter-adapter-test.cpp | 54 +++++- tests/lib/itertools-test.cpp | 192 +++++++++++++++++++ 5 files changed, 582 insertions(+), 9 deletions(-) create mode 100644 src/lib/itertools.hpp create mode 100644 tests/lib/itertools-test.cpp diff --git a/src/lib/iter-adapter.hpp b/src/lib/iter-adapter.hpp index 5dc9bab54..b62bb96c7 100644 --- a/src/lib/iter-adapter.hpp +++ b/src/lib/iter-adapter.hpp @@ -113,6 +113,14 @@ namespace lib { typedef const TY* pointer; }; + + void + _throwIterExhausted() + { + throw lumiera::error::Invalid ("Can't iterate further", + lumiera::error::LUMIERA_ERROR_ITER_EXHAUST); + } + } @@ -252,8 +260,7 @@ namespace lib { _maybe_throw() const { if (!isValid()) - throw lumiera::error::Invalid ("Can't iterate further", - lumiera::error::LUMIERA_ERROR_ITER_EXHAUST); + _throwIterExhausted(); } /// comparison is allowed to access impl iterator @@ -371,8 +378,7 @@ namespace lib { _maybe_throw() const { if (!isValid()) - throw lumiera::error::Invalid ("Can't iterate further", - lumiera::error::LUMIERA_ERROR_ITER_EXHAUST); + _throwIterExhausted(); } }; diff --git a/src/lib/itertools.hpp b/src/lib/itertools.hpp new file mode 100644 index 000000000..3c541242a --- /dev/null +++ b/src/lib/itertools.hpp @@ -0,0 +1,320 @@ +/* + ITERTOOLS.hpp - collection of tools for building and combining iterators + + Copyright (C) Lumiera.org + 2009, 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 itertools.hpp + ** Helpers for working with iterators based on the pipeline model. + ** Iterators abstract from the underlying data container and provide + ** the contained data as a source to pull values from. Based on this + ** model, we can build pipelines, with filters, valves, junction points + ** and transforming facilities. The templates in this header enable such, + ** based on the Lumiera Forward Iterator concept. They build on generic + ** programming techniques, thus are intended to be combined at compile time + ** using definitive type information. Contrast this to an iterator model + ** as in Java's Commons-Collections, where Iterator is an Interface based + ** on virtual functions. Thus, the basic problem to overcome is the lack + ** of a single common interface, which could serve as foundation for + ** type inference. As a solution, we use a "onion" approach, where a + ** generic base gets configured with an active core, implementing + ** the filtering or processing functionality, while the base class + ** (IterTool) exposes the operations necessary to comply to the + ** Forward Iterator Concept. + ** + ** \par Filtering Iterator + ** The FilterIter template can be used to build a filter into a pipeline, + ** as it forwards only those elements from its source iterator, which pass + ** the predicate evaluation. Anything acceptable as ctor parameter for a + ** tr1::function object can be passed in as predicate, but of course the + ** signature must be sensible. Please note, that -- depending on the + ** predicate -- already the ctor or even a simple \c bool test might + ** pull and exhaust the source iterator completely, in an attempt + ** to find the first element passing the predicate test. + ** + ** @todo WIP WIP WIP + ** @todo see Ticket #347 + ** + ** @see IterAdapter + ** @see itertools-test.cpp + ** @see contents-query.hpp + */ + + +#ifndef LIB_ITERTOOLS_H +#define LIB_ITERTOOLS_H + + +#include "lib/bool-checkable.hpp" +#include "lib/iter-adapter.hpp" +#include "lib/util.hpp" + +#include + + + +namespace lib { + + using std::tr1::function; + using util::unConst; + + + + + /** + * A neutral \em identity-function core, + * also serving as point-of reference how any + * core is intended to work. Any core is intended + * to serve as inner part of an iterator tool template. + * - it provides the trait typedefs + * - it abstracts the "source" + * - it abstracts the local operation to be performed + * - the ctor of the core sets up the configuration. + * @note cores should be copyable without much overhead + */ + template + struct IdentityCore + { + mutable IT source_; + + IdentityCore (IT const& orig) + : source_(orig) + { } + + IT& + source () + { + return source_; + } + + IT const& + source () const + { + return source_; + } + + bool + evaluate () const + { + return bool(source_); + } + + typedef typename IT::pointer pointer; + typedef typename IT::reference reference; + typedef typename IT::value_type value_type; + }; + + + /** + * Standard functionality to build up any iterator tool. + * IterTool exposes the frontend functions necessary to + * comply to the Lumiera Forward Iterator concept. + * The protected part provides the building blocks + * to implement the actual processing/filter logic. + */ + template + class IterTool + : public lib::BoolCheckable > + { + + protected: /* iteration control */ + CORE core_; + + bool + hasData() const + { + return core_.evaluate() + || unConst(this)->iterate(); + } // skipping irrelevant results doesn't count as "mutation" + + bool + iterate () + { + if (!core_.source()) return false; + + do ++core_.source(); + while (core_.source() && !core_.evaluate()); + return core_.source(); + } + + void + _maybe_throw() const + { + if (!isValid()) + _throwIterExhausted(); + } + + + + public: + typedef typename CORE::pointer pointer; + typedef typename CORE::reference reference; + typedef typename CORE::value_type value_type; + + + IterTool (CORE const& setup) + : core_(setup) + { + hasData(); + } + + + + /* === lumiera forward iterator concept === */ + + reference + operator*() const + { + _maybe_throw(); + return *core_.source(); + } + + pointer + operator->() const + { + _maybe_throw(); + return core_.source(); + } + + IterTool& + operator++() + { + _maybe_throw(); + iterate(); + return *this; + } + + bool + isValid () const + { + return hasData(); + } + + bool + empty () const + { + return !isValid(); + } + + + /// comparison is allowed to access the source iterator + template + friend bool operator== (IterTool const& it1, IterTool const& it2); + }; + + + template + inline bool + operator== (IterTool const& it1, IterTool const& it2) + { + return it1.isValid() == it2.isValid() + && it1.core_.source() == it2.core_.source() + ; + } + + template + inline bool + operator!= (IterTool const& ito1, IterTool const& ito2) + { + return !(ito1 == ito2); + } + + + + + + + + + /** + * Implementation of the filter logic. + * This core stores a function object instance, + * passing each pulled source element to this + * predicate function for evaluation. + */ + template + struct FilterCore + : IdentityCore + { + typedef IdentityCore _Par; + typedef typename IT::reference Val; + + + function predicate_; + + bool + evaluate () const + { + return _Par::source() + && predicate_(*_Par::source()); + } + + + template + FilterCore (IT const& orig, PRED prediDef) + : _Par(orig) + , predicate_(prediDef) // induces a signature check + { } + }; + + + /** + * Iterator tool filtering pulled data according to a predicate + */ + template + class FilterIter + : public IterTool > + { + typedef FilterCore _Filter; + typedef IterTool<_Filter> _Impl; + + static bool acceptAll(typename _Filter::Val) { return true; } + + + public: + FilterIter () + : _Impl(FilterCore(IT(), acceptAll)) + { } + + template + FilterIter (IT const& src, PRED filterPredicate) + : _Impl(_Filter(src,filterPredicate)) + { } + + }; + + + /** Build a FilterIter: convenience free function shortcut, + * picking up the involved types automatically. + * @param filterPredicate to be invoked for each source element + * @return Iterator filtering contents of the source + */ + template + inline FilterIter + filterIterator (IT const& src, PRED filterPredicate) + { + return FilterIter(src,filterPredicate); + } + + + + + +} // namespace lib +#endif diff --git a/tests/40components.tests b/tests/40components.tests index a88881147..ccaa911c8 100644 --- a/tests/40components.tests +++ b/tests/40components.tests @@ -322,10 +322,21 @@ END TEST "Lumiera Iterator Concept" IterAdapter_test 20 < data_; + + WrappedVector(uint num) + { + while (num) + data_.push_back(num--); + } + + typedef vector::iterator sourceIter; + typedef RangeIter iterator; + + typedef vector::const_iterator const_sourceIter; + typedef RangeIter const_iterator; + + iterator begin() { return iterator(data_.begin(),data_.end()); } + iterator end() { return iterator(); } + const_iterator begin() const { return const_iterator(data_.begin(),data_.end()); } + const_iterator end() const { return const_iterator(); } + + }; + + /** * Example of a more elaborate custom container exposing an iteration API. * While the demo implementation here is based on pointers within a vector, @@ -154,10 +181,12 @@ namespace test{ { if (0 < arg.size()) NUM_ELMS = lexical_cast (arg[0]); - wrapIterRange (); + useSimpleWrappedContainer (); + wrapIterRange (); TestContainer testElms (NUM_ELMS); simpleUsage (testElms); + iterTypeVariations (testElms); verifyComparisons (testElms); } @@ -192,17 +221,32 @@ namespace test{ /** @test use the IterAdapter as if it was a STL iterator */ + template void - simpleUsage (TestContainer& elms) + simpleUsage (CON& elms) { - for_each (elms, showIt); + for_each (elms, showIntP); cout << endl; } - static void showIt (int* elm) { cout << "::" << *elm; } + static void showIntP (int* elm) { cout << "::" << *elm; } + static void showInt (int elm) { cout << "::" << elm; } + void + useSimpleWrappedContainer () + { + WrappedVector testVec (NUM_ELMS); + for_each (testVec, showInt); + cout << endl; + + WrappedVector const& ref (testVec); + for_each (ref, showInt); // uses const_iterator + cout << endl; + } + + /** @test verify the const and dereferencing variants, * which can be created based on IterAdapter */ void diff --git a/tests/lib/itertools-test.cpp b/tests/lib/itertools-test.cpp new file mode 100644 index 000000000..baa74bc11 --- /dev/null +++ b/tests/lib/itertools-test.cpp @@ -0,0 +1,192 @@ +/* + IterTools(Test) - building combined and filtering iterators based on the Iterator tools + + Copyright (C) Lumiera.org + 2009, 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/util.hpp" + +#include "lib/itertools.hpp" + +#include +#include +#include + + + +namespace lib { +namespace test{ + + using ::Test; + using boost::lexical_cast; + using util::for_each; + using util::isnil; + using std::vector; + using std::cout; + using std::endl; + + + + namespace { // Test data + + uint NUM_ELMS = 10; + + struct TestSource + { + vector data_; + + TestSource(uint num) + { + while (num) + data_.push_back(num--); + } + + typedef vector::iterator sourceIter; + typedef RangeIter iterator; + + iterator begin() { return iterator(data_.begin(),data_.end()); } + iterator end() { return iterator(); } + + }; + + } // (END) Test data + + + + + + + + /******************************************************************************* + * @test build combined and filtering iterators with the help of lib::IterTool. + * Check correct behaviour of the resulting iterator and + * verify they fulfil the Lumiera Forward Iterator concept + * + * @todo implement more iterator tools.... see Ticket #347 + */ + class IterTools_test : public Test + { + + typedef TestSource::iterator Iter; + + + virtual void + run (Arg arg) + { + if (0 < arg.size()) NUM_ELMS = lexical_cast (arg[0]); + + TestSource source(NUM_ELMS); + + pullOut (source.begin()); + verifyComparisons (source.begin()); + + + buildFilterIterator (source.begin()); + Iter ii (source.begin()); + ++++++ii; + buildFilterIterator (ii); + } + + + + template + void + pullOut (IT const& ii) + { + for (IT iter(ii) ; iter; ++iter ) + cout << "::" << *iter; + cout << endl; + } + + + + static bool takeAll (int) { return true; } + static bool takeEve (int i) { return 0 == i % 2; } + static bool takeOdd (int i) { return 0 != i % 2; } + + void + buildFilterIterator (Iter const& ii) + { + pullOut (filterIterator (ii, takeAll)); // note: using the convenient builder function + pullOut (filterIterator (ii, takeEve)); + pullOut (filterIterator (ii, takeOdd)); + + FilterIter all (ii, takeAll); + FilterIter odd (ii, takeOdd); + verifyComparisons (all); + verifyComparisons (odd); + + while (++all && ++odd) + ASSERT (all != odd); + + while (++all) { } + ASSERT (isnil (odd)); + ASSERT (all == odd); + } + + + + /** @test verify equality handling and NIL detection + * for the given iterator/wrapper handed in */ + template + void + verifyComparisons (IT const& ii) + { + IT i1(ii); + IT i2(ii); + IT iN; + ASSERT ( isnil (iN)); + ASSERT (!isnil (i1)); + ASSERT (!isnil (i2)); + + ASSERT (i1 == i2); ASSERT (i2 == i1); + ASSERT (i1 != iN); ASSERT (iN != i1); + ASSERT (i2 != iN); ASSERT (iN != i2); + + ++i1; + ASSERT (i1 != i2); + ASSERT (i1 != iN); + + ++i2; + ASSERT (i1 == i2); + ASSERT (i1 != iN); + ASSERT (i2 != iN); + + while (++i1) { } + ASSERT (isnil(i1)); + ASSERT (i1 != i2); + ASSERT (i1 == iN); + + while (++i2) { } + ASSERT (isnil(i2)); + ASSERT (i2 == i1); + ASSERT (i2 == iN); + } + + + }; + + LAUNCHER (IterTools_test, "unit common"); + + +}} // namespace lib::test +