![]() |
James Thornton |
| Internet Business Consultant | Call Toll Free: 1 (800) 409-2501 |
| About James | My MySpace | Internet Marketing | Enron Loophole | Lock Bumping | Contact Me |
|---|
The other half of the STL is the algorithms,
which are templatized functions designed to work with the containers (or, as you
will see, anything that can behave like a container, including arrays and
string objects).
The STL was originally designed around the algorithms. The
goal was that you use algorithms for almost every piece of code that you write.
In this sense it was a bit of an experiment, and only time will tell how well it
works. The real test will be in how easy or difficult it is for the average
programmer to adapt. At the end of this chapter you’ll be able to decide
for yourself whether you find the algorithms addictive or too confusing to
remember. If you’re like me, you’ll resist them at first but then
tend to use them more and more.
Before you make your judgment, however, there’s one
other thing to consider. The STL algorithms provide a vocabulary with
which to describe solutions. That is, once you become familiar with the
algorithms you’ll have a new set of words with which to discuss what
you’re doing, and these words are at a higher level than what you’ve
had before. You don’t have to say “this loop moves through and
assigns from here to there ... oh, I see, it’s copying!” Instead,
you say copy( ). This is the kind of thing we’ve been doing in
computer programming from the beginning – creating more dense ways to
express what we’re doing and spending less time saying how
we’re doing it. Whether the STL algorithms and generic programming
are a great success in accomplishing this remains to be seen, but that is
certainly the objective.
A concept that is used heavily in the STL algorithms is the
function object, which was introduced in the previous chapter. A function
object has an overloaded operator( ), and the result is that a
template function can’t tell whether you’ve handed it a pointer to a
function or an object that has an operator( ); all the template
function knows is that it can attach an argument list to the object as if
it were a pointer to a function:
//: C05:FuncObject.cpp
// Simple function objects
#include <iostream>
using namespace std;
template<class UnaryFunc, class T>
void callFunc(T& x, UnaryFunc f) {
f(x);
}
void g(int& x) {
x = 47;
}
struct UFunc {
void operator()(int& x) {
x = 48;
}
};
int main() {
int y = 0;
callFunc(y, g);
cout << y << endl;
y = 0;
callFunc(y, UFunc());
cout << y << endl;}
///:~
The template callFunc( ) says “give me an
f and an x, and I’ll write the code f(x).” In
main( ), you can see that it doesn’t matter if f is a
pointer to a function (as in the case of g( )), or if it’s a
function object (which is created as a temporary object by the expression
UFunc( )). Notice you can only accomplish this genericity with a
template function; a non-template function is too particular about its argument
types to allow such a thing. The STL algorithms use this flexibility to take
either a function pointer or a function object, but you’ll usually find
that creating a function object is more powerful and flexible.
The function object is actually a variation on the theme of a
callback, which is described in the design patterns chapter. A callback
allows you to vary the behavior of a function or object by passing, as an
argument, a way to execute some other piece of code. Here, we are handing
callFunc( ) a pointer to a function or a function object.
The following descriptions of function objects should not only
make that topic clear, but also give you an introduction to the way the STL
algorithms work.
Just as the STL classifies iterators (based on their
capabilities), it also classifies function objects based on the number of
arguments that their operator( ) takes and the kind of value
returned by that operator (of course, this is also true for function pointers
when you treat them as function objects). The classification of function objects
in the STL is based on whether the operator( ) takes zero, one or
two arguments, and if it returns a bool or non-bool
value.
Generator: Takes no arguments, and returns a value of
the desired type. A RandomNumberGenerator is a special case.
UnaryFunction: Takes a single argument of any type and
returns a value which may be of a different type.
BinaryFunction: Takes two arguments of any two types
and returns a value of any type.
A special case of the unary and binary functions is the
predicate, which simply means a function that returns a bool. A
predicate is a function you use to make a true/false
decision.
Predicate: This can also be called a
UnaryPredicate. It takes a single argument of any type and returns a
bool.
BinaryPredicate: Takes two arguments of any two types
and returns a bool.
StrictWeakOrdering: A binary predicate that says that
if you have two objects and neither one is less than the other, they can be
regarded as equivalent to each other.
In addition, there are sometimes qualifications on object
types that are passed to an algorithm. These qualifications are given in the
template argument type identifier name:
LessThanComparable: A class that has a less-than
operator<.
Assignable: A class that has an assignment
operator= for its own type.
The STL has, in the header file <functional>, a
set of templates that will automatically create function objects for you. These
generated function objects are admittedly simple, but the goal is to provide
very basic functionality that will allow you to compose more complicated
function objects, and in many situations this is all you’ll need. Also,
you’ll see that there are some function object adapters that allow
you to take the simple function objects and make them slightly more
complicated.
Here are the templates that generate function objects, along
with the expressions that they effect.
|
Name |
Type |
Result produced by generated function object |
|---|---|---|
|
plus |
BinaryFunction |
arg1 + arg2 |
|
minus |
BinaryFunction |
arg1 - arg2 |
|
multiplies |
BinaryFunction |
arg1 * arg2 |
|
divides |
BinaryFunction |
arg1 / arg2 |
|
modulus |
BinaryFunction |
arg1 % arg2 |
|
negate |
UnaryFunction |
- arg1 |
|
equal_to |
BinaryPredicate |
arg1 == arg2 |
|
not_equal_to |
BinaryPredicate |
arg1 != arg2 |
|
greater |
BinaryPredicate |
arg1 > arg2 |
|
less |
BinaryPredicate |
arg1 < arg2 |
|
greater_equal |
BinaryPredicate |
arg1 >= arg2 |
|
less_equal |
BinaryPredicate |
arg1 <= arg2 |
|
logical_and |
BinaryPredicate |
arg1 && arg2 |
|
logical_or |
BinaryPredicate |
arg1 || arg2 |
|
logical_not |
UnaryPredicate |
!arg1 |
|
not1( ) |
Unary Logical |
!(UnaryPredicate(arg1)) |
|
not2( ) |
Binary Logical |
!(BinaryPredicate(arg1, arg2)) |
The following example provides simple tests for each of the
built-in basic function object templates. This way, you can see how to use each
one, along with their resulting behavior.
//: C05:FunctionObjects.cpp
// Using the predefined function object templates
// in the Standard C++ library
// This will be defined shortly:
#include "Generators.h"
#include <algorithm>
#include <vector>
#include <iostream>
#include <functional>
using namespace std;
template<typename T>
void print(vector<T>& v, char* msg = "") {
if(*msg != 0)
cout << msg << ":" << endl;
copy(v.begin(), v.end(),
ostream_iterator<T>(cout, " "));
cout << endl;
}
template<typename Contain, typename UnaryFunc>
void testUnary(Contain& source, Contain& dest,
UnaryFunc f) {
transform(source.begin(), source.end(),
dest.begin(), f);
}
template<typename Contain1, typename Contain2,
typename BinaryFunc>
void testBinary(Contain1& src1, Contain1& src2,
Contain2& dest, BinaryFunc f) {
transform(src1.begin(), src1.end(),
src2.begin(), dest.begin(), f);
}
// Executes the expression, then stringizes the
// expression into the print statement:
#define T(EXPR) EXPR; print(r, "After " #EXPR);
// For Boolean tests:
#define B(EXPR) EXPR; print(br,"After " #EXPR);
// Boolean random generator:
struct BRand {
BRand() { srand(time(0)); }
bool operator()() {
return rand() > RAND_MAX / 2;
}
};
int main() {
const int sz = 10;
const int max = 50;
vector<int> x(sz), y(sz), r(sz);
// An integer random number generator:
URandGen urg(max);
generate_n(x.begin(), sz, urg);
generate_n(y.begin(), sz, urg);
// Add one to each to guarantee nonzero divide:
transform(y.begin(), y.end(), y.begin(),
bind2nd(plus<int>(), 1));
// Guarantee one pair of elements is ==:
x[0] = y[0];
print(x, "x");
print(y, "y");
// Operate on each element pair of x & y,
// putting the result into r:
T(testBinary(x, y, r, plus<int>()));
T(testBinary(x, y, r, minus<int>()));
T(testBinary(x, y, r, multiplies<int>()));
T(testBinary(x, y, r, divides<int>()));
T(testBinary(x, y, r, modulus<int>()));
T(testUnary(x, r, negate<int>()));
vector<bool> br(sz); // For Boolean results
B(testBinary(x, y, br, equal_to<int>()));
B(testBinary(x, y, br, not_equal_to<int>()));
B(testBinary(x, y, br, greater<int>()));
B(testBinary(x, y, br, less<int>()));
B(testBinary(x, y, br, greater_equal<int>()));
B(testBinary(x, y, br, less_equal<int>()));
B(testBinary(x, y, br,
not2(greater_equal<int>())));
B(testBinary(x,y,br,not2(less_equal<int>())));
vector<bool> b1(sz), b2(sz);
generate_n(b1.begin(), sz, BRand());
generate_n(b2.begin(), sz, BRand());
print(b1, "b1");
print(b2, "b2");
B(testBinary(b1, b2, br, logical_and<int>()));
B(testBinary(b1, b2, br, logical_or<int>()));
B(testUnary(b1, br, logical_not<int>()));
B(testUnary(b1, br, not1(logical_not<int>())));}
///:~
To keep this example small, some tools are created. The
print( ) template is designed to print any vector<T>,
along with an optional message. Since print( ) uses the STL
copy( ) algorithm to send objects to cout via an
ostream_iterator, the ostream_iterator must know the type of
object it is printing, and therefore the print( ) template must know
this type also. However, you’ll see in main( ) that the
compiler can deduce the type of T when you hand it a
vector<T>, so you don’t have to hand it the template argument
explicitly; you just say print(x) to print the vector<T>
x.
The next two template functions automate the process of
testing the various function object templates. There are two since the function
objects are either unary or binary. In testUnary( ), you pass a
source and destination vector, and a unary function object to apply to the
source vector to produce the destination vector. In testBinary( ),
there are two source vectors which are fed to a binary function to produce the
destination vector. In both cases, the template functions simply turn around and
call the transform( ) algorithm, although the tests could certainly
be more complex.
For each test, you want to see a string describing what the
test is, followed by the results of the test. To automate this, the preprocessor
comes in handy; the T( ) and B( ) macros each take the
expression you want to execute. They call that expression, then call
print( ), passing it the result vector (they assume the expression
changes a vector named r and br, respectively), and to produce the
message the expression is “string-ized” using the preprocessor. So
that way you see the code of the expression that is executed followed by the
result vector.
The last little tool is a generator object that creates random
bool values. To do this, it gets a random number from rand( )
and tests to see if it’s greater than RAND_MAX/2. If the random
numbers are evenly distributed, this should happen half the time.
In main( ), three vector<int> are
created: x and y for source values, and r for results. To
initialize x and y with random values no greater than 50, a
generator of type URandGen is used; this will be defined shortly. Since
there is one operation where elements of x are divided by elements of
y, we must ensure that there are no zero values of y. This is
accomplished using the transform( ) algorithm, taking the source
values from y and putting the results back into y. The function
object for this is created with the expression:
bind2nd(plus<int>(),
1)
This uses the plus function object that adds two
objects together. It is thus a binary function which requires two arguments; we
only want to pass it one argument (the element from y) and have the other
argument be the value 1. A “binder” does the trick (we will look at
these next). The binder in this case says “make a new function object
which is the plus function object with the second argument fixed at
1.”
Another of the tests in the program compares the elements in
the two vectors for equality, so it is interesting to guarantee that at least
one pair of elements is equivalent; in this case element zero is
chosen.
Once the two vectors are printed, T( ) is used to
test each of the function objects that produces a numerical value, and then
B( ) is used to test each function object that produces a Boolean
result. The result is placed into a vector<bool>, and when this
vector is printed it produces a ‘1’ for a true value and a
‘0’ for a false value.
It’s common to want to take a binary function object and
to “bind” one of its arguments to a constant value. After binding,
you get a unary function object.
For example, suppose you want to find integers that are less
than a particular value, say 20. Sensibly enough, the STL algorithms have a
function called find_if( ) that will search through a sequence;
however, find_if( ) requires a unary predicate to tell it if this is
what you’re looking for. This unary predicate can of course be some
function object that you have written by hand, but it can also be created using
the built-in function object templates. In this case, the less template
will work, but that produces a binary predicate, so we need some way of forming
a unary predicate. The binder templates (which work with any binary function
object, not just binary predicates) give you two choices:
bind1st(const BinaryFunction& op, const T&
t);
bind2nd(const BinaryFunction& op, const T&
t);
Both bind t to one of the arguments of op, but
bind1st( ) binds t to the first argument, and
bind2nd( ) binds t to the second argument. With less,
the function object that provides the solution to our exercise is:
bind2nd(less<int>(),
20);
This produces a new function object that returns true if its
argument is less than 20. Here it is, used with
find_if( ):
//: C05:Binder1.cpp
// Using STL "binders"
#include "Generators.h"
#include "copy_if.h"
#include <algorithm>
#include <vector>
#include <iostream>
#include <functional>
using namespace std;
int main() {
const int sz = 10;
const int max = 40;
vector<int> a(sz), r;
URandGen urg(max);
ostream_iterator<int> out(cout, " ");
generate_n(a.begin(), sz, urg);
copy(a.begin(), a.end(), out);
int* d = find_if(a.begin(), a.end(),
bind2nd(less<int>(), 20));
cout << "\n *d = " << *d << endl;
// copy_if() is not in the Standard C++ library
// but is defined later in the chapter:
copy_if(a.begin(), a.end(), back_inserter(r),
bind2nd(less<int>(), 20));
copy(r.begin(), r.end(), out);
cout << endl;}
///:~
The vector<int> a is filled with random
numbers between 0 and max. find_if( ) finds the first element
in a that satisfies the predicate (that is, which is less than 20) and
returns an iterator to it (here, the type of the iterator is actually just
int* although I could have been more precise and said
vector<int>::iterator instead).
A more interesting algorithm to use is copy_if( ),
which isn’t part of the STL but is defined at the end of this chapter.
This algorithm only copies an element from the source to the destination if that
element satisfies a predicate. So the resulting vector will only contain
elements that are less than 20.
Here’s a second example, using a
vector<string> and replacing strings that satisfy particular
conditions:
//: C05:Binder2.cpp
// More binders
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <functional>
using namespace std;
int main() {
ostream_iterator<string> out(cout, " ");
vector<string> v, r;
v.push_back("Hi");
v.push_back("Hi");
v.push_back("Hey");
v.push_back("Hee");
v.push_back("Hi");
copy(v.begin(), v.end(), out);
cout << endl;
// Replace each "Hi" with "Ho":
replace_copy_if(v.begin(), v.end(),
back_inserter(r),
bind2nd(equal_to<string>(), "Hi"), "Ho");
copy(r.begin(), r.end(), out);
cout << endl;
// Replace anything that's not "Hi" with "Ho":
replace_if(v.begin(), v.end(),
not1(bind2nd(equal_to<string>(),"Hi")),"Ho");
copy(v.begin(), v.end(), out);
cout << endl;}
///:~
This uses another pair of STL algorithms. The first,
replace_copy_if( ), copies each element from a source range to a
destination range, performing replacements on those that satisfy a particular
unary predicate. The second, replace_if( ), doesn’t do any
copying but instead performs the replacements directly into the original
range.
A binder doesn’t have to produce a unary predicate; it
can also create a unary function (that is, a function that returns something
other than bool). For example, suppose you’d like to multiply every
element in a vector by 10. Using a binder with the
transform( ) algorithm does the trick:
//: C05:Binder3.cpp
// Binders aren't limited to producing predicates
#include "Generators.h"
#include <algorithm>
#include <vector>
#include <iostream>
#include <functional>
using namespace std;
int main() {
ostream_iterator<int> out(cout, " ");
vector<int> v(15);
generate(v.begin(), v.end(), URandGen(20));
copy(v.begin(), v.end(), out);
cout << endl;
transform(v.begin(), v.end(), v.begin(),
bind2nd(multiplies<int>(), 10));
copy(v.begin(), v.end(), out);
cout << endl;}
///:~
Since the third argument to transform( ) is the
same as the first, the resulting elements are copied back into the source
vector. The function object created by bind2nd( ) in this case
produces an int result.
The “bound” argument to a binder cannot be a
function object, but it does not have to be a compile-time constant. For
example:
//: C05:Binder4.cpp
// The bound argument does not have
// to be a compile-time constant
#include "copy_if.h"
#include "PrintSequence.h"
#include "../require.h"
#include <iostream>
#include <algorithm>
#include <functional>
#include <cstdlib>
using namespace std;
int boundedRand() { return rand() % 100; }
int main(int argc, char* argv[]) {
requireArgs(argc, 1, "usage: Binder4 int");
const int sz = 20;
int a[20], b[20] = {0};
generate(a, a + sz, boundedRand);
int* end = copy_if(a, a + sz, b,
bind2nd(greater<int>(), atoi(argv[1])));
// Sort for easier viewing:
sort(a, a + sz);
sort(b, end);
print(a, a + sz, "array a", " ");
print(b, end, "values greater than yours"," ");}
///:~
Here, an array is filled with random numbers between 0 and
100, and the user provides a value on the command line. In the
copy_if( ) call, you can see that the bound argument to
bind2nd( ) is the result of the function call atoi( )
(from <cstdlib>).
Any place in an STL algorithm where a function object is
required, it’s very conceivable that you’d like to use a function
pointer instead. Actually, you can use an ordinary function pointer
– that’s how the STL was designed, so that a “function
object” can actually be anything that can be dereferenced using an
argument list. For example, the rand( ) random number generator can
be passed to generate( ) or generate_n( ) as a function
pointer, like this:
//: C05:RandGenTest.cpp
// A little test of the random number generator
#include <algorithm>
#include <vector>
#include <iostream>
#include <functional>
#include <cstdlib>
#include <ctime>
using namespace std;
int main() {
const int sz = 10000;
int v[sz];
srand(time(0)); // Seed the random generator
for(int i = 0; i < 300; i++) {
// Using a naked pointer to function:
generate(v, v + sz, std::rand);
int count = count_if(v, v + sz,
bind2nd(greater<int>(), RAND_MAX/2));
cout << (((double)count)/((double)sz)) * 100
<< ' ';
}}
///:~
The “iterators” in this case are just the starting
and past-the-end pointers for the array v, and the generator is just a
pointer to the standard library rand( ) function. The program
repeatedly generates a group of random numbers, then it uses the STL algorithm
count_if( ) and a predicate that tells whether a particular element
is greater than RAND_MAX/2. The result is the number of elements that
match this criterion; this is divided by the total number of elements and
multiplied by 100 to produce the percentage of elements greater than the
midpoint. If the random number generator is reasonable, this value should hover
at around 50% (of course, there are many other tests to determine if the random
number generator is reasonable).
The ptr_fun( ) adapters take a pointer to a
function and turn it into a function object. They are not designed for a
function that takes no arguments, like the one above (that is, a generator).
Instead, they are for unary functions and binary functions. However, these could
also be simply passed as if they were function objects, so the
ptr_fun( ) adapters might at first appear to be redundant.
Here’s an example where using ptr_fun( ) and simply passing
the address of the function both produce the same results:
//: C05:PtrFun1.cpp
// Using ptr_fun() for single-argument functions
#include <algorithm>
#include <vector>
#include <iostream>
#include <functional>
using namespace std;
char* n[] = { "01.23", "91.370", "56.661",
"023.230", "19.959", "1.0", "3.14159" };
const int nsz = sizeof n / sizeof *n;
template<typename InputIter>
void print(InputIter first, InputIter last) {
while(first != last)
cout << *first++ << "\t";
cout << endl;
}
int main() {
print(n, n + nsz);
vector<double> vd;
transform(n, n + nsz, back_inserter(vd), atof);
print(vd.begin(), vd.end());
transform(n,n + nsz,vd.begin(), ptr_fun(atof));
print(vd.begin(), vd.end());}
///:~
The goal of this program is to convert an array of
char* which are ASCII representations of floating-point numbers into a
vector<double>. After defining this array and the
print( ) template (which encapsulates the act of printing a range of
elements), you can see transform( ) used with atof( ) as
a “naked” pointer to a function, and then a second time with
atof passed to ptr_fun( ). The results are the same. So why
bother with ptr_fun( )? Well, the actual effect of
ptr_fun( ) is to create a function object with an
operator( ). This function object can then be passed to other
template adapters, such as binders, to create new function objects. As
you’ll see a bit later, the SGI extensions to the STL contain a number of
other function templates to enable this, but in the Standard C++ STL there are
only the bind1st( ) and bind2nd( ) function templates,
and these expect binary function objects as their first arguments. In the above
example, only the ptr_fun( ) for a unary function is used, and that
doesn’t work with the binders. So ptr_fun( ) used with a unary
function in Standard C++ really is redundant (note that Gnu g++ uses the SGI
STL).
With a binary function and a binder, things can be a little
more interesting. This program produces the squares of the input vector
d:
//: C05:PtrFun2.cpp
// Using ptr_fun() for two-argument functions
#include <algorithm>
#include <vector>
#include <iostream>
#include <functional>
#include <cmath>
using namespace std;
double d[] = { 01.23, 91.370, 56.661,
023.230, 19.959, 1.0, 3.14159 };
const int dsz = sizeof d / sizeof *d;
int main() {
vector<double> vd;
transform(d, d + dsz, back_inserter(vd),
bind2nd(ptr_fun(pow), 2.0));
copy(vd.begin(), vd.end(),
ostream_iterator<double>(cout, " "));
cout << endl; }
///:~
Here, ptr_fun( ) is indispensable;
bind2nd( ) must have a function object as its first argument
and a pointer to function won’t cut it.
A trickier problem is that of converting a member function
into a function object suitable for using in the STL algorithms. As a simple
example, suppose we have the “shape” problem and would like to apply
the draw( ) member function to each pointer in a container of
Shape:
//: C05:MemFun1.cpp
// Applying pointers to member functions
#include "../purge.h"
#include <algorithm>
#include <vector>
#include <iostream>
#include <functional>
using namespace std;
class Shape {
public:
virtual void draw() = 0;
virtual ~Shape() {}
};
class Circle : public Shape {
public:
virtual void draw() {
cout << "Circle::Draw()" << endl;
}
~Circle() {
cout << "Circle::~Circle()" << endl;
}
};
class Square : public Shape {
public:
virtual void draw() {
cout << "Square::Draw()" << endl;
}
~Square() {
cout << "Square::~Square()" << endl;
}
};
int main() {
vector<Shape*> vs;
vs.push_back(new Circle);
vs.push_back(new Square);
for_each(vs.begin(), vs.end(),
mem_fun(&Shape::draw));
purge(vs);}
///:~
The for_each( ) function does just what it sounds
like it does: passes each element in the range determined by the first two
(iterator) arguments to the function object which is its third argument. In this
case we want the function object to be created from one of the member functions
of the class itself, and so the function object’s “argument”
becomes the pointer to the object that the member function is called for. To
produce such a function object, the mem_fun( ) template takes a
pointer to member as its argument.
The mem_fun( ) functions are for producing
function objects that are called using a pointer to the object that the member
function is called for, while mem_fun_ref( ) is used for calling the
member function directly for an object. One set of overloads of both
mem_fun( ) and mem_fun_ref( ) are for member functions
that take zero arguments and one argument, and this is multiplied by two to
handle const vs. non-const member functions. However, templates
and overloading takes care of sorting all of that out; all you need to remember
is when to use mem_fun( ) vs. mem_fun_ref( ).
Suppose you have a container of objects (not pointers) and you
want to call a member function that takes an argument. The argument you pass
should come from a second container of objects. To accomplish this, the second
overloaded form of the transform( ) algorithm is used:
//: C05:MemFun2.cpp
// Applying pointers to member functions
#include <algorithm>
#include <vector>
#include <iostream>
#include <functional>
using namespace std;
class Angle {
int degrees;
public:
Angle(int deg) : degrees(deg) {}
int mul(int times) {
return degrees *= times;
}
};
int main() {
vector<Angle> va;
for(int i = 0; i < 50; i += 10)
va.push_back(Angle(i));
int x[] = { 1, 2, 3, 4, 5 };
transform(va.begin(), va.end(), x,
ostream_iterator<int>(cout, " "),
mem_fun_ref(&Angle::mul));
cout << endl;}
///:~
Because the container is holding objects,
mem_fun_ref( ) must be used with the pointer-to-member function.
This version of transform( ) takes the start and end point of the
first range (where the objects live), the starting point of second range which
holds the arguments to the member function, the destination iterator which in
this case is standard output, and the function object to call for each object;
this function object is created with mem_fun_ref( ) and the desired
pointer to member. Notice the transform( ) and
for_each( ) template functions are incomplete;
transform( ) requires that the function it calls return a value and
there is no for_each( ) that passes two arguments to the function it
calls. Thus, you cannot call a member function that returns void and
takes an argument using transform( ) or
for_each( ).
Any member function works, including those in the Standard
libraries. For example, suppose you’d like to read a file and search for
blank lines; you can use the string::empty( ) member function like
this:
//: C05:FindBlanks.cpp
// Demonstrate mem_fun_ref() with string::empty()
#include "../require.h"
#include <algorithm>
#include <list>
#include <string>
#include <fstream>
#include <functional>
using namespace std;
typedef list<string>::iterator LSI;
LSI blank(LSI begin, LSI end) {
return find_if(begin, end,
mem_fun_ref(&string::empty));
}
int main(int argc, char* argv[]) {
requireArgs(argc, 1);
ifstream in(argv[1]);
assure(in, argv[1]);
list<string> ls;
string s;
while(getline(in, s))
ls.push_back(s);
LSI lsi = blank(ls.begin(), ls.end());
while(lsi != ls.end()) {
*lsi = "A BLANK LINE";
lsi = blank(lsi, ls.end());
}
string f(argv[1]);
f += ".out";
ofstream out(f.c_str());
copy(ls.begin(), ls.end(),
ostream_iterator<string>(out, "\n"));}
///:~
The blank( ) function uses find_if( )
to locate the first blank line in the given range using
mem_fun_ref( ) with string::empty( ). After the file is
opened and read into the list, blank( ) is called repeated
times to find every blank line in the file. Notice that subsequent calls to
blank( ) use the current version of the iterator so it moves forward
to the next one. Each time a blank line is found, it is replaced with the
characters “A BLANK LINE.” All you have to do to accomplish this is
dereference the iterator, and you select the current
string.
The SGI STL (mentioned at the end of the previous chapter)
also includes additional function object templates, which allow you to write
expressions that create even more complicated function objects. Consider a more
involved program which converts strings of digits into floating point numbers,
like PtrFun2.cpp but more general. First, here’s a generator that
creates strings of integers that represent floating-point values (including an
embedded decimal point):
//: C05:NumStringGen.h
// A random number generator that produces
// strings representing floating-point numbers
#ifndef NUMSTRINGGEN_H
#define NUMSTRINGGEN_H
#include <string>
#include <cstdlib>
#include <ctime>
class NumStringGen {
const int sz; // Number of digits to make
public:
NumStringGen(int ssz = 5) : sz(ssz) {
std::srand(std::time(0));
}
std::string operator()() {
static char n[] = "0123456789";
const int nsz = 10;
std::string r(sz, ' ');
for(int i = 0; i < sz; i++)
if(i == sz/2)
r[i] = '.'; // Insert a decimal point
else
r[i] = n[std::rand() % nsz];
return r;
}
};#endif // NUMSTRINGGEN_H
///:~
You tell it how big the strings should be when you
create the NumStringGen object. The random number generator is used to
select digits, and a decimal point is placed in the middle.
The following program (which works with the Standard C++ STL
without the SGI extensions) uses NumStringGen to fill a
vector<string>. However, to use the Standard C library function
atof( ) to convert the strings to floating-point numbers, the
string objects must first be turned into char pointers, since
there is no automatic type conversion from string to char*. The
transform( ) algorithm can be used with mem_fun_ref( )
and string::c_str( ) to convert all the strings to
char*, and then these can be transformed using atof:
//: C05:MemFun3.cpp
// Using mem_fun()
#include "NumStringGen.h"
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <functional>
using namespace std;
int main() {
const int sz = 9;
vector<string> vs(sz);
// Fill it with random number strings:
generate(vs.begin(), vs.end(), NumStringGen());
copy(vs.begin(), vs.end(),
ostream_iterator<string>(cout, "\t"));
cout << endl;
const char* vcp[sz];
transform(vs.begin(), vs.end(), vcp,
mem_fun_ref(&string::c_str));
vector<double> vd;
transform(vcp,vcp + sz,back_inserter(vd),
std::atof);
copy(vd.begin(), vd.end(),
ostream_iterator<double>(cout, "\t"));
cout << endl;}
///:~
The SGI extensions to the STL contain a number of additional
function object templates that accomplish more detailed activities than the
Standard C++ function object templates, including identity (returns its
argument unchanged), project1st and project2nd (to take two
arguments and return the first or second one, respectively), select1st
and select2nd (to take a pair object and return the first or
second element, respectively), and the “compose” function
templates.
If you’re using the SGI extensions, you can make the
above program denser using one of the two “compose” function
templates. The first, compose1(f1, f2), takes the two function
objects f1 and f2 as its arguments. It produces a function object
that takes a single argument, passes it to f2, then takes the result of
the call to f2 and passes it to f1. The result of f1 is
returned. By using compose1( ), the process of converting the
string objects to char*, then converting the char* to a
floating-point number can be combined into a single operation, like
this:
//: C05:MemFun4.cpp
// Using the SGI STL compose1 function
#include "NumStringGen.h"
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <functional>
using namespace std;
int main() {
const int sz = 9;
vector<string> vs(sz);
// Fill it with random number strings:
generate(vs.begin(), vs.end(), NumStringGen());
copy(vs.begin(), vs.end(),
ostream_iterator<string>(cout, "\t"));
cout << endl;
vector<double> vd;
transform(vs.begin(), vs.end(), back_inserter(vd),
compose1(ptr_fun(atof),
mem_fun_ref(&string::c_str)));
copy(vd.begin(), vd.end(),
ostream_iterator<double>(cout, "\t"));
cout << endl;}
///:~
You can see there’s only a single call to
transform( ) now, and no intermediate holder for the char
pointers.
The second “compose” function is
compose2( ), which takes three function objects as its arguments.
The first function object is binary (it takes two arguments), and its arguments
are the results of the second and third function objects, respectively. The
function object that results from compose2( ) expects one argument,
and it feeds that argument to the second and third function objects. Here is an
example:
//: C05:Compose2.cpp
// Using the SGI STL compose2() function
#include "copy_if.h"
#include <algorithm>
#include <vector>
#include <iostream>
#include <functional>
#include <cstdlib>
#include <ctime>
using namespace std;
int main() {
srand(time(0));
vector<int> v(100);
generate(v.begin(), v.end(), rand);
transform(v.begin(), v.end(), v.begin(),
bind2nd(divides<int>(), RAND_MAX/100));
vector<int> r;
copy_if(v.begin(), v.end(), back_inserter(r),
compose2(logical_and<bool>(),
bind2nd(greater_equal<int>(), 30),
bind2nd(less_equal<int>(), 40)));
sort(r.begin(), r.end());
copy(r.begin(), r.end(),
ostream_iterator<int>(cout, " "));
cout << endl;}
///:~
The vector<int> v is first filled with random
numbers. To cut these down to size, the transform( ) algorithm is
used to divide each value by RAND_MAX/100, which will force the values to
be between 0 and 100 (making them more readable). The copy_if( )
algorithm defined later in this chapter is then used, along with a composed
function object, to copy all the elements that are greater than or equal to 30
and less than or equal to 40 into the destination vector<int> r.
Just to show how easy it is, r is sorted, and then displayed.
The arguments of compose2( ) say, in
effect:
(x >= 30) && (x <=
40)
You could also take the function object that comes from a
compose1( ) or compose2( ) call and pass it into another
“compose” expression ... but this could rapidly get very difficult
to decipher.
Instead of all this composing and transforming, you can write
your own function objects (without using the SGI extensions) as
follows:
//: C05:NoCompose.cpp
// Writing out the function objects explicitly
#include "copy_if.h"
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <functional>
#include <cstdlib>
#include <ctime>
using namespace std;
class Rgen {
const int max;
public:
Rgen(int mx = 100) : max(RAND_MAX/mx) {
srand(time(0));
}
int operator()() { return rand() / max; }
};
class BoundTest {
int top, bottom;
public:
BoundTest(int b, int t) : bottom(b), top(t) {}
bool operator()(int arg) {
return (arg >= bottom) && (arg <= top);
}
};
int main() {
vector<int> v(100);
generate(v.begin(), v.end(), Rgen());
vector<int> r;
copy_if(v.begin(), v.end(), back_inserter(r),
BoundTest(30, 40));
sort(r.begin(), r.end());
copy(r.begin(), r.end(),
ostream_iterator<int>(cout, " "));
cout << endl;}
///:~
There are a few more lines of code, but you can’t deny
that it’s much clearer and easier to understand, and therefore to
maintain.
We can thus observe two drawbacks to the SGI extensions to the
STL. The first is simply that it’s an extension; yes, you can download and
use them for free so the barriers to entry are low, but your company may be
conservative and decide that if it’s not in Standard C++, they don’t
want to use it. The second drawback is complexity. Once you get familiar and
comfortable with the idea of composing complicated functions from simple ones
you can visually parse complicated expressions and figure out what they mean.
However, my guess is that most people will find anything more than what you can
do with the Standard, non-extended STL function object notation to be
overwhelming. At some point on the complexity curve you have to bite the bullet
and write a regular class to produce your function object, and that point might
as well be the point where you can’t use the Standard C++ STL. A
stand-alone class for a function object is going to be much more readable and
maintainable than a complicated function-composition expression (although my
sense of adventure does lure me into wanting to experiment more with the SGI
extensions...).
As a final note, you can’t compose generators; you can
only create function objects whose operator( ) requires one or two
arguments.
This section provides a quick reference for when you’re
searching for the appropriate algorithm. I leave the full exploration of all the
STL algorithms to other references (see the end of this chapter, and Appendix
XX), along with the more intimate details of complexity, performance, etc. My
goal here is for you to become rapidly comfortable and facile with the
algorithms, and I will assume you will look into the more specialized references
if you need more depth of detail.
Although you will often see the algorithms described using
their full template declaration syntax, I am not doing that here because you
already know they are templates, and it’s quite easy to see what the
template arguments are from the function declarations. The type names for the
arguments provide descriptions for the types of iterators required. I think
you’ll find this form is easier to read, while you can quickly find the
full declaration in the template header file if for some reason you feel the
need.
The names of the iterator classes describe the iterator type
they must conform to. The iterator types were described in the previous chapter,
but here is a summary:
InputIterator. You (or rather, the STL algorithm
and any algorithms you write that use InputIterators) can
increment this with operator++ and dereference it with operator*
to read the value (and only read the value), but you can only read
each value once. InputIterators can be tested with
operator== and operator!=. That’s all. Because an
InputIterator is so limited, it can be used with istreams (via
istream_iterator).
OutputIterator. This can be incremented with
operator++, and dereferenced with operator* to write the
value (and only write the value), but you can only dereference/write each
value once. OutputIterators cannot be tested with operator== and
operator!=, however, because you assume that you can just keep sending
elements to the destination and that you don’t have to see if the
destination’s end marker has been reached. That is, the container that an
OutputIterator references can take an infinite number of objects, so no
end-checking is necessary. This requirement is important so that an
OutputIterator can be used with ostreams (via
ostream_iterator), but you’ll also commonly use the
“insert” iterators insert_iterator,
front_insert_iterator and back_insert_iterator (generated by the
helper templates inserter( ), front_inserter( ) and
back_inserter( )).
With both InputIterator and OutputIterator, you
cannot have multiple iterators pointing to different parts of the same range.
Just think in terms of iterators to support istreams and ostreams,
and InputIterator and OutputIterator will make perfect sense. Also
note that InputIterator and OutputIterator put the weakest
restrictions on the types of iterators they will accept, which means that you
can use any “more sophisticated” type of iterator when you see
InputIterator or OutputIterator used as STL algorithm template
arguments.
ForwardIterator. InputIterator and
OutputIterator are the most restricted, which means they’ll work
with the largest number of actual iterators. However, there are some operations
for which they are too restricted; you can only read from an
InputIterator and write to an OutputIterator, so you can’t
use them to read and modify a range, for example, and you can’t have more
than one active iterator on a particular range, or dereference such an iterator
more than once. With a ForwardIterator these restrictions are relaxed;
you can still only move forward using operator++, but you can both write
and read and you can write/read multiple times in each location. A
ForwardIterator is much more like a regular pointer, whereas
InputIterator and OutputIterator are a bit strange by
comparison.
BidirectionalIterator. Effectively, this is a
ForwardIterator that can also go backward. That is, a
BidirectionalIterator supports all the operations that a
ForwardIterator does, but in addition it has an operator--.
RandomAccessIterator. An iterator that is random access
supports all the same operations that a regular pointer does: you can add and
subtract integral values to move it forward and backward by jumps (rather than
just one element at a time), you can subscript it with operator[ ],
you can subtract one iterator from another, and iterators can be compared to see
which is greater using operator<, operator>, etc. If
you’re implementing a sorting routine or something similar, random access
iterators are necessary to be able to create an efficient algorithm.
The names used for the template parameter types consist of the
above iterator types (sometimes with a ‘1’ or ‘2’
appended to distinguish different template arguments), and may also include
other arguments, often function objects.
When describing the group of elements that an operation is
performed on, mathematical “range” notation will often be used. In
this, the square bracket means “includes the end point” while the
parenthesis means “does not include the end point.” When using
iterators, a range is determined by the iterator pointing to the initial
element, and the “past-the-end” iterator, pointing past the last
element. Since the past-the-end element is never used, the range determined by a
pair of iterators can thus be expressed as [first, last), where
first is the iterator pointing to the initial element and last is
the past-the-end iterator.
Most books and discussions of the STL algorithms arrange them
according to side effects: non-mutating algorithms don’t change the
elements in the range, mutating algorithms do change the elements, etc. These
descriptions are based more on the underlying behavior or implementation of the
algorithm – that is, the designer’s perspective. In practice, I
don’t find this a very useful categorization so I shall instead organize
them according to the problem you want to solve: are you searching for an
element or set of elements, performing an operation on each element, counting
elements, replacing elements, etc. This should help you find the one you want
more easily.
Note that all the algorithms are in the namespace std.
If you do not see a different header such as <utility> or
<numerics> above the function declarations, that means it appears
in <algorithm>.
It’s useful to create some basic tools with which to
test the algorithms.
Displaying a range is something that will be done constantly,
so here is a templatized function that allows you to print any sequence,
regardless of the type that’s in that sequence:
//: C05:PrintSequence.h
// Prints the contents of any sequence
#ifndef PRINTSEQUENCE_H
#define PRINTSEQUENCE_H
#include <iostream>
template<typename InputIter>
void print(InputIter first, InputIter last,
char* nm = "", char* sep = "\n",
std::ostream& os = std::cout) {
if(*nm != '\0') // Only if you provide a string
os << nm << ": " << sep; // is this printed
while(first != last)
os << *first++ << sep;
os << std::endl;
}
// Use template-templates to allow type deduction
// of the typename T:
template<typename T, template<typename> class C>
void print(C<T>& c, char* nm = "",
char* sep = "\n",
std::ostream& os = std::cout) {
if(*nm != '\0') // Only if you provide a string
os << nm << ": " << sep; // is this printed
std::copy(c.begin(), c.end(),
std::ostream_iterator<T>(os, " "));
cout << endl;
}#endif // PRINTSEQUENCE_H
///:~
There are two forms here, one that requires you to give an
explicit range (this allows you to print an array or a sub-sequence) and one
that prints any of the STL containers, which provides notational convenience
when printing the entire contents of that container. The second form performs
template type deduction to determine the type of T so it can be used in
the copy( ) algorithm. That trick wouldn’t work with the first
form, so the copy( ) algorithm is avoided and the copying is just
done by hand (this could have been done with the second form as well, but
it’s instructive to see a template-template in use). Because of this, you
never need to specify the type that you’re printing when you call either
template function.
The default is to print to cout with newlines as
separators, but you can change that. You may also provide a message to print at
the head of the output.
Next, it’s useful to have some generators (classes with
an operator( ) that returns values of the appropriate type) which
allow a sequence to be rapidly filled with different values.
//: C05:Generators.h
// Different ways to fill sequences
#ifndef GENERATORS_H
#define GENERATORS_H
#include <set>
#include <cstdlib>
#include <cstring>
#include <ctime>
// A generator that can skip over numbers:
class SkipGen {
int i;
int skp;
public:
SkipGen(int start = 0, int skip = 1)
: i(start), skp(skip) {}
int operator()() {
int r = i;
i += skp;
return r;
}
};
// Generate unique random numbers from 0 to mod:
class URandGen {
std::set<int> used;
int modulus;
public:
URandGen(int mod) : modulus(mod) {
std::srand(std::time(0));
}
int operator()() {
while(true) {
int i = (int)std::rand() % modulus;
if(used.find(i) == used.end()) {
used.insert(i);
return i;
}
}
}
};
// Produces random characters:
class CharGen {
static const char* source;
static const int len;
public:
CharGen() { std::srand(std::time(0)); }
char operator()() {
return source[std::rand() % len];
}
};
// Statics created here for convenience, but
// will cause problems if multiply included:
const char* CharGen::source = "ABCDEFGHIJK"
"LMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const int CharGen::len = std::strlen(source);#endif
// GENERATORS_H ///:~
To create some interesting values, the SkipGen
generator skips by the value skp each time its operator( ) is
called. You can initialize both the start value and the skip value in the
constructor.
URandGen (‘U’ for “unique”) is
a generator for random ints between 0 and mod, with the additional
constraint that each value can only be produced once (thus you must be careful
not to use up all the values). This is easily accomplished with a
set.
CharGen generates chars and can be used to fill
up a string (when treating a string as a sequence container).
You’ll note that the one member function that any generator implements is
operator( ) (with no arguments). This is what is called by the
“generate” functions.
The use of the generators and the print( )
functions is shown in the following section.
Finally, a number of the STL algorithms that move elements of
a sequence around distinguish between “stable” and
“unstable” reordering of a sequence. This refers to preserving the
original order of the elements for those elements that are equivalent but not
identical. For example, consider a sequence { c(1), b(1), c(2), a(1), b(2),
a(2) }. These elements are tested for equivalence based on their letters,
but their numbers indicate how they first appeared in the sequence. If you sort
(for example) this sequence using an unstable sort, there’s no guarantee
of any particular order among equivalent letters, so you could end up with {
a(2), a(1), b(1), b(2), c(2), c(1) }. However, if you used a stable sort, it
guarantees you will get { a(1), a(2), b(1), b(2), c(1), c(2) }.
To demonstrate the stability versus instability of algorithms
that reorder a sequence, we need some way to keep track of how the elements
originally appeared. The following is a kind of string object that keeps
track of the order in which that particular object originally appeared, using a
static map that maps NStrings to Counters. Each
NString then contains an occurrence field that indicates the order
in which this NString was discovered:
//: C05:NString.h
// A "numbered string" that indicates which
// occurrence this is of a particular word
#ifndef NSTRING_H
#define NSTRING_H
#include <string>
#include <map>
#include <iostream>
class NString {
std::string s;
int occurrence;
struct Counter {
int i;
Counter() : i(0) {}
Counter& operator++(int) {
i++;
return *this;
} // Post-incr
operator int() { return i; }
};
// Keep track of the number of occurrences:
typedef std::map<std::string, Counter> csmap;
static csmap occurMap;
public:
NString() : occurrence(0) {}
NString(const std::string& x)
: s(x), occurrence(occurMap[s]++) {}
NString(const char* x)
: s(x), occurrence(occurMap[s]++) {}
// The synthesized operator= and
// copy-constructor are OK here
friend std::ostream& operator<<(
std::ostream& os, const NString& ns) {
return os << ns.s << " ["
<< ns.occurrence << "]";
}
// Need this for sorting. Notice it only
// compares strings, not occurrences:
friend bool
operator<(const NString& l, const NString& r) {
return l.s < r.s;
}
// For sorting with greater<NString>:
friend bool
operator>(const NString& l, const NString& r) {
return l.s > r.s;
}
// To get at the string directly:
operator const std::string&() const {return s;}
};
// Allocate static member object. Done here for
// brevity, but should actually be done in a
// separate cpp file:
NString::csmap NString::occurMap;#endif
// NSTRING_H ///:~
In the constructors (one that takes a string, one that
takes a char*), the simple-looking initialization
occurrence(occurMap[s]++) performs all the work of maintaining and
assigning the occurrence counts (see the demonstration of the map class
in the previous chapter for more details).
To do an ordinary ascending sort, the only operator
that’s necessary is NString::operator<( ), however to sort
in reverse order the operator>( ) is also provided so that the
greater template can be used.
As this is just a demonstration class I am getting away with
the convenience of putting the definition of the static member occurMap
in the header file, but this will break down if the header file is included in
more than one place, so you should normally relegate all static
definitions to cpp files.
These algorithms allow you to automatically fill a range with
a particular value, or to generate a set of values for a particular range (these
were introduced in the previous chapter). The “fill” functions
insert a single value multiple times into the container, while the
“generate” functions use an object called a generator
(described earlier) to create the values to insert into the container.
void fill(ForwardIterator first, ForwardIterator last,
const T& value);
void fill_n(OutputIterator first, Size n, const
T& value);
fill( ) assigns value to every element in
the range [first, last). fill_n( ) assigns value to
n elements starting at first.
void generate(ForwardIterator first, ForwardIterator last,
Generator gen);
void generate_n(OutputIterator first, Size n,
Generator gen);
generate( ) makes a call to gen( ) for
each element in the range [first, last), presumably to produce a
different value for each element. generate_n( ) calls
gen( ) n times and assigns each result to n elements
starting at first.
The following example fills and generates into vectors.
It also shows the use of print( ):
//: C05:FillGenerateTest.cpp
// Demonstrates "fill" and "generate"
#include "Generators.h"
#include "PrintSequence.h"
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
int main() {
vector<string> v1(5);
fill(v1.begin(), v1.end(), "howdy");
print(v1, "v1", " ");
vector<string> v2;
fill_n(back_inserter(v2), 7, "bye");
print(v2.begin(), v2.end(), "v2");
vector<int> v3(10);
generate(v3.begin(), v3.end(), SkipGen(4,5));
print(v3, "v3", " ");
vector<int> v4;
generate_n(back_inserter(v4),15, URandGen(30));
print(v4, "v4", " ");}
///:~
A vector<string> is created with a pre-defined
size. Since storage has already been created for all the string objects
in the vector, fill( ) can use its assignment operator to
assign a copy of “howdy” to each space in the vector. To
print the result, the second form of print( ) is used which simply
needs a container (you don’t have to give the first and last iterators).
Also, the default newline separator is replaced with a space.
The second vector<string> v2 is not given an
initial size so back_inserter must be used to force new elements in
instead of trying to assign to existing locations. Just as an example, the other
print( ) is used which requires a range.
The generate( ) and generate_n( )
functions have the same form as the “fill” functions except that
they use a generator instead of a constant value; here, both generators are
demonstrated.
All containers have a method size( ) that will
tell you how many elements they hold. The following two algorithms count objects
only if they satisfy certain criteria.
IntegralValue count(InputIterator first, InputIterator
last,
const EqualityComparable& value);
Produces the number of elements in [first, last) that
are equivalent to value (when tested using operator==).
IntegralValue count_if(InputIterator first, InputIterator
last, Predicate pred);
Produces the number of elements in [first, last)
which each cause pred to return true.
Here, a vector<char> v is filled with
random characters (including some duplicates). A set<char> is
initialized from v, so it holds only one of each letter represented in
v. This set is used to count all the instances of all the
different characters, which are then displayed:
//: C05:Counting.cpp
// The counting algorithms
#include "PrintSequence.h"
#include "Generators.h"
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<char> v;
generate_n(back_inserter(v), 50, CharGen());
print(v, "v", "");
// Create a set of the characters in v:
set<char> cs(v.begin(), v.end());
set<char>::iterator it = cs.begin();
while(it != cs.end()) {
int n = count(v.begin(), v.end(), *it);
cout << *it << ": " << n << ", ";
it++;
}
int lc = count_if(v.begin(), v.end(),
bind2nd(greater<char>(), 'a'));
cout << "\nLowercase letters: " << lc << endl;
sort(v.begin(), v.end());
print(v, "sorted", "");}
///:~
The count_if( ) algorithm is demonstrated by
counting all the lowercase letters; the predicate is created using the
bind2nd( ) and greater function object
templates.
These algorithms allow you to move sequences around.
OutputIterator copy(InputIterator, first InputIterator
last, OutputIterator destination);
Using assignment, copies from [first, last) to
destination, incrementing destination after each assignment. Works
with almost any type of source range and almost any kind of destination. Because
assignment is used, you cannot directly insert elements into an empty container
or at the end of a container, but instead you must wrap the destination
iterator in an insert_iterator (typically by using
back_inserter( ), or inserter( ) in the case of an
associative container).
The copy algorithm is used in many examples in this
book.
BidirectionalIterator2 copy_backward(BidirectionalIterator1
first,
BidirectionalIterator1 last, BidirectionalIterator2
destinationEnd);
Like copy( ), but performs the actual copying of
the elements in reverse order. That is, the resulting sequence is the same,
it’s just that the copy happens in a different way. The source range
[first, last) is copied to the destination, but the first destination
element is destinationEnd - 1. This iterator is then decremented after
each assignment. The space in the destination range must already exist (to allow
assignment), and the destination range cannot be within the source
range.
void reverse(BidirectionalIterator first,
BidirectionalIterator last);
OutputIterator
reverse_copy(BidirectionalIterator first, BidirectionalIterator last,
OutputIterator destination);
Both forms of this function reverse the range [first,
last). reverse( ) reverses the range in place, while
reverse_copy( ) leaves the original range alone and copies the
reversed elements into destination, returning the past-the-end iterator
of the resulting range.
ForwardIterator2 swap_ranges(ForwardIterator1 first1,
ForwardIterator1 last1,
ForwardIterator2 first2);
Exchanges the contents of two ranges of equal size, by moving
from the beginning to the end of each range and swapping each set of
elements.
void rotate(ForwardIterator first, ForwardIterator middle,
ForwardIterator last);
OutputIterator rotate_copy(ForwardIterator
first, ForwardIterator middle,
ForwardIterator last,
OutputIterator destination);
Swaps the two ranges [first, middle) and [middle,
last). With rotate( ), the swap is performed in place, and with
rotate_copy( ) the original range is untouched and the rotated
version is copied into destination, returning the past-the-end iterator
of the resulting range. Note that while swap_ranges( ) requires that
the two ranges be exactly the same size, the “rotate” functions do
not.
bool next_permutation(BidirectionalIterator first,
BidirectionalIterator last);
bool
next_permutation(BidirectionalIterator first, BidirectionalIterator
last,
StrictWeakOrdering binary_pred);
bool
prev_permutation(BidirectionalIterator first, BidirectionalIterator
last);
bool prev_permutation(BidirectionalIterator first,
BidirectionalIterator last,
StrictWeakOrdering
binary_pred);
A permutation is one unique ordering of a set of
elements. If you have n unique elements, then there are n!
(n factorial) distinct possible combinations of those elements. All these
combinations can be conceptually sorted into a sequence using a lexicographical
ordering, and thus produce a concept of a “next” and
“previous” permutation. Therefore, whatever the current ordering of
elements in the range, there is a distinct “next” and
“previous” permutation in the sequence of permutations.
The next_permutation( ) and
prev_permutation( ) functions re-arrange the elements into their
next or previous permutation, and if successful return true. If there are
no more “next” permutations, it means that the elements are in
sorted order so next_permutation( ) returns false. If there
are no more “previous” permutations, it means that the elements are
in descending sorted order so previous_permutation( ) returns
false.
The versions of the functions which have a
StrictWeakOrdering argument perform the comparisons using
binary_pred instead of operator<.
void random_shuffle(RandomAccessIterator first,
RandomAccessIterator last);
void random_shuffle(RandomAccessIterator
first, RandomAccessIterator last
RandomNumberGenerator&
rand);
This function randomly rearranges the elements in the range.
It yields uniformly distributed results. The first form uses an internal random
number generator and the second uses a user-supplied random-number
generator.
BidirectionalIterator partition(BidirectionalIterator
first, BidirectionalIterator last,
Predicate
pred);
BidirectionalIterator stable_partition(BidirectionalIterator
first,
BidirectionalIterator last, Predicate pred);
The “partition” functions use pred to
organize the elements in the range [first, last) so they are before or
after the partition (a point in the range). The partition point is given by the
returned iterator. If pred(*i) is true (where i is the
iterator pointing to a particular element), then that element will be placed
before the partition point, otherwise it will be placed after the partition
point.
With partition( ), the order of the elements is
after the function call is not specified, but with
stable_parition( ) the relative order of the elements before and
after the partition point will be the same as before the partitioning
process.
This gives a basic demonstration of sequence
manipulation:
//: C05:Manipulations.cpp
// Shows basic manipulations
#include "PrintSequence.h"
#include "NString.h"
#include "Generators.h"
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
int main() {
vector<int> v1(10);
// Simple counting:
generate(v1.begin(), v1.end(), SkipGen());
print(v1, "v1", " ");
vector<int> v2(v1.size());
copy_backward(v1.begin(), v1.end(), v2.end());
print(v2, "copy_backward", " ");
reverse_copy(v1.begin(), v1.end(), v2.begin());
print(v2, "reverse_copy", " ");
reverse(v1.begin(), v1.end());
print(v1, "reverse", " ");
int half = v1.size() / 2;
// Ranges must be exactly the same size:
swap_ranges(v1.begin(), v1.begin() + half,
v1.begin() + half);
print(v1, "swap_ranges", " ");
// Start with fresh sequence:
generate(v1.begin(), v1.end(), SkipGen());
print(v1, "v1", " ");
int third = v1.size() / 3;
for(int i = 0; i < 10; i++) {
rotate(v1.begin(), v1.begin() + third,
v1.end());
print(v1, "rotate", " ");
}
cout << "Second rotate example:" << endl;
char c[] = "aabbccddeeffgghhiijj";
const char csz = strlen(c);
for(int i = 0; i < 10; i++) {
rotate(c, c + 2, c + csz);
print(c, c + csz, "", "");
}
cout << "All n! permutations of abcd:" << endl;
int nf = 4 * 3 * 2 * 1;
char p[] = "abcd";
for(int i = 0; i < nf; i++) {
next_permutation(p, p + 4);
print(p, p + 4, "", "");
}
cout << "Using prev_permutation:" << endl;
for(int i = 0; i < nf; i++) {
prev_permutation(p, p + 4);
print(p, p + 4, "", "");
}
cout << "random_shuffling a word:" << endl;
string s("hello");
cout << s << endl;
for(int i = 0; i < 5; i++) {
random_shuffle(s.begin(), s.end());
cout << s << endl;
}
NString sa[] = { "a", "b", "c", "d", "a", "b",
"c", "d", "a", "b", "c", "d", "a", "b", "c"};
const int sasz = sizeof sa / sizeof *sa;
vector<NString> ns(sa, sa + sasz);
print(ns, "ns", " ");
vector<NString>::iterator it =
partition(ns.begin(), ns.end(),
bind2nd(greater<NString>(), "b"));
cout << "Partition point: " << *it << endl;
print(ns, "", " ");
// Reload vector:
copy (sa, sa + sasz, ns.begin());
it = stable_partition(ns.begin(), ns.end(),
bind2nd(greater<NString>(), "b"));
cout << "Stable partition" << endl;
cout << "Partition point: " << *it << endl;
print(ns, "", " ");}
///:~
The best way to see the results of the above program is to run
it (you’ll probably want to redirect the output to a file).
The vector<int> v1 is initially loaded with a
simple ascending sequence and printed. You’ll see that the effect of
copy_backward( ) (which copies into v2, which is the same
size as v1) is the same as an ordinary copy. Again,
copy_backward( ) does the same thing as copy( ), it just
performs the operations in backward order.
reverse_copy( ), however, actually does created a
reversed copy, while reverse( ) performs the reversal in place.
Next, swap_ranges( ) swaps the upper half of the reversed sequence
with the lower half. Of course, the ranges could be smaller subsets of the
entire vector, as long as they are of equivalent size.
After re-creating the ascending sequence,
rotate( ) is demonstrated by rotating one third of v1
multiple times. A second rotate( ) example uses characters and just
rotates two characters at a time. This also demonstrates the flexibility of both
the STL algorithms and the print( ) template, since they can both be
used with arrays of char as easily as with anything else.
To demonstrate next_permutation( ) and
prev_permutation( ), a set of four characters “abcd” is
permuted through all n! (n factorial) possible combinations.
You’ll see from the output that the permutations move through a
strictly-defined order (that is, permuting is a deterministic
process).
A quick-and-dirty demonstration of
random_shuffle( ) is to apply it to a string and see what
words result. Because a string object has begin( ) and
end( ) member functions that return the appropriate iterators, it
too may be easily used with many of the STL algorithms. Of course, an array of
char could also have been used.
Finally, the partition( ) and
stable_partition( ) are demonstrated, using an array of
NString. You’ll note that the aggregate initialization expression
uses char arrays, but NString has a char* constructor which
is automatically used.
When partitioning a sequence, you need a predicate which will
determine whether the object belongs above or below the partition point. This
takes a single argument and returns true (the object is above the
partition point) or false (it isn’t). I could have written a
separate function or function object to do this, but for something simple, like
“the object is greater than ‘b’”, why not use the
built-in function object templates? The expression is:
bind2nd(greater<NString>(),
"b")
And to understand it, you need to pick it apart from the
middle outward. First,
greater<NString>()
produces a binary function object which compares its first and
second arguments:
return first >
second;
and returns a bool. But we don’t want a binary
predicate, and we want to compare against the constant value
“b.” So bind2nd( ) says: create a new function
object which only takes one argument, by taking this
greater<NString>( ) function and forcing the second argument
to always be “b.” The first argument (the only argument) will
be the one from the vector ns.
You’ll see from the output that with the unstable
partition, the objects are correctly above and below the partition point, but in
no particular order, whereas with the stable partition their original order is
maintained.
All of these algorithms are used for searching for one or more
objects within a range defined by the first two iterator arguments.
InputIterator find(InputIterator first, InputIterator
last,
const EqualityComparable& value);
Searches for value within a range of elements. Returns
an iterator in the range [first, last) that points to the first
occurrence of value. If value isn’t in the range, then
find( ) returns last. This is a linear search, that
is, it starts at the beginning and looks at each sequential element without
making any assumptions about the way the elements are ordered. In contrast, a
binary_search( ) (defined later) works on a sorted sequence and can
thus be much faster.
InputIterator find_if(InputIterator first, InputIterator
last, Predicate pred);
Just like find( ), find_if( ) performs
a linear search through the range. However, instead of searching for
value, find_if( ) looks for an element such that the
Predicate pred returns true when applied to that element. Returns
last if no such element can be found.
ForwardIterator adjacent_find(ForwardIterator first,
ForwardIterator last);
ForwardIterator adjacent_find(ForwardIterator
first, ForwardIterator last,
BinaryPredicate
binary_pred);
Like find( ), performs a linear search through the
range, but instead of looking for only one element it searches for two elements
that are right next to each other. The first form of the function looks for two
elements that are equivalent (via operator==). The second form looks for
two adjacent elements that, when passed together to binary_pred, produce
a true result. If two adjacent elements cannot be found, last is
returned.
ForwardIterator1 find_first_of(ForwardIterator1 first1,
ForwardIterator1 last1,
ForwardIterator2 first2, ForwardIterator2
last2);
ForwardIterator1 find_first_of(ForwardIterator1 first1,
ForwardIterator1 last1,
ForwardIterator2 first2, ForwardIterator2
last2, BinaryPredicate binary_pred);
Like find( ), performs a linear search through the
range. The first form finds the first element in the first range that is
equivalent to any of the elements in the second range. The second form finds the
first element in the first range that produces true when passed to
binary_pred along with any of the elements in the second range. When a
BinaryPredicate is used with two ranges in the algorithms, the element
from the first range becomes the first argument to binary_pred, and the
element from the second range becomes the second argument.
ForwardIterator1 search(ForwardIterator1 first1,
ForwardIterator1 last1,
ForwardIterator2 first2, ForwardIterator2
last2);
ForwardIterator1 search(ForwardIterator1 first1,
ForwardIterator1 last1,
ForwardIterator2 first2, ForwardIterator2
last2 BinaryPredicate binary_pred);
Attempts to find the entire range [first2, last2)
within the range [first1, last1). That is, it checks to see if the second
range occurs (in the exact order of the second range) within the first range,
and if so returns an iterator pointing to the place in the first range where the
second range begins. Returns last1 if no subset can be found. The first
form performs its test using operator==, while the second checks to see
if each pair of objects being compared causes binary_pred to return
true.
ForwardIterator1 find_end(ForwardIterator1 first1,
ForwardIterator1 last1,
ForwardIterator2 first2, ForwardIterator2
last2);
ForwardIterator1 find_end(ForwardIterator1 first1,
ForwardIterator1 last1,
ForwardIterator2 first2, ForwardIterator2
last2, BinaryPredicate binary_pred);
The forms and arguments are just like search( ) in
that it looks for the second range within the first range, but while
search( ) looks for the first occurrence of the second range,
find_end( ) looks for the last occurrence of the second range
within the first.
ForwardIterator search_n(ForwardIterator first,
ForwardIterator last,
Size count, const T&
value);
ForwardIterator search_n(ForwardIterator first,
ForwardIterator last,
Size count, const T& value,
BinaryPredicate binary_pred);
Looks for a group of count consecutive values in
[first, last) that are all equal to value (in the first form) or
that all cause a return value of true when passed into binary_pred
along with value (in the second form). Returns last if such a
group cannot be found.
ForwardIterator min_element(ForwardIterator first,
ForwardIterator last);
ForwardIterator min_element(ForwardIterator
first, ForwardIterator last,
BinaryPredicate
binary_pred);
Returns an iterator pointing to the first occurrence of the
smallest value in the range (there may be multiple occurrences of the smallest
value). Returns last if the range is empty. The first version performs
comparisons with operator< and the value r returned is such
that
*e < *r
is false for every element e in the range.
The second version compares using binary_pred and the value r
returned is such that binary_pred (*e, *r) is false for every element
e in the range.
ForwardIterator max_element(ForwardIterator first,
ForwardIterator last);
ForwardIterator max_element(ForwardIterator
first, ForwardIterator last,
BinaryPredicate
binary_pred);
Returns an iterator pointing to the first occurrence of the
largest value in the range (there may be multiple occurrences of the largest
value). Returns last if the range is empty. The first version performs
comparisons with operator< and the value r returned is such
that
*r < *e
is false for every element e in the range.
The second version compares using binary_pred and the value r
returned is such that binary_pred (*r, *e) is false for every element
e in the range.
void replace(ForwardIterator first, ForwardIterator last,
const T& old_value, const T& new_value);
void
replace_if(ForwardIterator first, ForwardIterator last,
Predicate
pred, const T& new_value);
OutputIterator
replace_copy(InputIterator first, InputIterator last,
OutputIterator result, const T& old_value, const T&
new_value);
OutputIterator replace_copy_if(InputIterator first,
InputIterator last,
OutputIterator result, Predicate pred, const
T& new_value);
Each of the “replace” forms moves through the
range [first, last), finding values that match a criterion and replacing
them with new_value. Both replace( ) and
replace_copy( ) simply look for old_value to replace, while
replace_if( ) and replace_copy_if( ) look for values
that satisfy the predicate pred. The “copy” versions of the
functions do not modify the original range but instead make a copy with the
replacements into result (incrementing result after each
assignment).
To provide easy viewing of the results, this example will
manipulate vectors of int. Again, not every possible version of
each algorithm will be shown (some that should be obvious have been
omitted).
//: C05:SearchReplace.cpp
// The STL search and replace algorithms
#include "PrintSequence.h"
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
struct PlusOne {
bool operator()(int i, int j) {
return j == i + 1;
}
};
class MulMoreThan {
int value;
public:
MulMoreThan(int val) : value(val) {}
bool operator()(int v, int m) {
return v * m > value;
}
};
int main() {
int a[] = { 1, 2, 3, 4, 5, 6, 6, 7, 7, 7,
8, 8, 8, 8, 11, 11, 11, 11, 11 };
const int asz = sizeof a / sizeof *a;
vector<int> v(a, a + asz);
print(v, "v", " ");
vector<int>::iterator it =
find(v.begin(), v.end(), 4);
cout << "find: " << *it << endl;
it = find_if(v.begin(), v.end(),
bind2nd(greater<int>(), 8));
cout << "find_if: " << *it << endl;
it = adjacent_find(v.begin(), v.end());
while(it != v.end()) {
cout << "adjacent_find: " << *it
<< ", " << *(it + 1) << endl;
it = adjacent_find(it + 2, v.end());
}
it = adjacent_find(v.begin(), v.end(),
PlusOne());
while(it != v.end()) {
cout << "adjacent_find PlusOne: " << *it
<< ", " << *(it + 1) << endl;
it = adjacent_find(it + 1, v.end(),
PlusOne());
}
int b[] = { 8, 11 };
const int bsz = sizeof b / sizeof *b;
print(b, b + bsz, "b", " ");
it = find_first_of(v.begin(), v.end(),
b, b + bsz);
print(it, it + bsz, "find_first_of", " ");
it = find_first_of(v.begin(), v.end(),
b, b + bsz, PlusOne());
print(it,it + bsz,"find_first_of PlusOne"," ");
it = search(v.begin(), v.end(), b, b + bsz);
print(it, it + bsz, "search", " ");
int c[] = { 5, 6, 7 };
const int csz = sizeof c / sizeof *c;
print(c, c + csz, "c", " ");
it = search(v.begin(), v.end(),
c, c + csz, PlusOne());
print(it, it + csz,"search PlusOne", " ");
int d[] = { 11, 11, 11 };
const int dsz = sizeof d / sizeof *d;
print(d, d + dsz, "d", " ");
it = find_end(v.begin(), v.end(), d, d + dsz);
print(it, v.end(),"find_end", " ");
int e[] = { 9, 9 };
print(e, e + 2, "e", " ");
it = find_end(v.begin(), v.end(),
e, e + 2, PlusOne());
print(it, v.end(),"find_end PlusOne"," ");
it = search_n(v.begin(), v.end(), 3, 7);
print(it, it + 3, "search_n 3, 7", " ");
it = search_n(v.begin(), v.end(),
6, 15, MulMoreThan(100));
print(it, it + 6,
"search_n 6, 15, MulMoreThan(100)", " ");
cout << "min_element: " <<
*min_element(v.begin(), v.end()) << endl;
cout << "max_element: " <<
*max_element(v.begin(), v.end()) << endl;
vector<int> v2;
replace_copy(v.begin(), v.end(),
back_inserter(v2), 8, 47);
print(v2, "replace_copy 8 -> 47", " ");
replace_if(v.begin(), v.end(),
bind2nd(greater_equal<int>(), 7), -1);
print(v, "replace_if >= 7 -> -1", " ");}
///:~
The example begins with two predicates: PlusOne which
is a binary predicate that returns true if the second argument is
equivalent to one plus the first argument, and MulMoreThan which returns
true if the first argument times the second argument is greater than a
value stored in the object. These binary predicates are used as tests in the
example.
In main( ), an array a is created and fed
to the constructor for vector<int> v. This vector will be used as
the target for the search and replace activities, and you’ll note that
there are duplicate elements – these will be discovered by some of the
search/replace routines.
The first test demonstrates find( ), discovering
the value 4 in v. The return value is the iterator pointing to the first
instance of 4, or the end of the input range (v.end( )) if the
search value is not found.
find_if( ) uses a predicate to determine if it has
discovered the correct element. In the above example, this predicate is created
on the fly using greater<int> (that is, “see if the first
int argument is greater than the second”) and
bind2nd( ) to fix the second argument to 8. Thus, it returns true if
the value in v is greater than 8.