In The Beginning...

We've been using variables to store values since day 1 or 2. But, each variable can store only one value at a time. Any new value that is stored there overwrites the old one and it is gone:

    #include <iostream>

    using namespace std;

    int main(void)
    {
        short n;
        cout << "Initial n value is " << n
             << ".  (Garbage, but probably 0...)\n";
        n = 4;
        cout << "New n value is " << n
             << ".  (Better be 4!)\n";
        n = 12;
        cout << "Now n value is " << n
             << ".  (So long 4...hello 12...)\n";
        return 0;
    }

Now we'll learn how to make a variable to represent an entire list of values. Just think, one variable to hold many values! (Hmm... didn't strings do that with multiple characters? Must be something like that...)

Another class In A Library

We've heard on and off about 'classes'. cin and cout are class type objects. The string class let us store strings of characters in objects. We know we're eventually going to learn to write our own class types. However, to gain a little more experience with using classes that the standard library provides to us, we'll investigate the vector. The vector class is a container for collecting together many, many values of whatever type we choose. It can store collections of strings, collections of doubles, collections of short integers, collections of boolean values, ...even collections of characters! It is found in the vector library, oddly enough. So, to use the vector class, we need to have:

    #include <vector>

at the top of our program. Then, to declare a variable (or object) of type vector, we simply use it like any other type...except that we must tell it what types of items (elements) will be in its list:

    vector<double> heights;

The type inside the angle-brackets is known as the base-type or element type of the vector. So heights, here, is an object that keeps track of a list of values all of whose type is double.

We won't have a need to create constant vectors this semester (it is rarely, if ever, done, in fact). What we do do, however, is pass vector objects to functions as constant references for that 'fast' by-value effect we discussed earlier:

    ... f(... const vector<long> & student_ids ...);

This function has amongst its argument list one which is a constant reference to a vector of long integers. The function will be calling this vector student_ids during its run. (The '...' parts are not real code, but represent where other arguments and/or the return type could/would be. Just like 'f' is not a real function name...ever!)

With class You Get Function

As we've seen from both cin, cout, and strings, classes provide functions for their objects to use. The vector class is no different. It gives us many functions for normal use and some for enhanced/advanced use.

To call such functions, simply use the name of your vector variable/object, a dot, and the function call following. For instance, if we wanted to find out how many items were in a vector, we'd use the size() function:

    vector<string> names;

    // do something to fill in the names vector...

    cout << "I've collected " << names.size()
         << " names!" << endl;

In the table of functions below, e is an element/value of the same type as the vector's base-type. Also, 'calling vector' refers to the vector object which is on the left of the dot (.) operator for the call. For instance, in:

    cout << names.size();

names is the 'calling vector' for the size() function.

Several other functions are available, here is a table of those you'll find most useful:

Generalized HeadDescription
size() returns the number of elements in the 'calling vector' (this number is of type vector<XXX>::size_type where XXX is the base-type of the 'calling vector')
empty() returns true if the 'calling vector' has no contents (false otherwise)
clear() forces the 'calling vector' to become empty (throws away all currently contained elements)
resize(desired_size) causes the 'calling vector' to contain the desired number of elements
if this number is greater than the old size(), the new elements will be default constructed (built-in types become their 'zero' value)
if this number is less than the old size(), the extra elements will be removed
capacity() returns the number of elements the 'calling vector' might be expected to hold
this should be larger than or equal to the value returned by size()
push_back(e) puts a new element (e) on the back of the 'calling vector's list
if there is not currently room for the new element, the vector will automatically grow to accomodate
pop_back() removes a single element from the back of the 'calling vector's list
front() returns a reference to the element at the front of the 'calling vector's list
this can only work if the vector has at least one element!
back() returns a reference to the element at the back of the 'calling vector's list
this can only work if the vector has at least one element!

Containers of a Feather

