Generic Algorithms

We have encountered and put to use the concept of generic classes, particularly generic containers. Generic containers could be characterized as implementations of ADTs that are re-usable across a broad spectrum of client programs. There is another equally important component to generic programming, generic algorithms. A generic algorithm is an implementation of an algorithm that is re-usable across a broad spectrum of containers.

The key idea facilitating generic algorithms in C++ is the iterator. By writing algorithm implementations interacting with iterators rather than directly with containers, the code can be applied directly to any container whose iterators satisfy the needs of the algorithm. Iterators form the interface between a generic algorithm and a container.

Generic Copy

The interfacing of an algorithm with a container is accomplished by using iterators as template parameters for the algorithm. This slide shows a typical example, the generic copy algorithm. The template parameters are "I" and "J". The algorithm uses local variables of these types:

I source_beg, source_end; // defines a range of values in a source container
J dest_beg;               // defines the beginning of a range in a destination container

and procedes to copy elements from the source range to the target range using a loop:

while (source_beg != source_end)
  *dest_beg++ = *source_beg++;

This dense but efficient code is equivalent to the following:

i = source_beg;
j = dest_beg;
while (i != source_end)
{
  *j = *i; // assign value *i to *j
  ++i;     // increment i
  ++j;     // increment j
}

There is no purpose served (other than explanatory) by declaring extra local variables in the second version, and the postfix increment calls in the first version effect the post-assignment increment of both iterators in the second version.

This template function can be invoked whenever containers have iterators with the operations *() and ++(int) defined. Thus we can invoke the function for Vector, List, Deque, or ordinary arrays (whose pointers satisfy the same conditions). Given that we have declarations

Vector <sometype> V;        // a vector object
List   <sometype> L;        // a list object
Deque  <sometype> D;        // a deque object
sometype *         A[size];  // an array

and all containers have size size, the function can be invoked to copy elements from any one of the containers to another, as in the following examples:

g_copy (V.Begin(), V.End(), L.Begin());  // copies V to L
g_copy (L.Begin(), L.End(), D.Begin());  // copies L to D
g_copy (D.Begin(), D.End(), A);          // copies D to A
g_copy (A, A + size, L.Begin());         // copies A to L

The actual code for g_copy() is re-used across all four container types.

Generic Find

This is the classic sequential search algorithm introduced in Chapter 7, converted into a generic algorithm. The input to the algorithm consists of iterarors defining a range of values. The algorithm searches through the range sequentially for a match to t. As soon as success is achieved an iterator pointing to the location of t is returned. Return of the End() iterator (generally an invalid iterator) indicates failure of the search.

The generic find algorithm can be invoked for any range in a container whose iterators have operators *() and ++(). Assume the following container declarations:

Vector <sometype> V;        // a vector object
List   <sometype> L;        // a list object
Deque  <sometype> D;        // a deque object
sometype           A[size];  // an array

and the following iterator declarations:

Vector <sometype>::Iterator Vitr;  // a vector iterator
List   <sometype>::Iterator Litr;  // a list iterator
Deque  <sometype>::Iterator Ditr;  // a deque iterator
sometype *                   Aitr;  // a pointer (array iterator)

Then generic find can be invoked as follows:

Litr = g_find (L.Begin(), L.End(), t) // location of t in List L
Vitr = g_find (V.Begin(), V.End(), t) // location of t in Vector V
Ditr = g_find (D.Begin(), D.End(), t) // location of t in Deque D
Aitr = g_find (A, A + size, t)        // location of t in array A


Generic Max

This algorithm, whose implementation is shown on the slide, returns the location of the largest element of a range in a container (the first largest, if there is more than one). The range is given by a begin/end iterator pair, and the location is returned also as an iterator. The code for this implementation is equivalent to the following more readable, less compact version:

template <class I>
I g_max_element (I beg, I end)
{
  if (beg == end)
    return end;
  I max;
  max = beg;
  ++beg;
  while (beg != end)
  {
    if (*max < *beg)
      max = beg;
    ++beg;
  }
  return max;
}

