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 :
bool isEven( int x ) { return (x%2)==0; }
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 :
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 :
mem_fun_ref
to invoke class methods. Only a pointer is needed.
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 :
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.
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 resulting value of reduce
is the value returned by the last call to the function