Note that there are many similarities between strings and vectors. One of the most useful is that both strings and vectors support integral-typed positions to use with the subscript ([]) operator. (Recall that such positions are 0-based, unsigned integer typed values. For the string class this type was known as string::size_type. For the vector class, it will be vector<XXX>::size_type where the XXX is the name of the base type you've chosen for your vector variable/object.) (Note: The assignment operator (=) can be used to assign whole vectors to one another, also.) Both have a size() function, an empty() function, and a clear() function. The syntax of declaring and using their size_type's are nearly identical (except that you have to mention the vector's base-type in the declaration).

All of these similarities (and more!) were not accidental. They were designed this way to keep the 'learning curve' shallow for new users of these library features.

Notice that searching was left out of the vector repertroire, we'll have to investigate our own means of searching next!

But On With The Show!

So, let's do something with these vectors, shall we? Let's say that a teacher wanted to enter all her students' scores on the last test to see if any adjustment might be in order.

(Note: students always think that they want a curve, but this act actually forces all the scores to fit to the standard bell curve. Doing so would definitely bring failing students up toward the C range -- where the largest part of the curve is. But it would also drag A/B students down toward that central hump as well. Not generally a good idea!)

Good adjustments to perform to a poor grade distribution are either scaling (where each person's score is multiplied by a certain amount, say 1.05) or shifting (where a certain amount is added to each person's score, say 5). But, first we need to know if such adjustments are necessary or not!

vector Input

Now let's do as we pretended to do in the beginning and gather all the students' grades to store them in a vector together:

    vector<double> grades;
    double grade;

    cout << "\nEnter your students' scores ('q' to end):\n";
    cin >> grade;
    while (!cin.fail())
    {
        grades.push_back(grade);
        cin >> grade;
    }
    cin.clear();
    cin.ignore(numeric_limits<streamsize>::max(), '\n');

Here we've declared a vector of double's to hold the students' grades on the exam. We also have a single double to hold each grade as it is entered for the first time -- before we add it to the vector collection. For entry, we've used the unknown-count, failure-flagged loop we used back when we were summing numbers. The only change is that the 'process data' phase isn't adding the grades onto a sum but storing them in the vector.

This storing is done with the vector function push_back(). As described above, this function adds the new item to the end of the vector's list. So, when we first declared the grades vector, it was empty (much like a string declared without any initial value):

    grades
    |

But as the user enters their data (let's say 90, 75, 82, 41, 96, 103, 88, and 72 are their students' scores), the vector adds each new item to the end of its list:

    'cin >> grade'         |   grades vector becomes
         reads in          |
   ------------------------+------------------------------------------------
     90                    |   | 90 |
     75                    |   | 90 | 75 |
     82                    |   | 90 | 75 | 82 |
     41                    |   | 90 | 75 | 82 | 41 |
     96                    |   | 90 | 75 | 82 | 41 | 96 |
     103                   |   | 90 | 75 | 82 | 41 | 96 | 103 |
     88                    |   | 90 | 75 | 82 | 41 | 96 | 103 | 88 |
     72                    |   | 90 | 75 | 82 | 41 | 96 | 103 | 88 | 72 |

Finally, when the user enters 'q', 'x', "done", "end", or some such non-numeric value, cin will fail and the loop ends -- not attempting to perform another push_back. The post-loop clean-up code clears cin's memory of the failure and ignores the rest of the input buffer.

vector Processing -- Examining Data

Now that we have the data entered, let's figure out the average so the teacher can decide if the class is doing okay or not:

    // (at top of program...after 'using' statement...)
    typedef vector<double>::size_type vecD_sz;

    // (in program code...after input loop above)
    sum = 0;    // (sum should be double)
    for (vecD_sz g = 0; g != grades.size(); ++g)
    {
        sum += grades[g];
    }

Note how we are using size_type's and subscripting ([]) just like we did with string's to walk from item to item of the vector and (in this case) add each one onto a sum (note that sum is made a double because the elements in grades are double's). We've even used the typedef'inition like we'd done with the string class. (Again, 'g' here is the size_type which represents a position in the vector. 'grades[g]' is the actual data in the position to which 'g' is the offset.)

Now to find the average:

    avg = grades.size() == 0 ? 0                    // (avg should be double)
                             : sum / grades.size();

Here we look at how many elements were entered in the input loop via the size() function of the vector. If nothing was entered, we don't want to divide by 0, so we just set the average to 0. (This is technically incorrect, but the CompSci motto is largely known to be 'garbage in, garbage out'...)

(BTW, the avg variable here is of data type double not because sum was nor because grades' base-type was, but rather because anytime you take the average of two or more numeric values, you are likely to end up with decimal places -- whether they were present in the original values or not!)

Now that we know the average, we can present this to the teacher and ask if they want to take corrective action or not. If they do, we could ask if they want to scale or shift, but I'm only going to show the code for shifting here. (If you want your grades scaled, you'll just have to do that for yourself. *shrug*)

Standard Maximum Finding Code

So, let's assume that they felt (especially with that 41 above) that something should be done. We'll need to find out how much to shift the grades by. A common method is to find the highest grade, subtract it from 100 (percent), and add that much to everyone's grade. This will move that highest person to a 100 (%) and everyone else will move up apportionately. To do this, we need to know what the the highest score was:

    // assume maximum was 100 (%)
    max_grade = 100;         // (max_grade should be double)
    if (grades.size() != 0)
    {
        // assume first grade is largest (it's the largest we've seen so far!)
        max_grade = grades[0];  // (could also do '= grades.front();')
        for (vecD_sz g = 1;   // start at second grade
             g != grades.size(); ++g)
        {
            if (grades[g] > max_grade)    // if this grade is larger than max
            {
                max_grade = grades[g];      // store new max grade
            }
        }
    }
    // else there were no grades so we'll go with the 100 above (see below
    // for why)

Note how the largest grade is assumed to be the first in the list (position 0) (if anything was in the list). This is standard procedure since the first one is the largest one we've looked at so far. (And it is the only one we know to exist...)

The loop then begins looking for a larger score from the second position (1). We don't need to bother checking the first position since we already assumed it was the largest and it can't possibly be larger than itself! This loop may not execute, of course: if there was only one grade entered. But that's fine since we'd have that one grade stored as our maximum already.

vector Processing -- Altering Data

Once we have the maximum grade, we just need to shift everyone's grades over by 100-max_grade:

    // (in program code...after max code above)
    for (vecD_sz g = 0; g != grades.size(); ++g)
    {
        grades[g] += 100 - max_grade;  // this will be a 0 shift if max_grade
                                       // 'default'ed above the previous if
    }

Notice how we are now changing the items in the vector. Recall that the subscript operator ([]) allows you to both view and alter the contents of a single element -- just like it did for strings. In our case, we write the sum of the current value at that position and our shifting amount (100-max_grade). Again, this will make the highest score now be 100 (%) and the rest will move up apportionately.

Problem-Specific Adjustment of Maximum Algorithm

Unfortunately, looking over our sample input, we realize that this code is naive. The maximum score in the sample set is 103 and so everyone's grades would be shifted up by -3...which is actually down by 3! I think what we'd prefer is to shift by a positive amount...*grin*

The shifting code is still fine, but we'll need to adjust our maximum finding code to only find the maximum value less than 100.

Above, we assumed the first score in the list was our maximum. But what if the 103 was first in the list instead of toward the end? Then we'd assume our maximum was a value larger than 100! Not good. Instead, we need to look for the first value less than 100 and make that our assumed maximum:

    g = 0;      // (g is, again, a vecD_sz)
    while (g != grades.size() && grades[g] >= 100)
    {
        ++g;
    }
    max_grade = 100;        // assume 100 for maximum
    if (g != grades.size())    // if something under 100 was found
    {
        max_grade = grades[g];
        for (++g; g != grades.size(); ++g)
        {
            if (grades[g] < 100 &&    // if this grade is under 100  AND
                grades[g] > max_grade)        // if this grade is larger than max
            {
                max_grade = grades[g];    // store new max grade
            }
        }
    }
    // max_grade is now either 100 or the largest score under 100

Now executing the shifting code from above will actually move everyone up! (Or leave everyone alone if everyone scored over 100...well, it could happen...)

Printing vector Elements

Once we've shifted everyone's grades, the teacher will probably want to see the new scores and average. To print the scores we can just use a little for loop like this:

    cout << "\nNew scores:\n";
    for (vecD_sz g = 0; g != grades.size(); ++g)
    {
        cout << grades[g] << '\n';
    }

This walks through the updated list and prints each item -- one per line. (See the examples for some really nice code to print more than one item on each line. It's called n_per_line()...)

Of course, to print the average, we use our vast statistics experience to realize that if everyone was shifted up by 10, the average would also go up by 10 and so:

    cout << "\nAverage:  " << (avg + 100 - max_grade) << '\n';

Passing vectors to Functions

Of course, if all of this were being placed in the main program, it would be around 100 lines long now. Since our goal for easy maintenance is to keep each function around 20-30 lines long, maybe we should learn to pass vectors to functions so we can break up this monstrous main()!

vector Arguments We Change

First, the input code:

    vector<double> grades;
    double grade;

    cout << "\nEnter your students' scores ('q' to end):\n";
    cin >> grade;
    while (!cin.fail())
    {
        grades.push_back(grade);
        cin >> grade;
    }
    cin.clear();
    cin.ignore(numeric_limits<streamsize>::max(), '\n');

To make this a function, realize that the vector itself is our result -- our product/goal/answer. The individual grades entered are merely stepping stones -- all those values are placed into the vector and subsequently the 'grade' variable is overwritten. Since a vector can be a potentially large thing, we probably don't want to copy it back and forth too much. So we should probably have our function reference the vector of its caller to make changes directly in their vector (rather than change a local vector and then copy the whole thing back to the caller's space).

Then, genericizing the names and prompt:

void read_dbl_fail(vector<double> & vec, const string & prompt)
{
    double num;
    vec.clear();              // empty vector of contents -- fresh start
    cout << prompt;
    cin >> num;
    while (!cin.fail())
    {
        vec.push_back(num);
        cin >> num;
    }
    cin.clear();
    cin.ignore(numeric_limits<streamsize>::max(), '\n');

    return;
}

Notice how we've also changed so that we empty out the vector before beginning to fill it. This ensures there aren't any old data (grades or whatever) in the vector. If there were, we'd just be adding these new values onto the end of that old list. (*ponder* *shrug*)

And here's an appropriate prototype:

void read_dbl_fail(vector<double> & vec,
                   const string & prompt = "");

(Like usual, the prompt defaults out to nothing if the caller feels they don't need a prompt or want to prompt for themselves.) Of course, you'd be adding appropriate comments to both the prototype and definition in your own program...*smile*

Hmm...thinking about it, the caller may want to use our function to append to a vector they already have -- not just fill a new one from scratch. Let's allow this:

void read_dbl_fail(vector<double> & vec, bool append = false,
                   const string & prompt = "");

// ...

void read_dbl_fail(vector<double> & vec, bool append,
                   const string & prompt)
{
    double num;
    if (!append)
    {
        vec.clear();          // empty vector of contents -- fresh start
    }
    // no else: just go ahead and add to end of [current elements in] vector...

// ...

The rest of the code need not change...

(The decision as to the order of the append and prompt arguments is a tricky matter. Recall that the caller can only leave off default'ed arguments from the end of the argument list. So the question is, then "Will the caller be more likely to want us to prompt for them or to append to the end of their vector?"

The current set-up assumes that the caller is more likely to want to append to the end of their vector than to want us to prompt for them. If you feel that this assumption is in error, feel free to change their order. Just remember to do this in the prototype, the definition, and any calls using either feature!

vector Arguments We Don't Change

Next, let's take that maximum finding code and place it in a function:

    g = 0;      // (g is, again, a vecD_sz)
    while (g != grades.size() && grades[g] >= 100)
    {
        ++g;
    }
    max_grade = 100;        // assume 100 for maximum
    if (g != grades.size())    // if something under 100 was found
    {
        max_grade = grades[g];
        for (++g; g != grades.size(); ++g)
        {
            if (grades[g] < 100 &&    // if this grade is under 100  AND
                grades[g] > max_grade)        // if this grade is larger than max
            {
                max_grade = grades[g];    // store new max grade
            }
        }
    }
    // max_grade is now either 100 or largest score under 100

We'll need to have the maximum grade as our answer, the grades aren't changed during the examination process, and then genericizing:

double max_less_than(const vector<double> & vec, double cap, double def_val,
                     vecD_sz end_before, vecD_sz begin_at)
{
    double max = def_val;        // assume def_val for maximum
    vecD_sz v = begin_at;
    while (v != end_before && vec[v] >= cap)
    {
        ++v;
    }
    if (v != end_before)    // if something under cap was found
    {
        max = vec[v];
        for (++v; v != end_before; ++v)
        {
            if (vec[v] < cap &&    // if this value is under cap  AND
                vec[v] > max)              // if this value is larger than max
            {
                max = vec[v];    // store new max value
            }
        }
    }
    // max is now either def_val or largest value under cap
    return max;
}

All we need to know during processing of the vector is what position to begin searching for the maximum at and where to end [before]. The other inputs are the default maximum value (for when nothing is under the cap) and the cap itself.

Now, a prototype:

double max_less_than(const vector<double> & vec, double cap, double def_val,
                     vecD_sz end_before, vecD_sz begin_at = 0);

inline
double max_less_than(const vector<double> & vec, double cap,
                     double def_val)
{
    return max_less_than(vec, cap, def_val, vec.size());
}

Here we not only prototype the function we wrote, but also provide a companion inline version that takes a constant vector (by reference to avoid the copying), and cap and default values and passes all these things through to the original function -- calling the vector's size() function along the way (the begin_at defaults to 0). This allows the caller more flexibility in expression. If they want to just pass us their entire vector for searching, they can. If they want to pass us the beginning and ending of [a part of] their vector, they can. This is a common practice with vector processing functions and is considered very good style. (Note, we couldn't have done this with input since that vector had no starting or ending positions to speak of -- no elements at all initially. We needed the vector itself to fill in.)

(Okay, so we could have let them specify that they wanted to overwrite a portion of the vector during input, but that would have been a whole other function. The one we'd written focused on adding to the end of a vector for either appending or new growth.)

Other Examples

Here are a few other vector functions you might extract from the above program 'design':

void shift(vector<double> & vec, double shift_by, vecD_sz end_before,
           vecD_sz begin_at)
{
    for (vecD_sz v = begin_at; v != end_before; ++v)
    {
        vec[v] += shift_by;
    }
    return;
}

void print(const vector<double> & vec, vecD_sz end_before,
           vecD_sz begin_at)
{
    for (vecD_sz v = begin_at; v != end_before; ++v)
    {
        cout << vec[v] << '\n';
    }
    return;
}

double sum(const vector<double> & vec, vecD_sz end_before,
           vecD_sz begin_at)
{
    double total = 0;
    for (vecD_sz v = begin_at; v != end_before; ++v)
    {
        total += vec[v];
    }
    return total;
}

(Again, there is a nicer printing function in the online examples...)

And prototypes for these:

void shift(vector<double> & vec, double shift_by, vecD_sz end_before,
           vecD_sz begin_at = 0);

inline
void shift(vector<double> & vec, double shift_by)
{
    shift(vec, shift_by, vec.size());
    return;
}

void print(const vector<double> & vec, vecD_sz end_before,
           vecD_sz begin_at = 0);

inline
void print(const vector<double> & vec)
{
    print(vec, vec.size());
    return;
}

double sum(const vector<double> & vec, vecD_sz end_before,
           vecD_sz begin_at = 0);

inline
double sum(const vector<double> & vec)
{
    return sum(vec, vec.size());
}

Again, note the inline companions to make the caller's life easier...

(Also note that if you were at all worried about the caller passing a begin_at that was greater than or equal to their end_before, you could change all the !='s in the functions' loop conditions to <'s to make them a bit more safe.)

Placing It In A Library

If we wanted to make some of these now truly generic functions into a library, we'd need to remember to #include the vector library in our interface and to place std:: in front of each mention of the vector type (including on our typedef's for the size_type!). Also, we'd probably want to include the generic maximum algorithm -- since it belongs in a general vector (of double) processing library):

// (prototype -- and inline helper -- in interface; see the std::...:)
    double maximum(const std::vector<double> & vec, double def_val,
                   vecD_sz end_before, vecD_sz begin_at = 0);

    inline
    double maximum(const std::vector<double> & vec, double def_val)
    {
        return maximum(vec, def_val, vec.size());
    }


// (function definition in implementation; no std:: here...:)
    double maximum(const vector<double> & vec, double def_val,
                   vecD_sz end_before, vecD_sz begin_at)
    {
        // assume maximum was their default
        double max = def_val;
        if (end_before-begin_at != 0)     // recall that we're [beg, end) so if this is 0,
                                          // there's nothing in the range
        {
            // assume first value is largest
                 // (it's the largest we've seen so far!)
            max = vec[begin_at];
            for (vecD_sz v = begin_at+1;   // start at second value
                 v != end_before; ++v)
            {
                if (vec[v] > max)    // if this value is larger than max
                {
                    max = vec[v];      // store new max value
                }
            }
        }
        // else there were no values so we'll go with the default above
        return max;
    }

Another Example Altogether

The other day, a student approached me asking about sorting (placing into order) strings that contained numbers. So, let's say that you were given a vector full of strings whose contents were numbers:

    | "1" | "2" | "10" | "3" | "11" | "20" | "25" | "5" | "4" | "42" |

Given such a vector and sorting it with standard string comparisons, we would end up with the following order:

    1
    10
    11
    2
    20
    25
    3
    4
    42
    5

This seems odd at first glance, but recall the rules for string comparison: compare character by character until an end or a difference is found. Either the shorter string or the one whose differing character is earlier in the ASCII code is considered 'less than' or to come before. So, indeed, not only is "1" less than "10", but "10" is less than "2" (since the '1' character that differs from the '2' character comes earlier in ASCII).

To fix this, we could take several approaches:

    "01"
    "02"
    "03"
    "04"
    "05"
    "10"
    "11"
    "20"
    "25"
    "42"

Here a '0' has been 'pre-pended' to (i.e. inserted in front of) each numeric string that didn't 'line up' with the others in the list. (Note that the crux of the problem is getting the digits to line up: tens digit to tens digit, ones digit to ones digit, etc.) This would indeed solve the sorting problem since the '0' character would come before all the other numeric digits. But, if we are later displaying these string's on-screen, some people might find it odd looking to have those leading 0's there. (...depeneding, of course, on what the numbers represent.)

Another solution, then, would be:

    " 1"
    " 2"
    " 3"
    " 4"
    " 5"
    "10"
    "11"
    "20"
    "25"
    "42"

Here a space (' ') has been pre-pended to each number string that didn't line up with the others in the list. This also solves the problem since the space character preceeds all the numeric digits (it is, in fact, the first of the printable ASCII codes). Plus, when displayed, it doesn't detract from our normal perception of the way numbers should look.

Taking this latter approach, we will need to discover what strings need adjustment and how many spaces it will take to line them up with the others in the list. Let's consider a few possibilities:

    'string'  |  needed spaces
   -----------+-----------------
      1       |    1
      10      |    0

    'string'  |  needed spaces
   -----------+-----------------
      1       |    2
      10      |    1
      100     |    0

Here I've stripped down the list to a representative sample of the problem. Note that all single digit numbers suffer similarly as do all two-digit numbers, etc.

After a bit of thought, it becomes apparent that the ones that need no adjustment are the ones with the most digits (or, since they are stored in strings for us, the ones with the longest length()). The others need additional spaces enough to bring them to the same length() as the longest ones. That shifts their ones digits over to line up together (likewise the tens, hundreds, etc.).

So, it looks like we need to be able to find the string in our list with the longest length... Well, actually, we only need to know the length() of the longest string in the vector -- which string(s) have that length() is technically irrelevant here.

So, given a vector full of strings, we can retrofit our standard maximum finding code from above to find the length of the longest string (i.e. the maximum length):

    // (at top after 'using':)
    typedef string::size_type str_sz;
    typedef vector<string>::size_type vecS_sz;

    // (below main somewhere:)
    str_sz longest(const vector<string> & str, vecS_sz end_before,
                   vecS_sz begin_at)
    {
        str_sz max = string::npos;    // invalid 'position' as max default
        for (vecS_sz s = begin_at; s != end_before; ++s)
        {
            if (str[s].length() > max ¦¦   // string is longer than maximum OR
                max == string::npos)       // we haven't yet assigned a maximum
            {
                max = str[s].length();    // update maximum to string's length
            }
        }
        return max;
    }

There are several things of note here. First we had to make all new typedef'initions for this since these vectors will contain string data instead of double data -- that alone makes their size_types incompatible with one another.

Second we've made the assumption that the maximum length will be the invalid 'position' (for when there are no strings in the vector). This at first sounds silly, but it does flow with the general ideas of string processing. Because of this, our if for seeing if we've found a new maximum needed an ¦¦ to detect when we'd not yet actually assigned a maximum -- when it was still the default. The reasons behind this are slightly mysterious, but essentially, string::npos is an unsigned version of -1 and so is the largest value that string::size_type can hold. Because of this, the > test would never succeed and we'd continually tell the caller that their longest string was this largest unsigned value for string::size_type (either 65535 or 4294967295 depending on if the system set this up as short- or long- sized). We'll discuss an alternative shortly...

Third the call to length() to find out the number of characters in each string (number of digits in each number for us) has some strange syntax: str[s].length(). The 's', of course, is a position in the vector -- not a value. 'str[s]' is the value at the position 's' and therefore is a string. And by simply doing str[s].length(), the compiler uses the .length() call on the string s positions from the beginning of str! (If it makes you feel better, you can parenthesize the subscripting operation: (str[s]).length().

But, what about that string::npos thingy... Well, another way to code it would be this:

    str_sz longest(const vector<string> & str, vecS_sz end_before,
                   vecS_sz begin_at)
    {
        str_sz max = 0;    // all strings are 0 or more long!
        for (vecS_sz s = begin_at; s != end_before; ++s)
        {
            if (str[s].length() > max)  // string is longer than maximum
            {
                max = str[s].length();    // update maximum to string's length
            }
        }
        return end_before-begin_at==0 ? string::npos : max;
    }

Here we still return string::npos when we are given an empty vector, but we avoid much excess processing while looking at an actual vector by removing the ¦¦ from inside the for loop's if. (This would have short-circuited out only when the element was actually the new longest string. Shorter or equal length items would have 'failed into' testing the second half which would have failed after the first loop iteration -- wasting our time significantly!) Now it is just a single test after the loop ends.

Oh, and a prototype (with inline helper):

    // (after the typedef's above:)
    str_sz longest(const vector<string> & vec, vecS_sz end_before,
                   vecS_sz begin_at = 0);

    inline
    str_sz longest(const vector<string> & vec)
    {
        return longest(vec, vec.size());
    }

Now, having found the length of the longest string in the vector, we can now proceed to insert the extra spaces necessary to align our numbers:

    void align(vector<string> & vec, char fill, vecS_sz end_before,
               vecS_sz begin_at)
    {
        str_sz biggest = longest(vec, end_before, begin_at);
        for (vecS_sz s = begin_at; s != end_before; ++s)
        {
            vec[s].insert(0, string(biggest - vec[s].length(), fill));
        }
        return;
    }

Note we've used the insert function of the string class to insert before position 0. What we insert is a string consisting of enough fill characters (we'll be using spaces, remember?) to align this string with the longest string(s). How is this filling string constructed? With the funkiest of the constructors for strings and a syntax for creating anonymous objects.

Recall that you can construct a string in (at least) four ways:

string construction patterns.
Declaration in C++ Program string Contains Description
string s;
"" empty
string t = "literal";
"literal" exactly what you asked for
string u = t;
"literal" an exact copy of the other string
string w(4, '*');
"****" four star's as requested

That last one is the one we're using here. It creates a string that is a certain number of repetitions of a certain character we specify. The number of characters we want is the difference between the longest string and our length (biggest - str[s].length()). And the character we want to repeat is the fill character (spaces for us).

But, we aren't creating a variable! We could have:

        for (vecS_sz s = begin_at; s != end_before; ++s)
        {
            string t(biggest - str[s].length(), fill);
            str[s].insert(0, t);
        }

But this would've proven wasteful and is considered clumsy and in-elegant. Instead, we've just taken out the name of the variable and used what's left as a way to construct an anonymous string -- no name, no memory (well, some...somewhere -- but definitely better than the 'temporary' above).

This 'anonymous construction' syntax can actually be used for any of the construction techniques:

Comparison of variable declarations to anonymous object constructions for strings.
Variable Declaration Anonymous string
string s; string()
string t = "literal"; string("literal")
string u = t; string(t)
string w(4, '*'); string(4, '*')

Pretty much just add parentheses to those without (in place of the assignment if need be). In fact, you may recall that this is an alternate syntax for variable declaration too (except for the default case):

Comparison of declarations of variables initializing with = to initializing with () syntax.
Variable Declaration with = Variable Declaration with ()
string s; string s;
string t = "literal"; string t("literal");
string u = t; string u(t);
string w(4, '*'); string w(4, '*');

But all of that was covered in examples elsewhere...

Anyway, we still need an appropriate prototype (and helper):

    void align(vector<string> & vec, char fill, vecS_sz end_before,
               vecS_sz begin_at = 0);

    inline
    void align(vector<string> & vec, char fill)
    {
        align(vec, fill, vec.size());
        return;
    }

For More

Don't forget to visit the vector examples area for other interesting examples of using vectors. One of my favorites is the phrase framer.


NOTE: More to come..?