This generic algorithm can be invoked for any range in a container whose iterators have operators *() and ++(). Assume the container declarations as above and the following iterator declarations:

Vector <sometype>::Iterator Vitr;  // a vector iterator
List   <sometype>::Iterator Litr;  // a list iterator
Deque  <sometype>::Iterator Ditr;  // a deque iterator
sometype *                   Aitr;  // a pointer (array iterator)

Then generic max can be invoked as follows:

Vitr = g_max_element (V.Begin(), V.End())  // max element of V = *Vitr
Litr = g_max_element (L.Begin(), L.End())  // max element of L = *Litr
Ditr = g_max_element (D.Begin(), D.End())  // max element of D = *Ditr
Aitr = g_max_element (A, A + size)         // max element of A = *Aitr

Again, the actual code for the generic algorithm is re-used for four different kinds of containers

Generic Algorithms and Predicate Objects

Function objects are used extensively by generic algorithms. Predicate objects, in particular, are used when non-default boolean operations are needed. For example, there is an alternate form of generic max that imports a user-definable notion of order:

template <class I, class P>
I g_max_element (I beg, I end, const P& p)
{
  if (beg == end)
    return end;
  I max(beg);
  while (++beg != end)
    if (p(*max, *beg))
      max = beg;
  return max;
}

A predicate object P p is passed to the algorithm and used in place of operator <() to determine the maximum value in the incoming range. The following are example uses of this version:

TGreaterThan <sometype> gt;
Vitr = g_max_element (V.Begin(), V.End(), gt)  // min element of V = *Vitr
Litr = g_max_element (L.Begin(), L.End(), gt)  // min element of L = *Litr
Ditr = g_max_element (D.Begin(), D.End(), gt)  // min element of D = *Ditr
Aitr = g_max_element (A, A + size, gt)         // min element of A = *Aitr

Note that this invocation actually finds the minimum value, because of the substitution of greater-than for less-than.

Generic Algorithms and Function Objects

Non-boolean function objects are also used in some algorithms. One of the most used is g_for_each, shown in the slide, which applies a function object to every element in a specified range.

Generic For Each is an algorithm that applies a function object to each element in a range.

template <class I, class F>
F g_for_each (I beg, I end, F f)
{
  while (beg != end)
    f(*(beg++));
  return f;
}

The function class F may be anything that may be applied to elements of type t. It could count things, accumulate a value over the range (sum or product, for example), or modify all the objects in the range. Here is an example that conditionally modifies the elements in a range, using the MakeUpperCase function class from the previous chapter:

List<char> L;
// ... code putting elements into L
MakeUpperCase muc;
g_for_each (L.Begin(), L.End(), muc); // converts every element of L to upper case

After the call to g_for_each, all alpha characters in L are upper case.

Generic For Each

The generic algorithm g_for_each, introduced in the previous slide, is unusual in that it has a return value: a copy of the function object passed into the algorithm is returned as a value. For stateless function objects this has little utility, but for function objects that modify their own data as they are used, this can be very useful.

A "smart" version of MakeUpperCase is shown in this slide; SmartMakeUpperCase objects keep count of the number of times a letter is converted from lower to upper case. By returning a copy of this function object, a running count of the number of lower case letters converted to upper case is preserved. This example is somewhat contrived, but it illustrates some of the power of "smart" functions and how the generic for each algorithm can facilitate their utility.

Here is another example that accumulates values over a range. This function class is defined as a template class so that it may be applied to any type for which operator +() is defined.

template <typename T>
class Addemup
{
  public:
    T sum;
    Addemup (const T& init) : sum(init)
    {}
    void operator () (const T& t)
    {
      sum += t;
    }
};
List<float> L;
// ... code putting elements into L
Addemup adder(0);
adder = g_for_each (L.Begin(), L.End(), adder);
std::cout << adder.sum;

Note that

  1. adder is passed by value
  2. The state of the local copy of adder is modified during the call to g_for_each
  3. adder is returned by value, as modified

The result output to screen is the sum of all elements in the range.

Testing Generic Algorithms

/*  fga.cpp

    functionality test of generic algorithms using
    List<T>, Vector<T>, Deque<T>, and arrays of T
*/

typedef char ElementType; const char* e_t = "char";

List      < ElementType > L;  // a list
Vector    < ElementType > V;  // a vector
Deque     < ElementType > Q;  // a deque
ElementType *              A;  // an array

List      < ElementType >::Iterator Litr, Lloc;
Vector    < ElementType >::Iterator Vitr, Vloc;
Deque     < ElementType >::Iterator Qitr, Qloc;
ElementType                       *  Aitr, * Aloc;

ElementType e, s; // entry, sentinal
unsigned int i, p, size;

// define the list L somehow, and properly size all containers
// ...

// copy to vector, deque, and array
g_copy (L.Begin(), L.End(), V.Begin());
g_copy (V.Begin(), V.End(), Q.Begin());
g_copy (Q.Begin(), Q.End(), A);
// ...
 
// g_min_element
Lloc = g_min_element(L.Begin(), L.End());
Vloc = g_min_element(V.Begin(), V.End());
Qloc = g_min_element(Q.Begin(), Q.End());
Aloc = g_min_element(A, A + size);
// ...

// g_max_element
Lloc = g_max_element(L.Begin(), L.End());
Vloc = g_max_element(V.Begin(), V.End());
Qloc = g_max_element(Q.Begin(), Q.End());
Aloc = g_max_element(A, A + size);

// g_find (sequential search each container for value e):
Lloc = g_find (L.Begin(), L.End(), e);
Vloc = g_find (V.Begin(), V.End(), e);
Qloc = g_find (Q.Begin(), Q.End(), e);
Aloc = g_find (A, A + size, e);

// g_for_each (apply a SmartMakeUpperCase object to each element of each container):
SmartMakeUpperCase smuc;
smuc = g_for_each(L.Begin(), L.End(), smuc);
smuc = g_for_each(V.Begin(), V.End(), smuc);
smuc = g_for_each(Q.Begin(), Q.End(), smuc);
smuc g_for_each(A, A + size, smuc);

// output the number of conversions from lower case to upper case:
cout << "Number of case conversions: " << smuc.count;

Testing Generic Algorithms

The following is taken from a test/demonstration program that calls various generic algorithms for four different container classes.

  /*  fga.cpp

      functionality test of generic algorithms using
      List<T>, Vector<T>, Deque<T>, and arrays of T
  */

  typedef char ElementType; const char* e_t = "char";

  List      < ElementType > L;  // a list
  Vector    < ElementType > V;  // a vector
  Deque     < ElementType > Q;  // a deque
  ElementType *              A;  // an array

  List      < ElementType >::Iterator Litr, Lloc;
  Vector    < ElementType >::Iterator Vitr, Vloc;
  Deque     < ElementType >::Iterator Qitr, Qloc;
  ElementType                       *  Aitr, * Aloc;

  ElementType e, s; // entry, sentinal
  unsigned int i, p, size;

  // define the list L somehow, and properly size all containers
  // ...

  // copy to vector, deque, and array
  g_copy (L.Begin(), L.End(), V.Begin());
  g_copy (V.Begin(), V.End(), Q.Begin());
  g_copy (Q.Begin(), Q.End(), A);
  // ...
 
  // g_min_element
  Lloc = g_min_element(L.Begin(), L.End());
  Vloc = g_min_element(V.Begin(), V.End());
  Qloc = g_min_element(Q.Begin(), Q.End());
  Aloc = g_min_element(A, A + size);
  // ...

  // g_max_element
  Lloc = g_max_element(L.Begin(), L.End());
  Vloc = g_max_element(V.Begin(), V.End());
  Qloc = g_max_element(Q.Begin(), Q.End());
  Aloc = g_max_element(A, A + size);

  // g_find (sequential search each container for value e):
  Lloc = g_find (L.Begin(), L.End(), e);
  Vloc = g_find (V.Begin(), V.End(), e);
  Qloc = g_find (Q.Begin(), Q.End(), e);
  Aloc = g_find (A, A + size, e);

  // g_for_each (apply a SmartMakeUpperCase object to each element of each container):
  SmartMakeUpperCase smuc;
  smuc = g_for_each(L.Begin(), L.End(), smuc);
  smuc = g_for_each(V.Begin(), V.End(), smuc);
  smuc = g_for_each(Q.Begin(), Q.End(), smuc);
  smuc g_for_each(A, A + size, smuc);

  // output the number of conversions from lower case to upper case:
  cout << "Number of case conversions: " << smuc.count;

The source code for this demonstration program is distributed in file tests/fga.cpp. An executable version is also available in area51/fga.x.

Generic Insertion Sort

A simple generic insertion sort algorithm is presented in two versions, one using the default order determined by int operator < (const T& ,const T& ) and the other taking an order predicate class in through the template parameter.

template < class BidirectionalIterator >
void g_insertion_sort (BidirectionalIterator beg, BidirectionalIterator end)
{
  BidirectionalIterator I, J, K;
  typename BidirectionalIterator::ValueType t;
  for (I = beg; I != end; ++I)
    {
      t = *I;
      for (K = I, J = K--; J != beg && t < *K; --J, --K)
        *J = *K;
      *J = t;
    }
}

template < class BidirectionalIterator, class Comparator >
void g_insertion_sort (BidirectionalIterator beg, BidirectionalIterator end, const Comparator& cmp)
{
  BidirectionalIterator I, J, K;
  typename BidirectionalIterator::ValueType t;
  for (I = beg; I != end; ++I)
    {
      t = *I;
      for (K = I, J = K--; J != beg && cmp(t,*K); --J, --K)
        *J = *K;
      *J = t;
    }
}

Here are sample uses of both these versions of insertion sort:

List<int> L;
...
g_insertion_sort(L.Begin(), L.End());                // sorts in increasing order
g_insertion_sort(L.Begin(), L.End(), GreaterThan);   // sorts in decreasing order

One interesting point illustrated by g_insertion_sort is the way the value type of the range is needed and accessed. For this reason, these algorithms will not compile for raw pointers. To make them work for arrays, we need to specialize the templates, as follows:

// specialization for pointers
template < typename T >
void g_insertion_sort (T* beg, T* end)
{
  T *I, *J, *K;
  T t;
  for (I = beg; I != end; ++I)
    {
      t = *I;
      for (K = I, J = K--; J != beg && t < *K; --J, --K)
	*J = *K;
      *J = t;
    }
}

// specialization for pointers
template < typename T , class Comparator >
void g_insertion_sort (T* beg, T* end, const Comparator& cmp)
{
  T *I, *J, *K;
  T t;
  for (I = beg; I != end; ++I)
    {
      t = *I;
      for (K = I, J = K--; J != beg && cmp(t,*K); --J, --K)
	*J = *K;
      *J = t;
    }
}

Now one can use the same generic algorithm for vectors and arrays, as in:

Vector<int> V;
int A[size];
...
g_insertion_sort(V.Begin(), V.End());    // sorts vector V
g_insertion_sort(A, A + size);           // sorts array A

The technique relies on the feature of C++ that allows specializations of templates. When the compiler encounters more than one way to instantiate a template, it chooses the most specific. Thus, when we apply generic insertion sort to a range specified by pointers, there are two possible templates that fir. The specialization is the more specific, so it is chosen.

Generic Binary Search

There are six versions of binary search available as generic algorithms: two versions each of lower_bound, upper_bound, and binary_search. One version of each uses standard less_than order and one uses a predicate object to determine order. These algorithms require random access iterators (operator [] () and "pointer" arithmetic) and also require that the range of search be ordered (either by operator <() or by the predicate object passed in as a parameter).

The predicate version of lower_bound is shown in the slide. It is important to understand this implementation in detail, including:

All six versions of generic binary search are distributed in tcpp/gbsearch.h. Headers are presented at the end of this chapter.

Testing Generic Sort and Binary Search

/*  fgss.cpp

    "generic sort & search"
    functionality test of generic sort and binary search
    on Vector<T>, Deque<T>, and arrays of T
*/

#include <iostream.h>
#include <vector.cpp>
#include <deque.cpp>
#include <compare.cpp>
#include <gsort.h>
#include <gbsearch.h>
#include <genalg.h>

int main()
{
  typedef char                        ElementType; const char* e_t = "char";

  typedef TLessThan <ElementType>    PredicateType1;
  typedef TGreaterThan <ElementType> PredicateType2;

  Vector    < ElementType > V;  // a vector
  Deque     < ElementType > Q;  // a deque
  ElementType *              A;  // an array
  PredicateType1             LT; // predicate object
  PredicateType2             GT; // predicate object

  Vector    < ElementType > ::Iterator Vitr, Vloc;
  Deque     < ElementType > ::Iterator Qitr, Qloc;
  ElementType                         * Aitr, * Aloc;

  ElementType e, s; // entry, sentinal
  unsigned int i, size;

  cout << "Begin test of g_insertion_sort and g_binary_search < " << e_t << " >\n"
       << "Enter sentinal: ";
  cin >> s;
  cout << "Enter elements ('"
       << s
       << "' to end): ";
  cin >> e;
  while (e != s)
  {
    V.PushBack(e);
    cin >> e;
  }

  cout << "V as entered:     " << V << '\n';

  // copy to deque
  size = V.Size();
  for (i = 0; i < size; ++i)
  {
    if (i%2)
      Q.PushFront(V[i]);
    else
      Q.PushBack(V[i]);
  }
  cout << "Q as copied:      " << Q << '\n';

  // copy to array
  A = new ElementType [size];
  for (i = 0; i < size; ++i)
    A[i] = V[i];
  cout << "A as copied:      ";
  for (i = 0; i < size; ++i)
    cout << A[i];
  cout << '\n';

  // apply generic insertion sort to each container, display, and recopy
  g_insertion_sort(V.Begin(), V.End(), GT);
  cout << "g_insertion_sort(V,>): " << V << '\n';
  g_copy(Q.Begin(), Q.End(), V.Begin());

  g_insertion_sort(Q.Begin(), Q.End(), GT);
  cout << "g_insertion_sort(Q,>): " << Q << '\n';
  g_copy(V.Begin(), V.End(), Q.Begin());

  g_insertion_sort(A, A + size, GT);
  cout << "g_insertion_sort(A,>): ";
  for (i = 0; i < size; ++i)
    cout << A[i];
  cout << '\n';
  g_copy(Q.Begin(), Q.End(), A);

  cout << "V as recopied:    " << V << '\n';
  cout << "Q as recopied:    " << Q << '\n';
  cout << "A as recopied:    ";
  for (i = 0; i < size; ++i)
    cout << A[i];
  cout << '\n';

  // apply generic insertion sort again to each container and display
  g_insertion_sort(V.Begin(), V.End());
  cout << "g_insertion_sort(V):   " << V << '\n';

  g_insertion_sort(Q.Begin(), Q.End());
  cout << "g_insertion_sort(Q):   " << Q << '\n';

  g_insertion_sort(A, A + size, LT);
  cout << "g_insertion_sort(A,<): ";
  for (i = 0; i < size; ++i)
    cout << A[i];
  cout << '\n';

  // now do binary search
  while(1)
  {
    cout << "Enter search value ('"
       << s
       << "' to quit): ";
    cin >> e;
    if (e == s) break;

    Vloc = g_lower_bound(V.Begin(), V.End(), e, LT);
    cout << "V = " << V << '\n';
    cout << "    ";
    for (Vitr = V.Begin(); Vitr != Vloc; ++Vitr) cout << ' ';
    cout << "^lb\n";
    Vloc = g_upper_bound(V.Begin(), V.End(), e, LT);
    cout << "V = " << V << '\n';
    cout << "    ";
    for (Vitr = V.Begin(); Vitr != Vloc; ++Vitr) cout << ' ';
    cout << "^ub\n";

    Qloc = g_lower_bound(Q.Begin(), Q.End(), e, LT);
    cout << "Q = " << Q << '\n';
    cout << "    ";
    for (Qitr = Q.Begin(); Qitr != Qloc; ++Qitr) cout << ' ';
    cout << "^lb\n";
    Qloc = g_upper_bound(Q.Begin(), Q.End(), e, LT);
    cout << "Q = " << Q << '\n';
    cout << "    ";
    for (Qitr = Q.Begin(); Qitr != Qloc; ++Qitr) cout << ' ';
    cout << "^ub\n";

    Aloc = g_lower_bound(A, A + size, e, LT);
    cout << "A = ";
    for (Aitr = A; Aitr != A + size; ++Aitr) cout << *Aitr; cout << '\n';
    cout << "    ";
    for (Aitr = A; Aitr != Aloc; ++Aitr) cout << ' ';
    cout << "^lb\n";
    Aloc = g_upper_bound(A, A + size, e, LT);
    cout << "A = ";
    for (Aitr = A; Aitr != A + size; ++Aitr) cout << *Aitr; cout << '\n';
    cout << "    ";
    for (Aitr = A; Aitr != Aloc; ++Aitr) cout << ' ';
    cout << "^ub\n";

    cout << e << " is ";
    if (!g_binary_search(V.Begin(), V.End(), e, LT))
      cout << "not ";
    cout << "in V\n";
    cout << e << " is ";
    if (!g_binary_search(Q.Begin(), Q.End(), e, LT))
      cout << "not ";
    cout << "in Q\n";
    cout << e << " is ";
    if (!g_binary_search(A, A + size, e, LT))
      cout << "not ";
    cout << "in A\n";
  }
  cout << "End test of g_insertion_sort/g_binary_search < " << e_t << " >\n";
  return 0;
}

(See tests/fgss.cpp.)

Generic Algorithms Catalog

Generic algorithms fall into functional categories differentiated by their assumptions and runtime characteristics and are located in separate files by category. The following table shows the categories and the file names (duplicated in the slide):

The following is a listing of all of the generic algorithms in these files, by category. Namespaces are not shown.

/*  genalg.h

    General utility generic algorithms for non-random-access iterators

    I, J  iterator classes (usually I = input iterator, J = output)
    T     ValueType
    P     predicate class (may be unary or binary, depending on context)
    F     function class  (context determined)
*/

template <class I, typename T>
void g_fill (I beg, I end, const T& t)

template <class I, typename T>
void g_fill_n (I beg, int n, const T& t)

template <class I, typename T>
void g_fill_n (I beg, unsigned int n, const T& t)

template <class I, class J>
void g_copy (I source_beg, I source_end, J dest_beg)

template <class I, class J>
void g_copy_backward (I source_beg, I source_end, J dest_beg)

template <class I>
I g_min_element (I beg, I end)

template <class I, class P>
I g_min_element (I beg, I end, const P& p)

template <class I>
I g_max_element (I beg, I end)

template <class I, class P>
I g_max_element (I beg, I end, const P& p)

template <class I, typename T>
I g_find (I beg, I end, const T& t)

template <class I, class P>
I g_find_if (I beg, I end, const P& p)

template <class I, class F>
F g_for_each(I beg, I end, F f)
/*  gsort.h

    generic sort algorithm applicable to a range specified by iterators
    implementing insertion sort algoriithm

*/

template < class BidirectionalIterator >
void g_insertion_sort (BidirectionalIterator beg, BidirectionalIterator end);

template < class BidirectionalIterator, class Comparator >
void g_insertion_sort (BidirectionalIterator beg, BidirectionalIterator end, const Comparator& cmp);

// specialization for pointers
template < typename T >
void g_insertion_sort (T* beg, T* end);

// specialization for pointers
template < typename T , class Comparator >
void g_insertion_sort (T* beg, T* end, const Comparator& cmp);

/*  gset.h

    generic set algorithms

    All of these algorithms have a common set of preconditions, as follows:

    1. I1, I2 are input iterators and I3 is output iterator. (I3 may be an insert iterator.)
    2. P is a predicate order class defined on the input ranges (transitive,
       anti-reflexive, and dichotomous)
    3. The two input ranges are sorted using p where present and < otherwise
    4. The input range ValueType is convertible to the output range ValueType

    A common postcondition is: the output range is sorted using p where present
    or < otherise
*/

template <class I1, class I2, class I3, class P>
void g_set_union(I1 beg1, I1 end1, I2 beg2, I2 end2, I3 dest, P p)
// range3 = range1 union range2

template <class I1, class I2, class I3>
void g_set_union(I1 beg1, I1 end1, I2 beg2, I2 end2, I3 dest) 
// default order version uses operator <

template <class I1, class I2, class I3, class P>
void g_set_merge (I1 beg1, I1 end1, I2 beg2, I2 end2, I3 dest, const P& p)
// range3 = range1 merge range2 

template <class I1, class I2, class I3>
void g_set_merge (I1 beg1, I1 end1, I2 beg2, I2 end2, I3 dest)
// default order version uses operator < ()

template <class I1, class I2, class I3, class P>
void g_set_intersection(I1 beg1, I1 end1, I2 beg2, I2 end2, I3 dest, const P& p)
// range3 = range1 intersection range2 

template <class I1, class I2, class I3>
void g_set_intersection(I1 beg1, I1 end1, I2 beg2, I2 end2, I3 dest)
// default order version uses operator <()

template <class I1, class I2, class I3, class P>
void g_set_difference(I1 beg1, I1 end1, I2 beg2, I2 end2, I3 dest, const P& p)
// range3 = range1 difference range2 

template <class I1, class I2, class I3>
void g_set_difference(I1 beg1, I1 end1, I2 beg2, I2 end2, I3 dest)
// default order version uses operator <()

template <class I1, class I2, class P>
int g_subset_of(I1 beg1, I1 end1, I2 beg2, I2 end2, P p)
// returns true iff range 1 is contained in range 2

template <class I1, class I2>
int g_subset_of (I1 beg1, I1 end1, I2 beg2, I2 end2)
// default version uses operator <()

/*  gbsearch.h

    Generic binary search algorithms for random access iterators

    These algrithms take parameters of type I, T, and optionally a third
    parameter of type P. 

    about I
    =======

    I is a random access iterator type; this means that the bracket operator

      T& operator [] (unsigned int);

    is defined and the usual "pointer" arithmetic operations are defined:

      I&    operator += (long n);
      I&    operator -= (long n);
      I     operator +  (long n) const;
      long  operator -  (const I& I2) const;

    Examples include: ordinary array iterators (pointers);
                      Vector<T>::Iterator;
                      Dequeue<T>::Iterator;

    about T
    =======

    T is the ValueType of the iterator I

    about P 
    =======

    P is a predicate class used for order; an object LessThan of class P
    has the function evaluation operator overloaded with the prototype

      bool operator () (const T&, const T&);

    Note that P must be used to order the elements so that P can work
    as a search condition.

    g_lower_bound() and g_upper_bound() implement binary search for random
    access iterators.  It is assumed that the elements of iteration are in
    nondecreasing order (using operator < or an optional predicate object).

    g_lower_bound returns an iterator to the first location containing the
    search value if found, and the location where it would be otherwise. 
    g_upper_bound returns an iterator to the first location whose element is
    greater than the search value if such exists or the end iterator
    otherwise. I.e. (assuming L = lower bound and U = upper bound):

    if t is in the collection: t == *L, t < *U, and L and U are the
    smallest iterators in the sequence with these properties; thus,
    beg = L and end = U mark the range of t in the collection;

    if t is not in the collection: L == U, and these iterators point to the
    location where t could be inserted and maintain nondecreasing order.

    bool binary_search() uses the same algorithm, returning true iff found.

    All these versions of binary search run in time O(log size).
*/

#include <compare.cpp>

