Algorithms as Patterns for Code

Steven J. Zeil

Last modified: Oct 26, 2023
Contents:

Templates describe common “patterns” for similar classes and functions that differ only in a few names.

Templates come in two varieties

Class templates are patterns for similar classes. Function templates are patterns for similar functions.

In this lesson, we’ll explore why the idea of a pattern for several different codes can be useful.

1 Same Pattern – Different Code

Let’s look at some common array manipulation functions that you have probably seen in earlier courses.

arrayops.h
/***** arrayops.h *****/
#ifndef ARRAYOPS_H
#define ARRAYOPS_H

/**
 *
 * Assume the elements of the array are already in order
 * Find the position where value could be added to keep
 *    everything in order, and insert it there.
 * Return the position where it was inserted
 *  - Assumes that we have a separate integer (size) indicating how
 *     many elements are in the array
 *  - and that the "true" size of the array is at least one larger 
 *      than the current value of that counter
 *
 *  @param array array into which to add an element
 *  @param size  number of data elements in hte array. Must be less than
 *               the number of elements allocated for the array. Incremented
 *               upon output from this function.
 *  @param value value to add into the array
 *  @return the position where the element was added
 */
int addInOrder (int* array, int& size, int value);

/*
 * Search an array for a given value, returning the index where 
 *    found or -1 if not found.
 *
 * From Malik, C++ Programming: From Problem Analysis to Program Design
 *
 * @param list the array to be searched
 * @param listLength the number of data elements in the array
 * @param searchItem the value to search for
 * @return the position at which value was found, or -1 if not found
 */
int seqSearch(const int list[], int listLength, int searchItem);


/*
 * Search an ordered array for a given value, returning the index where 
 *    found or -1 if not found.
 * @param list the array to be searched. Must be ordered.
 * @param listLength the number of data elements in the array
 * @param searchItem the value to search for
 * @return the position at which value was found, or -1 if not found
 */
int seqOrderedSearch(const int list[], int listLength, int searchItem);

/*
 * Removes an element from the indicated position in the array, moving
 * all elements in higher positions down one to fill in the gap.
 * 
 *  @param array array from which to remove an element
 *  @param size  number of data elements in the array. Decremented
 *               upon output from this function.
 *  @param index position from which to remove the element. Must be < size
 */
void removeElement (int* array, int& size, int index);



/**
 * Performs the standard binary search using two comparisons per level.
 * Returns index where item is found or -1 if not found
 *
 * From Weiss,  Data Structures and Algorithm Analysis, 4e
 * ( modified SJ Zeil)
 *
 * @param a array to search. Must be ordered.
 * @param size number of elements i nthe array
 * @param x value to search for
 * @return position where found or -1 if not found
 */
int binarySearch( const int* a, int size, const int & x );


#endif
arrayops.cpp
/***** arrayops.cpp *****/

#include "arrayops.h"

/**
 *
 * Assume the elements of the array are already in order
 * Find the position where value could be added to keep
 *    everything in order, and insert it there.
 * Return the position where it was inserted
 *  - Assumes that we have a separate integer (size) indicating how
 *     many elements are in the array
 *  - and that the "true" size of the array is at least one larger 
 *      than the current value of that counter
 *
 *  @param array array into which to add an element
 *  @param size  number of data elements in hte array. Must be less than
 *               the number of elements allocated for the array. Incremented
 *               upon output from this function.
 *  @param value value to add into the array
 *  @return the position where the element was added
 */
int addInOrder (int* array, int& size, int value)
{
  // Make room for the insertion
  int toBeMoved = size - 1;
  while (toBeMoved >= 0 && value < array[toBeMoved]) {
    array[toBeMoved+1] = array[toBeMoved];
    --toBeMoved;
  }
  // Insert the new value
  array[toBeMoved+1] = value;
  ++size;
  return toBeMoved+1;
}


/*
 * Search an array for a given value, returning the index where 
 *    found or -1 if not found.
 *
 * From Malik, C++ Programming: From Problem Analysis to Program Design
 *
 * @param list the array to be searched
 * @param listLength the number of data elements in the array
 * @param searchItem the value to search for
 * @return the position at which value was found, or -1 if not found
 */
int seqSearch(const int list[], int listLength, int searchItem)
{
    int loc;

    for (loc = 0; loc < listLength; loc++)
        if (list[loc] == searchItem)
            return loc;

    return -1;
}




/*
 * Search an ordered array for a given value, returning the index where 
 *    found or -1 if not found.
 * @param list the array to be searched. Must be ordered.
 * @param listLength the number of data elements in the array
 * @param searchItem the value to search for
 * @return the position at which value was found, or -1 if not found
 */
int seqOrderedSearch(const int list[], int listLength, int searchItem)
{
    int loc = 0;

    while (loc < listLength && list[loc] < searchItem)
      {
       ++loc;
      }
    if (loc < listLength && list[loc] == searchItem)
       return loc;
    else
       return -1;
}


/*
 * Removes an element from the indicated position in the array, moving
 * all elements in higher positions down one to fill in the gap.
 * 
 *  @param array array from which to remove an element
 *  @param size  number of data elements in the array. Decremented
 *               upon output from this function.
 *  @param index position from which to remove the element. Must be < size
 */
void removeElement (int* array, int& size, int index)
{
  int toBeMoved = index + 1;
  while (toBeMoved < size) {
    array[toBeMoved] = array[toBeMoved+1];
    ++toBeMoved;
  }
  --size;
}



const int NOT_FOUND = -1;

/**
 * Performs the standard binary search using two comparisons per level.
 * Returns index where item is found or -1 if not found
 *
 * From Weiss,  Data Structures and Algorithm Analysis, 4e
 * ( modified SJ Zeil)
 *
 * @param a array to search. Must be ordered.
 * @param size number of elements i nthe array
 * @param x value to search for
 * @return position where found or -1 if not found
 */
int binarySearch( const int* a, int size, const int & x )
{
    int low = 0, high = a.size( ) - 1;

    while( low <= high )
    {
        int mid = ( low + high ) / 2;

        if( a[ mid ] < x )
            low = mid + 1;
        else if( a[ mid ] > x )
            high = mid - 1;
        else
            return mid;   // Found
    }
    return NOT_FOUND;     // NOT_FOUND is defined as -1
}

Notice that our binarySearch routine operates on arrays of int. What would we have to do if we wanted to have a binary search over arrays of double, or of string?

We would simply need to make a new copy of the binarySearch declaration and body, replacing some of the occurrences of “int” by “double” or “string”.

In a large project, we might actually wind up with a lot of copies of binarySearch. (And in some projects, the original binary-search-of-ints might not even be one of the copies we would need!)

2 Generalizing the Element Type

One way we might generalize these functions to rewrite them to work on arrays of an imaginary type T.

arrayopst.h
/***** arrayops.h *****/
#ifndef ARRAYOPS_H
#define ARRAYOPS_H

/**
 *
 * Assume the elements of the array are already in order
 * Find the position where value could be added to keep
 *    everything in order, and insert it there.
 * Return the position where it was inserted
 *  - Assumes that we have a separate integer (size) indicating how
 *     many elements are in the array
 *  - and that the "true" size of the array is at least one larger 
 *      than the current value of that counter
 *
 *  @param array array into which to add an element
 *  @param size  number of data elements in hte array. Must be less than
 *               the number of elements allocated for the array. Incremented
 *               upon output from this function.
 *  @param value value to add into the array
 *  @return the position where the element was added
 */
int addInOrder (T* array, int& size, T value);

/*
 * Search an array for a given value, returning the index where 
 *    found or -1 if not found.
 *
 * From Malik, C++ Programming: From Problem Analysis to Program Design
 *
 * @param list the array to be searched
 * @param listLength the number of data elements in the array
 * @param searchItem the value to search for
 * @return the position at which value was found, or -1 if not found
 */
int seqSearch(const T list[], int listLength, T searchItem);


/*
 * Search an ordered array for a given value, returning the index where 
 *    found or -1 if not found.
 * @param list the array to be searched. Must be ordered.
 * @param listLength the number of data elements in the array
 * @param searchItem the value to search for
 * @return the position at which value was found, or -1 if not found
 */
int seqOrderedSearch(const T list[], int listLength, T searchItem);

/*
 * Removes an element from the indicated position in the array, moving
 * all elements in higher positions down one to fill in the gap.
 * 
 *  @param array array from which to remove an element
 *  @param size  number of data elements in the array. Decremented
 *               upon output from this function.
 *  @param index position from which to remove the element. Must be < size
 */
void removeElement (T* array, int& size, int index);



/**
 * Performs the standard binary search using two comparisons per level.
 * Returns index where item is found or -1 if not found
 *
 * From Weiss,  Data Structures and Algorithm Analysis, 4e
 * ( modified SJ Zeil)
 *
 * @param a array to search. Must be ordered.
 * @param size number of elements i nthe array
 * @param x value to search for
 * @return position where found or -1 if not found
 */
int binarySearch( const T* a, int size, const T & x );


#endif
arrayopst.cpp
/***** arrayops.cpp *****/

#include "arrayops.h"

/**
 *
 * Assume the elements of the array are already in order
 * Find the position where value could be added to keep
 *    everything in order, and insert it there.
 * Return the position where it was inserted
 *  - Assumes that we have a separate integer (size) indicating how
 *     many elements are in the array
 *  - and that the "true" size of the array is at least one larger 
 *      than the current value of that counter
 *
 *  @param array array into which to add an element
 *  @param size  number of data elements in hte array. Must be less than
 *               the number of elements allocated for the array. Incremented
 *               upon output from this function.
 *  @param value value to add into the array
 *  @return the position where the element was added
 */
int addInOrder (T* array, int& size, T value)
{
  // Make room for the insertion
  int toBeMoved = size - 1;
  while (toBeMoved >= 0 && value < array[toBeMoved]) {
    array[toBeMoved+1] = array[toBeMoved];
    --toBeMoved;
  }
  // Insert the new value
  array[toBeMoved+1] = value;
  ++size;
  return toBeMoved+1;
}


/*
 * Search an array for a given value, returning the index where 
 *    found or -1 if not found.
 *
 * From Malik, C++ Programming: From Problem Analysis to Program Design
 *
 * @param list the array to be searched
 * @param listLength the number of data elements in the array
 * @param searchItem the value to search for
 * @return the position at which value was found, or -1 if not found
 */
int seqSearch(const T list[], int listLength, T searchItem)
{
    int loc;

    for (loc = 0; loc < listLength; loc++)
        if (list[loc] == searchItem)
            return loc;

    return -1;
}




/*
 * Search an ordered array for a given value, returning the index where 
 *    found or -1 if not found.
 * @param list the array to be searched. Must be ordered.
 * @param listLength the number of data elements in the array
 * @param searchItem the value to search for
 * @return the position at which value was found, or -1 if not found
 */
int seqOrderedSearch(const T list[], int listLength, T searchItem)
{
    int loc = 0;

    while (loc < listLength && list[loc] < searchItem)
      {
       ++loc;
      }
    if (loc < listLength && list[loc] == searchItem)
       return loc;
    else
       return -1;
}


/*
 * Removes an element from the indicated position in the array, moving
 * all elements in higher positions down one to fill in the gap.
 * 
 *  @param array array from which to remove an element
 *  @param size  number of data elements in the array. Decremented
 *               upon output from this function.
 *  @param index position from which to remove the element. Must be < size
 */
void removeElement (T* array, int& size, int index)
{
  int toBeMoved = index + 1;
  while (toBeMoved < size) {
    array[toBeMoved] = array[toBeMoved+1];
    ++toBeMoved;
  }
  --size;
}



const int NOT_FOUND = -1;

/**
 * Performs the standard binary search using two comparisons per level.
 * Returns index where item is found or -1 if not found
 *
 * From Weiss,  Data Structures and Algorithm Analysis, 4e
 * ( modified SJ Zeil)
 *
 * @param a array to search. Must be ordered.
 * @param size number of elements i nthe array
 * @param x value to search for
 * @return position where found or -1 if not found
 */
int binarySearch( const T* a, int size, const T & x )
{
    int low = 0, high = a.size( ) - 1;

    while( low <= high )
    {
        int mid = ( low + high ) / 2;

        if( a[ mid ] < x )
            low = mid + 1;
        else if( a[ mid ] > x )
            high = mid - 1;
        else
            return mid;   // Found
    }
    return NOT_FOUND;     // NOT_FOUND is defined as -1
}

Now, we don’t actually have a predefined type by that name, so if were to try and use these functions this way:

#include "arrayops.h"
int importantNumbers[100];
int n;
  ⋮
addInOrder (importantNumbers, n, 42);

we would get a compilation error from somewhere inside arrayops.h complaining that T is undefined.

We can use any of these four functions, provided that we define T first:

typedef int T;
#include "arrayops.h"  // T == int

int importantNumbers[100];
int n;
  ⋮
addInOrder (importantNumbers, n, 42);

and this works just great until we start writing more complicated programs and, one day, discover that we need to manipulate an array of int and an array of some other type:

typedef int T;
#include "arrayops.h"  // T == int

typedef std::string T;
#include "arrayops.h"  // T == std::string

int importantNumbers[100];
std::string favoriteNames[100];
int n1, n2;
  ⋮
addInOrder (importantNumbers, n1, 42);
addInOrder (favoriteNames, n2, "Arthur Dent");

Again, we will get compilation errors, this time at the second typedef, because we can’t define T to mean two different things at the same time.

3 Making Copies

Instead of using typedefs to redefine T, we could simply make a distinct copy of arrayops.h and arrayops.cpp for each kind of array, using an ordinary text editor to replace T by a “real” type name:

arrayopsdouble.h
#ifndef ARRAYOPSDOUBLE_H
#define ARRAYOPDOUBLE_H

/**
 *
 * Assume the elements of the array are already in order
 * Find the position where value could be added to keep
 *    everything in order, and insert it there.
 * Return the position where it was inserted
 *  - Assumes that we have a separate integer (size) indicating how
 *     many elements are in the array
 *  - and that the "true" size of the array is at least one larger 
 *      than the current value of that counter
 *
 *  @param array array into which to add an element
 *  @param size  number of data elements in hte array. Must be less than
 *               the number of elements allocated for the array. Incremented
 *               upon output from this function.
 *  @param value value to add into the array
 *  @return the position where the element was added
 */
int addInOrder (double* array, int& size, double value);

/*
 * Search an array for a given value, returning the index where 
 *    found or -1 if not found.
 *
 * From Malik, C++ Programming: From Problem Analysis to Program Design
 *
 * @param list the array to be searched
 * @param listLength the number of data elements in the array
 * @param searchItem the value to search for
 * @return the position at which value was found, or -1 if not found
 */
int seqSearch(const double list[], int listLength, double searchItem);


/*
 * Search an ordered array for a given value, returning the index where 
 *    found or -1 if not found.
 * @param list the array to be searched. Must be ordered.
 * @param listLength the number of data elements in the array
 * @param searchItem the value to search for
 * @return the position at which value was found, or -1 if not found
 */
int seqOrderedSearch(const double list[], int listLength, double searchItem);

/*
 * Removes an element from the indicated position in the array, moving
 * all elements in higher positions down one to fill in the gap.
 * 
 *  @param array array from which to remove an element
 *  @param size  number of data elements in the array. Decremented
 *               upon output from this function.
 *  @param index position from which to remove the element. Must be < size
 */
void removeElement (double* array, int& size, int index);



/**
 * Performs the standard binary search using two comparisons per level.
 * Returns index where item is found or -1 if not found
 *
 * From Weiss,  Data Structures and Algorithm Analysis, 4e
 * ( modified SJ Zeil)
 *
 * @param a array to search. Must be ordered.
 * @param size number of elements i nthe array
 * @param x value to search for
 * @return position where found or -1 if not found
 */
int binarySearch( const double* a, int size, const double & x );


#endif
arrayopsstring.h
#ifndef ARRAYOPSSTRING_H
#define ARRAYOPSTRING_H



#include <string>


/**
 *
 * Assume the elements of the array are already in order
 * Find the position where value could be added to keep
 *    everything in order, and insert it there.
 * Return the position where it was inserted
 *  - Assumes that we have a separate integer (size) indicating how
 *     many elements are in the array
 *  - and that the "true" size of the array is at least one larger 
 *      than the current value of that counter
 *
 *  @param array array into which to add an element
 *  @param size  number of data elements in hte array. Must be less than
 *               the number of elements allocated for the array. Incremented
 *               upon output from this function.
 *  @param value value to add into the array
 *  @return the position where the element was added
 */
int addInOrder (std::string* array, int& size, std::string value);

/*
 * Search an array for a given value, returning the index where 
 *    found or -1 if not found.
 *
 * From Malik, C++ Programming: From Problem Analysis to Program Design
 *
 * @param list the array to be searched
 * @param listLength the number of data elements in the array
 * @param searchItem the value to search for
 * @return the position at which value was found, or -1 if not found
 */
int seqSearch(const std::string list[], int listLength, std::string searchItem);


/*
 * Search an ordered array for a given value, returning the index where 
 *    found or -1 if not found.
 * @param list the array to be searched. Must be ordered.
 * @param listLength the number of data elements in the array
 * @param searchItem the value to search for
 * @return the position at which value was found, or -1 if not found
 */
int seqOrderedSearch(const std::string list[], int listLength, std::string searchItem);

/*
 * Removes an element from the indicated position in the array, moving
 * all elements in higher positions down one to fill in the gap.
 * 
 *  @param array array from which to remove an element
 *  @param size  number of data elements in the array. Decremented
 *               upon output from this function.
 *  @param index position from which to remove the element. Must be < size
 */
void removeElement (std::string* array, int& size, int index);



/**
 * Performs the standard binary search using two comparisons per level.
 * Returns index where item is found or -1 if not found
 *
 * From Weiss,  Data Structures and Algorithm Analysis, 4e
 * ( modified SJ Zeil)
 *
 * @param a array to search. Must be ordered.
 * @param size number of elements i nthe array
 * @param x value to search for
 * @return position where found or -1 if not found
 */
int binarySearch( const std::string* a, int size, const std::string & x );


#endif

(Similar changes would be made to the .cpp files.)

With just a few additional edits to the #include’s, #ifdef’s, and so on, we get a perfectly workable pair of .h and .cpp files. (Don’t you just love those global find-and-replace commands in editors?)

Of course, in some situations, we may wish that we had used a slightly less common dummy type name than just “T”, so that we wouldn’t need to worry about changing lines like:

string Temp = "This is a Temporary string.";

to

string doubleemp = "doublehis is a doubleemporary string.";

But after the first time we get burned on that, we’ll never need reminding again.

A similar copy-and-edit could give us arrayopsdouble.cpp and arrayopsstring.cpp. Then we can write our code this way:

#include "arrayopsdouble.h"
#include "arrayopsstring.h"

double importantNumbers[100];
std::string favoriteNames[100];
int n1, n2;
  ⋮
addInORder (importantNumbers, n1, 42.0);
addInORder (favoriteNames, n2, "Arthur Dent");

Not too bad. But doing this in a large program does get to be a bit of a project management headache. Suppose we found a bug in our implementation of addInOrder. We would not only have to fix the problem in arrayops.cpp, but also in arrayopsdouble.cpp and arrayopsstring.cpp. In fact, we would have to remember every copy that has been made of the original files, and either insert our bug fix into those copies or else regenerate the copies from the fixed arrayops.cpp.

This isn’t impossible. Programmers working in C, Pascal, and other programming languages have been doing just this for decades. But since it is a common situation, and it does involve a fair amount of work, some programming language designers eventually found a way to do the same thing automatically.

In C++, this is done via templates, the subjects of our next readings.