Boost logo

Boost :

From: Brian McNamara (lorgon_at_[hidden])
Date: 2004-03-02 15:54:26


On Tue, Mar 02, 2004 at 09:33:40AM -0500, Gennadiy Rozental wrote:
> if you could provide an examples of intended usage for this class to address
> real-life problems with performance comparison with corresponding imperative
> analog, that would be good enough to satisfy me on the topic.

Here is a quick example (based on an idea from an earlier thread) which
you may or may not find interesting.

The "problem" is to code up some const forward iterators for
preorder/inorder traversals of this simple data structure:

   template <class T>
   struct Tree {
      typedef T value_type;
      T data;
      Tree<T>* left;
      Tree<T>* right;
      Tree( const T& d ) : data(d), left(0), right(0) {}
   };

Below, I code up the iterators using both a "standard" mechanism and
using FC++ lazy lists. I have not tried to "hand optimize" either
version. Both versions have a bit of boilerplate code associated with
them. Right now I want to focus on just the core logic.

With FC++, an inorder traversal basically looks like this:

   template <class T>
   list<T> operator()( Tree<T>* t ) const {
      if( !t )
         return NIL;
      else
         return (*this)(t->left) ^cat^ cons( t->data,
            thunk1(*this,t->right) );
   }

That is, you recurse on the left child, and concatenate that with a
(lazy) recursive call on the right child with the current data stuck on
the front. Pretty simple.

Without FC++, I used a stack to manage the traversal. (My code is
actually based on some code Allan Odgaard sent me when the topic was
mentioned in passing a few days ago.) Here is the corresponding code
in the iterator class that does the "logic":

   T* current;
   std::stack<T*> backtrack;
   ...
   in_tree_iterator (T* startAt = 0) : current(startAt) {
      backtrack.push(0);
      if( current )
         while( current->left )
            backtrack.push(current), current=current->left;
   }
   ...
   in_tree_iterator& operator++ () {
      if(current->right) {
         current = current->right;
         while( current->left )
            backtrack.push(current), current=current->left;
      }
      else {
         current = backtrack.top();
         backtrack.pop();
      }
      return *this;
   }
   
I am not as confident that it is correct, but it seems to work. I think
most people would agree that the FC++ solution is easier to understand.

I think the FC++ way is also easier to modify. To make it a preorder
traversal instead of inorder, I just change
   
         return (*this)(t->left) ^cat^ cons( t->data,
            thunk1(*this,t->right) );
to
         return cons( t->data, thunk1(*this,t->left) ) ^cat^
            thunk1(*this,t->right);

That is, I do "data left right" rather than "left data right". The
corresponding change in the other version is not so simple or intuitive
in my opinion; this code:

   in_tree_iterator (T* startAt = 0) : current(startAt) {
      backtrack.push(0);
      if( current )
         while( current->left )
            backtrack.push(current), current=current->left;
   }
   ...
   in_tree_iterator& operator++ () {
      if(current->right) {
         current = current->right;
         while( current->left )
            backtrack.push(current), current=current->left;
      }
      else {
         current = backtrack.top();
         backtrack.pop();
      }
      return *this;
   }

becomes

   pre_tree_iterator (T* startAt = 0) : current(startAt)
   { backtrack.push(0); }
   ...
   pre_tree_iterator& operator++ () {
      if(current->right)
         backtrack.push(current->right);
      if(!(current = current->left))
         current = backtrack.top(), backtrack.pop();
      return *this;
   }
   
The moral I am trying to sell is that lazy evaluation here is a win for
the programmer; the code is shorter and clearer.

Of course, there's a catch: performance. The FC++ version runs about
3-4 slower than the other version.

I think it's valuable to have the trade-off available, so that you can
choose between raw performance and code that's easier to read/understand.

Anyway, the program is below. Enjoy.

----------------------------------------------------------------------
#define BOOST_FCPP_ENABLE_LAMBDA
#include "prelude.hpp"
using namespace boost::fcpp;

#include <iostream>
using std::cin;
using std::cout;
using std::cerr;
using std::endl;
#include <stack>
#include <algorithm>
#include <iterator>

template <class T>
struct Tree {
   typedef T value_type;
   T data;
   Tree<T>* left;
   Tree<T>* right;
   Tree( const T& d ) : data(d), left(0), right(0) {}
};

template <class T>
void insert( Tree<T>*& root, const T& data ) {
   if( !root )
      root = new Tree<T>(data);
   else if( data < root->data )
      insert( root->left, data );
   else
      insert( root->right, data );
}

// Based on code from Allan Odgaard
template <typename T>
struct pre_tree_iterator : public std::iterator<std::forward_iterator_tag,
                                                typename T::value_type> {
   T* current;
   std::stack<T*> backtrack;
   
   pre_tree_iterator (T* startAt = 0) : current(startAt)
   { backtrack.push(0); }
   
   bool operator!= (pre_tree_iterator const& rhs) const
   { return current != rhs.current; }
   
   bool operator== (pre_tree_iterator const& rhs) const
   { return current == rhs.current; }
   
   typename T::value_type const& operator* () const
   { return current->data; }
   
   pre_tree_iterator& operator++ ()
   {
      if(current->right)
         backtrack.push(current->right);
      if(!(current = current->left))
         current = backtrack.top(), backtrack.pop();
      return *this;
   }
   
   pre_tree_iterator operator++ (int)
   { pre_tree_iterator tmp(*this); ++(*this); return tmp; }
};
// helper functions to create iterators
template <class T>
pre_tree_iterator< Tree<T> > pre_begin_tree (Tree<T>* tree)
{ return pre_tree_iterator< Tree<T> >(tree); }
template <class T>
pre_tree_iterator< Tree<T> > pre_end_tree (Tree<T>*)
{ return pre_tree_iterator< Tree<T> >(0); }

template <typename T>
struct in_tree_iterator : public std::iterator<std::forward_iterator_tag,
                                               typename T::value_type> {
   T* current;
   std::stack<T*> backtrack;
   
   in_tree_iterator (T* startAt = 0) : current(startAt) {
      backtrack.push(0);
      if( current )
         while( current->left )
            backtrack.push(current), current=current->left;
   }
   
   bool operator!= (in_tree_iterator const& rhs) const
   { return current != rhs.current; }
   
   bool operator== (in_tree_iterator const& rhs) const
   { return current == rhs.current; }
   
   typename T::value_type const& operator* () const
   { return current->data; }
   
   in_tree_iterator& operator++ () {
      if(current->right) {
         current = current->right;
         while( current->left )
            backtrack.push(current), current=current->left;
      }
      else {
         current = backtrack.top();
         backtrack.pop();
      }
      return *this;
   }
   
   in_tree_iterator operator++ (int)
   { in_tree_iterator tmp(*this); ++(*this); return tmp; }
};
// helper functions to create iterators
template <class T>
in_tree_iterator< Tree<T> > in_begin_tree (Tree<T>* tree)
{ return in_tree_iterator< Tree<T> >(tree); }
template <class T>
in_tree_iterator< Tree<T> > in_end_tree (Tree<T>*)
{ return in_tree_iterator< Tree<T> >(0); }

// FC++ way
struct PreFlatten {
   template <class TTP> struct sig : fun_type<
      list<typename std::iterator_traits<TTP>::value_type::value_type> > {};
   template <class T>
   list<T> operator()( Tree<T>* t ) const {
      if( !t )
         return NIL;
      else
         return cons( t->data, thunk1(*this,t->left) ) ^cat^
            thunk1(*this,t->right);
   }
} pre_flatten;

struct InFlatten {
   template <class TTP> struct sig : fun_type<
      list<typename std::iterator_traits<TTP>::value_type::value_type> > {};
   template <class T>
   list<T> operator()( Tree<T>* t ) const {
      if( !t )
         return NIL;
      else
         return (*this)(t->left) ^cat^ cons( t->data,
            thunk1(*this,t->right) );
   }
} in_flatten;

int main() {
   int MAX;
   cin >> MAX;
   int* a = new int[MAX];
   for( int i=0; i<MAX; ++i )
      a[i] = i;
   std::random_shuffle( a, a+MAX );
   
   Tree<int>* tree = 0;
   for( int i=0; i<MAX; ++i )
      insert( tree, a[i] );
   
   {
      cerr << "pre_tree_iterator" << endl;
      Timer timer; // please use your own timing mechanism here
      pre_tree_iterator<Tree<int> > end = pre_end_tree(tree);
      for( pre_tree_iterator<Tree<int> > i = pre_begin_tree(tree);
           i != end; ++i )
         cout << *i << " ";
      cout << endl;
      cerr << timer.ms_since_start() << endl;
   }

   {
      cerr << "pre_flatten" << endl;
      Timer timer;
      list<int> l = pre_flatten(tree);
      list<int>::iterator end = l.end();
      for( list<int>::iterator i = l.begin(); i != end; ++i )
         cout << *i << " ";
      cout << endl;
      while(l) l=tail(l);
      cerr << timer.ms_since_start() << endl;
   }

   {
      cerr << "in_flatten" << endl;
      Timer timer;
      list<int> l = in_flatten(tree);
      list<int>::iterator end = l.end();
      for( list<int>::iterator i = l.begin(); i != end; ++i )
         cout << *i << " ";
      cout << endl;
      while(l) l=tail(l);
      cerr << timer.ms_since_start() << endl;
   }

   {
      cerr << "in_tree_iterator" << endl;
      Timer timer;
      in_tree_iterator<Tree<int> > end = in_end_tree(tree);
      for( in_tree_iterator<Tree<int> > i = in_begin_tree(tree);
           i != end; ++i )
         cout << *i << " ";
      cout << endl;
      cerr << timer.ms_since_start() << endl;
   }
}
----------------------------------------------------------------------

-- 
-Brian McNamara (lorgon_at_[hidden])

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk