Patterns: Working with Arrays

Steven Zeil

Last modified: Jul 04, 2014

Contents:
1. Static & Dynamic Allocation
1.1 Static Allocation
1.2 Dynamic Allocation
2. Partially Filled Arrays
2.1 Adding Elements
2.2 Searching for Elements
2.3 Removing Elements
3. Arrays and Templates
4. Vectors
4.1 Vectors versus Arrays
5. Multi-Dimension Arrays
5.1 Arrays of Arrays

1. Static & Dynamic Allocation

1.1 Static Allocation


Static Allocation

const int MaxItems = 100;

// Names of items
std::string itemNames[MaxItems];


How Many Elements Do We Need?

If we don’t know how many elements we really need, we need to guess

1.2 Dynamic Allocation


Dynamic Allocation


Example: bidders.h

extern int nBidders;
extern Bidder* bidders;

void readBidders (std::istream& in);

Example: bidders.cpp

int nBidders;
Bidder* bidders;


/**
 * Read all bidders from the indicated file
 */
void readBidders (istream& in)
{
    in >> nBidders;                          ➊
    bidders = new Bidder[nBidders];          ➋
    for (int i = 0; i < nBidders; ++i)
    {
        read (in, bidders[i]);               ➌
    }
}


void cleanUpBidders()
{
  delete [] bidders;
}
Here we read the desired size of the arrays from the input
Here we use that value to actually allocate arrays of the desired size
Once that is done, we can access the arrays in the usual manner using the [].

Dynamic Array Declarations are Confusing

extern Bidder* bidders;


Why Are Arrays Different?

Because arrays are “really” pointers …

2. Partially Filled Arrays

In our prior examples, we have typically dealt with arrays that were just large enough to hold the data we wanted, or that were filled immediately to a certain fixed size and then, so long as we continued working with the array, that size never varied.

Now we turn to another common pattern: sometimes we add and remove elements to arrays as we progress with our algorithm. We might not even know ahead of time how many elements we will wind up with until we reach the end of the program.

Consider, for example, a spell checker that needs to accumulate a list of misspelled words from a document. That list would start out at zero. As we discover words in the document that are not in our dictionary, we would add them, one by one, to our list of misspellings. We won’t be able to predict just how long the list will get until we’ve finished the spellcheck.


Partially Filled Arrays

To work with a partially filled array, we generally need to have access to

With that in mind, let’s look at some typical operations on partially filled arrays.

2.1 Adding Elements


Adding Elements

Variations include


Add to the end

void addToEnd (std::string* array, int& size, std::string value)
{
   array[size] = value;
   ++size;
}


Add to the middle

addElement (array, 3, 1, "Smith");


Add to the middle: implementation

addElement.cpp


Add in order


addInOrder (array, 3, "Clarke");


Add in order implementation

This works:

int addInOrder (std::string* array, int& size,
    std::string value)
{
  // Find where to insert
  int pos = 0;
  while (pos < size && value > array[pos])
    ++pos;
  addElement (array, size, pos, value);
  return pos;
}


Add in order (streamlined)

This is a bit faster:

int addInOrder ((std::string* array, int& size,
    std::string 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;
}

Try This:
You can try out the two addInOrder functions here.

2.2 Searching for Elements


Sequential Search

Search an array for a given value, returning the index where found or –1 if not found.

seqSearch.cpp


Sequential Search 2

Search an array for a given value, returning the index where 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;
}

Sequential Search (Ordered Data)

Search an array for a given value, returning the index where found or –1 if not found.


seqOrderedSearch

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;
}

Try This:
You can try out the sequential search and sequential ordered search functions here.

2.3 Removing Elements


Removing Elements

Try This:
You can try out the removeElement function here.

3. Arrays and Templates


arrayUtils - first draft

The functions we have developed in this section can be used in many different programs, so it might be useful to collect them into an “Array Utilities” module.

arrayUtils0.h

arrayUtils0.cpp

testArrayUtils0.cpp

Question: The main function will not compile. Why?

Answer:


Overloading

One solution is to provide different versions of each function for each kind of array:

// Search an ordered array for a given value, returning the index where 
//    found or -1 if not found.
int seqOrderedSearch(const int list[], int listLength, int searchItem);
int seqOrderedSearch(const char list[], int listLength, char searchItem);
int seqOrderedSearch(const double list[], int listLength, double searchItem);
int seqOrderedSearch(const float list[], int listLength, float searchItem);
int seqOrderedSearch(const std::string list[], int listLength, std::string searchItem);

with nearly identical function bodies for each one.


Function Templates

A function template is a pattern for an infinite number of possible functions.


A Simple Template

swap.cpp


Using A Function Template

#include <algorithm>
using namespace std;
   ⋮
string a = "abc";
string b = "bcde";
swap (a, b); // compiler replaces "T" by "string"
int i = 0;
int j = 2;
swap (i, j); // compiler replaces "T" by "int"


Some Other std Function Templates


Building a Library of Array Templates

Second try, using templates:

arrayUtils.h

testArrayUtils.cpp


Function Templates Are Not Functions

Is it a problem that we have the bodies in a .h file?


Patterns versus Instances

In the same way that

4. Vectors


Keeping Information Together

One criticism of functions like

void addToEnd (std::string* array, int& size, 
               std::string value);
int addInOrder (std::string* array, int& size,
    std::string value);

is that they separate the array, the size, and the capacity


Wrapping arrays within structs

One solution: use a struct to gather the related elements together:

/// A collection of items
struct ItemSequence {
   static const int capacity = 500;
   int size;
   Item data[capacity];
};


A Better Version

Using dynamic allocation, we can be more flexible about the capacity:

struct ItemSequence {
   int capacity;
   int size;
   Item* data;

   ItemSequence (int cap);
   void addToEnd (Item item);
};


Implementing the Sequence

ItemSequence::ItemSequence (int cap)
   : capacity(cap), size(0)
{
  data = new Item[capacity];
}

void ItemSequence::addToEnd (Item item)
{
  if (size < capacity)
  {
    data[size] = item;
    ++size;
  }
  else
    cerr << "**Error: ItemSequence is full" << endl;
}

This is, however, such a common pattern, that the designers of C++ had pity on us and did even better.


Vectors – the Un-Array

The vector is an array-like structure provided in the std header <vector>.


Declaring Vectors

std::vector<int> vi; // a vector of 0 ints
std::vector<std::string> vs (10); // a vector of 10
                                  //   empty strings
std::vector<float> vf (5, 1.0); // a vector of 5 
                                //    floats, all 1.0


Accessing Elements in a Vector

Use the [] just as with an array:

vector<int> v(10);
for (int i = 0; i < 10; ++i)
  {
   int j;
   cin >> j;
   v[i] = j + 1;
   cout << v[i] << endl;
  }


Size of a Vector


void foo (vector<int>& v) {
  for (int i = 0; i < v.size(); ++i)
    {
     int j;
     cin >> j;
     v[i] = j + 1;
     cout << v[i] << endl;
    }
}



Adding to a Vector

Adding to the end:


Adding to the Middle

template <class Vector, class T>
void addElement (Vector& v, int index, T value)
{
  // Make room for the insertion
  int toBeMoved = v.size() - 1;
  v.push_back(value); // expand vector by 1 slot
  while (toBeMoved >= index) {
    v[toBeMoved+1] = v[toBeMoved];
    --toBeMoved;
  }
  // Insert the new value
  v[index] = value;
}


Adding to the Middle: v2

Actually, vectors provide a built-in operation for inserting into the middle:

string* a; // array of strings
vector<string> v;
int k, n;
   ⋮
v.insert (v.begin()+k, "Hello");
addElement (a, n, k, "Hello");

The last two statements do the same thing.


Add in Order to Vector

template <class Vector, class T>
int addInOrder (Vector& v, T value)
{
  // Make room for the insertion
  int toBeMoved = v.size() - 1;
  v.push_back(value); // expand vector by 1 slot
  while (toBeMoved >= 0 && value < v[toBeMoved]) {
    v[toBeMoved+1] = v[toBeMoved];
    --toBeMoved;
  }
  // Insert the new value
  v[toBeMoved+1] = value;
  return toBeMoved+1;
}

4.1 Vectors versus Arrays


Advantages of Vectors


Disadvantages of Vectors

5. Multi-Dimension Arrays


Multi-Dimension Arrays


Example

test2dimfixed.cpp

5.1 Arrays of Arrays


Arrays of Arrays

test2dimRowsCols.cpp


Linearized Arrays

We can map 2-D arrays onto a linear structure

int index (int i, j, numCols)
{
  return j + i * numColss;
}


Linearized Array Code

int* array;
array = new int[numRows*numCols];