template <class I, typename T, class P>
I g_lower_bound (I beg, I end, const T& val, const P& LessThan)
// pre:    I is a random access iterator (operator [] and "pointer" arithmetic)
//         I has ValueType T
//         beg + n = end for some n >= 0
//         beg[0] ... beg[n-1] are in non-decreasing order using LessThan
// post:   no state is changed
// return: itr = beg + i, where beg[i-1] < val <= beg[i]; or
//         itr = end if no such i exists

template <class I, typename T, class P>
I g_upper_bound (I beg, I end, const T& val, const P& LessThan)
// pre:    I is a random access iterator (operator [] and "pointer" arithmetic)
//         I has ValueType T
//         beg + n = end for some n >= 0
//         beg[0] ... beg[n-1] are in non-decreasing order using LessThan
// post:   no state is changed
// return: itr =  beg + i, where beg[i-1] <= val < beg[i]; or
//         itr = end if no such i exists

template <class I, typename T, class P>
int g_binary_search (I beg, I end, const T& val, const P& LessThan)
// pre:    I is a random access iterator (operator [] and "pointer" arithmetic)
//         I has ValueType T
//         beg + n = end for some n >= 0
//         beg[0] ... beg[n-1] are in non-decreasing order using LessThan
// post:   no state is changed
// return: true if found, false if not found

template <class I, typename T>
I g_lower_bound (I beg, I end, const T& val)

template <class I, typename T>
I g_upper_bound (I beg, I end, const T& val)

template <class I, typename T>
int g_binary_search (I beg, I end, const T& val)

/*  gheap.h

    The generic heap algorithms

    These algorithms use three types I, P, T
    but only two template parameters <I,P>

      I: a random access iterator class
      P: a predicate class for T
      T: the ValueType for underlying container on which I iterates

    A random access iterator I is one for which the bracket operator 
      T& operator [] (unsigned int );
    is defined and for which "pointer" arithmetic is defined. 
    Examples include: ordinary arrays of T; Vector<T>; and Deque<T>

    A predicate class is one for which the function evaluation operator 
      bool operator () (const T&, const T&);
    is defined. An object LessThan of class P is used to determine order
    in T: "LessThan (a,b) == true" iff "a < b"

    The type T is not explicitly mentioned in the template (or parameter)
    list for g_heap_sort, but it is needed for the call to g_XC.

*/

#include <compare.cpp>

template <class I, class P>
void g_heap_sort (I beg, I end, const P& LessThan)
// Implements heapsort for the iterators
// Heapsort has the following attributes:
//   * fast:     runtime O(size*log(size))
//   * in-place: the sort happens in the space implied by the iterators
//   * NOT stable -- the original order of objects a, b may change when
//                   a == b
// pre:  I is a random access iterator class (operator [] and "pointer" arithmetic)
//       T is the ValueType of I
//       P is a predicate class for type T
// post: the range of values specified by the iterators is ordered LessThan, 
//       i.e., LessThan(a,b) == true  ==>  a comes before b in the container

template <class I>
void g_heap_sort (I beg, I end)
// the default order version

template <class I, class P>
void g_push_heap (I beg, I end, const P& LessThan)
// Defn: a < b means LessThan(a,b) == true
// Defn: POT means parent >= children
// Pre:  the range [beg,end)   is valid
//       if the range [beg,end-1) is valid (i.e., [beg,end) is non-void)
//         then the range [beg,end-1) is a POT
// Post: the range [beg,end)   is a POT

template <class I>
void g_push_heap (I beg, I end)
// the default order version

template <class I, class P>
void g_pop_heap (I beg, I end, const P& LessThan)
// Defn: a < b means LessThan(a,b) == true
// Defn: POT means parent >= children
// Pre:  the range [beg,end)   is valid
//       the range [beg,end-1) is valid (i.e., [beg,end) is non-void)
//       the range [beg,end)   is a POT 
// Post: the largest element beg[0] is removed from the range [beg,end-1)
//       the range [beg,end-1) is a POT
//       the old largest element (i.e., beg[0]) is copied to 
//         the end of the old range (i.e., to beg[end - beg - 1])

template <class I>
void g_pop_heap (I beg, I end)
// the default order version

template <typename T>
void g_XC (T& t1, T& t2)