The Simple Functional Template Library
 

SFTL quick start examples

Collecting data with filter

We'll start with a simple example : let's say that we've got a list of integers and that we'd like to extract the even numbers.

We'll assume that we already have :

  • a function that tells whether a number is even
bool isEven( int x ) {
    return (x%2)==0;
}
  • a vector of integers initialized to 1..9
vector<int> numbers;
for (int i=1;i<=9;i++)
    numbers.push_back(i);


In typical STL style, the even numbers would be retrieved this way :

vector<int> evens;
for (vector<int>::iterator it = numbers.begin(); it!=numbers.end(); ++it) {
    if (isEven(*it)) {
        evens.push_back( *it );
    }
}


Or, in another fashion, using functors :

vector<int> evens;
remove_copy_if( numbers.begin(), numbers.end(), back_inserter(evens), not1(ptr_fun(isEven)));

(Shorter than the previous code but probably taking longer to get right).


In SFTL, it's done with filter :

vector<int> evens = filter( numbers, isEven );


This first glance at the SFTL shows some of its strengths :

  • It is shorter to type and easier to remember
  • It is also simpler to read
  • It behaves in a functional way : it returns the output container instead of taking it as an argument
  • It doesn't work with “begin” and “end” iterators : the computation always applies to the whole sequence


Transforming data with map

Let's now consider another example involving transformations. Let's say we've got this simple Destination class :

class Destination {
 
    int reference;
    string name;
 
public:
    Destination( int reference, string name ) : reference(reference), name(name) {};
 
    int getReference() {
        return reference;
    }
 
    string getName() {
        return name;
    }
};

And that we've got a bunch of them in a vector :

vector<Destination> destinations;
destinations.push_back( Destination(100,"Paris") );
destinations.push_back( Destination(200,"New York") );
destinations.push_back( Destination(300,"Dubai") );

In order to extract the names, in STL one must write :

vector<string> names;
for (vector<Destination>::iterator it=destinations.begin(); it!=destinations.end(); ++it) {
    names.push_back( it->getName() );
}

Or, using functors :

vector<string> names;
transform( destinations.begin(), destinations.end(), back_inserter(names), mem_fun_ref(&Destination::getName) );


In SFTL, it is done with map :

vector<string> names = map( destinations, &Destination::getName );


This example shows that :

  • As in the example with filter, the syntax is concise and minimal.
  • SFTL can use existing functions AND existing class methods as functors
  • Contrary to STL algorithms, SFTL doesn't require mem_fun_ref to invoke class methods. Only a pointer is needed.


Running closures with apply

Let's say we've got the following Soldier class with the ability to fire :

class Soldier {
    int ammunition;
public:
    Soldier( int ammo ) : ammunition(ammo) {};
    void fire() {
        ...
    }
};

And that we've got a bunch of them ready for action :

int ammo [] = {5,20,3,11,8};
list<Soldier> soldiers( &ammo[0], &ammo[5] );

Here is the typical STL code to make them fire :

for (list<Soldier>::iterator it=soldiers.begin(); it!=soldiers.end(); ++it ) {
    it->fire();
}

Or, in another way, using the for_each STL algorithm :

for_each( soldiers.begin(), soldiers.end(), mem_fun_ref(&Soldier::fire) );


In SFTL, it is done using apply :

apply( soldiers, &Soldier::fire );


Some other features of the SFTL :

  • SFTL algorithms work over any kind of STL container 1)
  • Algorithms return a container template of the same type as the input container template (vector, list, …)


Flattenning hierarchical data with expand

Suppose we've got a team players object hierarchy made of two classes : Team and Player.

class Player {
    string name;
 
public:
    Player( string name ) : name(name) {}
 
    string getName() {
        return name;
    }
};
 
class Team {
    string name;
    vector<Player> players;
 
public:
    Team( string name, vector<Player>& players ) : name(name), players(players) {};
 
    string getName() {
        return name;
    }
 
    vector<Player>& getPlayers() {
        return players;
    }
};

and the following dataset :

vector<Team> teams;
 
list<Player> players1;
players1.insert( Player("Ryan") ); players1.insert( Player("Ellen") );
teams.push_back( "Rookies", players1 );
 
list<Player> players2;
players2.insert( Player("Anna") ); players2.insert( Player("David") ); players2.insert( Player("Tom") );
teams.push_back( "Avengers", players2 );
 
list<Player> players3;
players3.insert( Player("Michael") );
teams.push_back( "Black dogs", players3 );

Now, suppose we've got a “buy player” function :

void buy_player( Player& player ) {
    cout << "buying " << player.getName() << endl;
}

and that we'd like to buy all the players. What's the simplest way to do that ?

In conventional STL, this would require two loops :

for (vector<Team>::iterator it1 = teams.begin(); it1!=teams.end(); ++it1) {
    Team team = *it1;
    for (list<Player>::iterator it2 = team.getNames().begin(); it2!=team.getNames().end(); ++it2) {
        buy_player(*it2);
    }
}


In SFTL, it can be done with expand, followed by apply :

vector<Player> players = expand( teams, &Team::getPlayers ); 
apply( players, buy_player );

Or, in one line :

apply( expand( teams, &Team::getPlayers), buy_player );

The expand function takes a list of objects and a class method -of the same object class- that returns another list of objects (usually from another type).

Each Team object in the container can be expanded into a collection of players, so the collection of teams, when expanded, leads to a collection of collection of players. expand flattens this collection of collections, and thus returns the flattened collection of all players of all teams.

Folding data with reduce

Let's now say we'd like to compute the greatest common divisor (also known as “gcd”) of numbers stored in a vector.
The simplest implementation that computes the gcd of two numbers is :

int compute_gcd( int a, int b ) {
    if (b==0)
        return a;
    return compute_gcd( b, a % b );
}

Now, given the following numbers :

int array [] = {6,12,21,9,30,18,15};
vector<int> numbers( &array[0], &array[7]);

Here's the STL code to get the gcd of the entire sequence :

vector<int>::iterator it=numbers.begin();
int gcd = *it++;
while (it!=numbers.end()) {
    gcd = compute_gcd( gcd, *it );
    ++it;
}
cout << "the gcd is " << gcd << endl;  // prints 3


This can be done in SFTL using reduce :

cout << "the gcd is " << reduce( numbers, compute_gcd ) << endl;

The idea behind reduce is also called “folding” and allows to take functions that are designed to accept 2 arguments (such as min, max, sum, product, gcd…) and apply them over n arguments.

reduce does this but successively applying the function to :

  • the value returned by the previous call to the fonction (or, if it's never been called, the first item in the sequence)
  • the next item in the sequence

The resulting value of reduce is the value returned by the last call to the function

1) in the actual state of SFTL, only sequence containers are supported
 
quick_start_examples.txt · Last modified: 2007/08/24 23:31 (external edit)
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki