Accelerated C++ Solution to Exercise 5-0

Exercise 5-0

Compile, execute, and test the programs in this chapter.

Solution

Chapter 5 contains 3 core sets of applications. I believe it makes sense to split Exercise 5-0 into 3 separate parts (i.e. 3 separate posts) for greater relevance and readability. Click the hyperlinks below to drill down.

Part 1 / 3

Click here to see full Solution to Exercise 5-0 (Part 1 / 3) : We extend the Solution to Exercise 4-0 (i.e. the student grading program) to include a capability that split the set of students (objects) into two groups – namely the passing students and failing students. This capability is enabled by a new “home-made” function called extract_fails. This function can be written in 4 different versions in which all 4 versions deliver the same result, with the slight difference in terms of efficiency. It goes from version 1 being the most primitive / inefficient, to version 4 being the most elegant / efficient. We will describe and test out each version one-by-one.

Part 2 / 3

Click here to see full Solution to Exercise 5-0 (Part 2 / 3): This is a very simple program with the purpose of splitting a line of text (i.e. a input string) into words (i.e. an output vector<string>).

Part 3 / 3

Click here to see full Solution to Exercise 5-0 (Part 3 / 3): This is a fairly clever yet simple-to-understand program. This exercise demonstrates 4 things: (1) how to create a paragraph and store in the form of a vector<string> container, (2) how to display the paragraph with and without a frame, (3) how to concatenate two paragraph “pictures” vertically, (4) how to concatenate two paragraph “pictures” horizontally.

Remark

If in doubt, try reading through chapter 5 multiple times to help understanding how the “bits and pieces” work individually and as a whole. It’s all in the book. What the book does not contain however, is the actual implementation of programs through an IDE (like Code::Block) and demonstrate what the the output results may look like. This post (and all the other posts in this Accelerated C++ series) aims to fill in that gap.

Reference

Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000

Accelerated C++ Solution to Exercise 5-0 (Part 3 / 3)

This is Part 3 of the 3-part  Solution to Exercise 5-0.

Exercise 5-0 (Part 3 / 3)

This is a fairly clever yet simple-to-understand program. This exercise demonstrates 4 things: (1) how to create a paragraph and store in the form of a vector<string> container, (2) how to display the paragraph with and without a frame, (3) how to concatenate two paragraph “pictures” vertically, (4) how to concatenate two paragraph “pictures” horizontally.

The Program Logic

  1. User to create a paragraph by providing multiple lines of input, via the console window.
  2. The program store the paragraph in the form of a vector<string> container called p – each string element corresponds the an input line. e.g. p[0] is the first line, p[1] is the second line, etc.
  3. A function called vcout may be used to display a vector<string> container in the form of a paragraph via the console window.
  4. A function called frame may be used to take in a vector<string> container and return a new vector<string> container representing a “framed” paragraph.
  5. A function called vcat may be used to concatenate multiple paragraph “box-like pictures” vertically. e.g. imagine two box-like paragraph pictures are stacked vertically.
  6. A function called hcat may be used to concatenate multiple paragraph “box-like pictures” horizontally. e.g. imagine two box-like paragraph pictures are aligned horizontally.
  7. An sub-function called width (used by the frame and hcat functions) is in place to ensure the paragraphs behave in a “box-like” shape when we add a frame around it, or concatenate horizontally.
  8. Within the main program, we apply the functions vcout, frame, vcat, and hcat in multiple combinations to demonstrate it is very easy to display the vector<string> p in the form of “box-like pictures” and in multiple ways. e.g. apply a frame around a paragraph, stack the two box-like picture paragraphs vertically / horizontally.

The Project

This is what the management tree looks like in Code::Block:

Acpp5p0p3MgntTree

C++ Source Files

  • main.cpp – this is the first program that is run during the implementation phase.
  • frame.cpp – contains the frame and width functions.
  • hcat.cpp – contains the hcat function.
  • vcat.cpp – contains the vcat function.
  • vcout.cpp – contains the vcout function.

C++ Header Files

  • frame.h – declare the functions in frame.cpp.
  • hcat.h – declare the functions in hcat.cpp.
  • vcat.h – declare the functions in vcat.cpp.
  • vcout.h – declare the functions in vcout.cpp.

Source Files

main.cpp

#include <iostream>  // cin, cout, endl, getline
#include <vector>    // vector
#include <string>    // string
#include "frame.h"   // width, frame
#include "vcat.h"    // vcat
#include "hcat.h"    // hcat
#include "vcout.h"   // vcout

using std::cin;
using std::cout;
using std::endl;
using std::getline;
using std::string;
using std::vector;

int main()
{
    string s;            // line
    vector<string> p;    // paragraph

    // read multiple lines to make a paragraph
    while (getline(cin, s))
        p.push_back(s);

    // have a play, manipulate and display paragraph (p) in multiple ways

    cout << "----------------------------------------------------\n"
            "Display: p                                          \n"
            "----------------------------------------------------\n";
    vcout(p);

    cout << "-----------------------------------------------------\n"
            "Display: frame(p)                                    \n"
            "-----------------------------------------------------\n";
    vcout(frame(p));

    cout << "-----------------------------------------------------\n"
            "Display: vcat(p, frame(p))                           \n"
            "-----------------------------------------------------\n";
    vcout(vcat(p,frame(p)));

    cout << "-----------------------------------------------------\n"
            "Display: vcat(frame(p), p)                           \n"
            "-----------------------------------------------------\n";
    vcout(vcat(frame(p),p));

    cout << "-----------------------------------------------------\n"
            "Display: hcat(p, frame(p))                           \n"
            "-----------------------------------------------------\n";

    vcout(hcat(p,frame(p)));

    cout << "-----------------------------------------------------\n"
            "Display: hcat(frame(p), p)                           \n"
            "-----------------------------------------------------\n";
    vcout(hcat(frame(p),p));

    cout << "-----------------------------------------------------\n"
            "Display: vcat(hcat(frame(p), p), hcat(p, frame(p)))  \n"
            "-----------------------------------------------------\n";
    vcout(vcat(hcat(frame(p), p), hcat(p, frame(p))));

    return 0;
}

frame.cpp

#include <string>      // string
#include <vector>      // vector
#include <algorithm>   // max

using std::string;
using std::vector;
using std::max;

string::size_type width(const vector<string>& v)
{
    string::size_type maxlen = 0;
    for(vector<string>::size_type i = 0; i != v.size(); ++i)
        maxlen = max(maxlen, v[i].size());
    return maxlen;
}

vector<string> frame(const vector<string>& v)
{
    vector<string> ret;
    string::size_type maxlen = width(v);
    string border(maxlen + 4, '*');

    // write the top border
    ret.push_back(border);

    // write each interior row, bordered by an asterisk and a space
    for (vector<string>::size_type i = 0; i != v.size(); ++i)
        ret.push_back("* " + v[i] + string(maxlen - v[i].size(), ' ') + " *");

    // write the bottom border
    ret.push_back(border);

    return ret;
}

hcat.cpp

#include <string>      // string
#include <vector>      // vector
#include "frame.h"     // width

using std::string;
using std::vector;

vector<string> hcat(const vector<string>& left, const vector<string>& right)
{
    vector<string> ret;

    // add 1 to leave a space between pictures
    string::size_type width1 = width(left) + 1;

    // indices to look at elements from left and right respectively
    vector<string>::size_type i = 0, j = 0;

    // continue until we've seen all rows from both pictures
    while (i != left.size() || j != right.size())
    {
        // construct new string to hold characters from both pictures
        string s;

        // copy a row from the left-hand side, if there is one
        if (i != left.size())
            s = left[i++];

        // pad to full width
        s += string(width1 - s.size(), ' ');

        // copy a row from teh right-hand side, if there is one
        if (j != right.size())
            s += right[j++];

        // add s to the picture we are creating
        ret.push_back(s);
    }

    return ret;
}

vcat.cpp

#include <string>      // string
#include <vector>      // vector

using std::string;
using std::vector;

vector<string> vcat(const vector<string>& top, const vector<string>& bottom)
{
    // copy the top picture
    vector<string> ret = top;

    // copy entire bottom picture (option 1 and option 2 have the same effect - pick whichever!)

    // option 1
    for (vector<string>::const_iterator it = bottom.begin();
         it != bottom.end(); ++it)
        ret.push_back(*it);

    // option 2
    /*
    ret.insert(ret.end(), bottom.begin(), bottom.end());
    */

    return ret;
}

vcout.cpp

#include <iostream>
#include <string>    // string
#include <vector>    // vector

using std::cout;
using std::endl;
using std::string;
using std::vector;

int vcout(const vector<string>& v)
{
    for (vector<string>::const_iterator iter = v.begin();
         iter != v.end(); ++iter)
    {
        cout << (*iter) << endl;
    }
    return 0;
}

Header Files

frame.h

#ifndef GUARD_FRAME_H
#define GUARD_FRAME_H

#include <string>
#include <vector>

std::string::size_type width(const std::vector<std::string>&);
std::vector<std::string> frame(const std::vector<std::string>&);

#endif // GUARD_FRAME_H

hcat.h

#ifndef GUARD_HCAT_H
#define GUARD_HCAT_H

#include <string>
#include <vector>

std::vector<std::string> hcat(const std::vector<std::string>&, const std::vector<std::string>&);

#endif // GUARD_HCAT_H

vcat.h

#ifndef GUARD_VCAT_H
#define GUARD_VCAT_H

#include <string>
#include <vector>

std::vector<std::string> vcat(const std::vector<std::string>&, const std::vector<std::string>&);

#endif // GUARD_VCAT_H

vcout.h

#ifndef GUARD_VCOUT_H
#define GUARD_VCOUT_H

#include <string>
#include <vector>

int vcout(const std::vector<std::string>&);

#endif // GUARD_VCOUT_H

Test Program

I have written the main program in a way that is easy enough to test out multiple combination of function usage (i.e. frame, vcat, and hcat) against the vector<string> p (the paragraph).

In this test, as soon as the program starts up, I provide the following 5 lines (followed by the end-of-file button. i.e. F6 for Windows).

  1. this is an
  2. example
  3. to
  4. illustrate
  5. framing

Test Result

this is an
example
to
illustrate
framing
^Z
----------------------------------------------------
Display: p
----------------------------------------------------
this is an
example
to
illustrate
framing
-----------------------------------------------------
Display: frame(p)
-----------------------------------------------------
**************
* this is an *
* example    *
* to         *
* illustrate *
* framing    *
**************
-----------------------------------------------------
Display: vcat(p, frame(p))
-----------------------------------------------------
this is an
example
to
illustrate
framing
**************
* this is an *
* example    *
* to         *
* illustrate *
* framing    *
**************
-----------------------------------------------------
Display: vcat(frame(p), p)
-----------------------------------------------------
**************
* this is an *
* example    *
* to         *
* illustrate *
* framing    *
**************
this is an
example
to
illustrate
framing
-----------------------------------------------------
Display: hcat(p, frame(p))
-----------------------------------------------------
this is an **************
example    * this is an *
to         * example    *
illustrate * to         *
framing    * illustrate *
           * framing    *
           **************
-----------------------------------------------------
Display: hcat(frame(p), p)
-----------------------------------------------------
************** this is an
* this is an * example
* example    * to
* to         * illustrate
* illustrate * framing
* framing    *
**************
-----------------------------------------------------
Display: vcat(hcat(frame(p), p), hcat(p, frame(p)))
-----------------------------------------------------
************** this is an
* this is an * example
* example    * to
* to         * illustrate
* illustrate * framing
* framing    *
**************
this is an **************
example    * this is an *
to         * example    *
illustrate * to         *
framing    * illustrate *
           * framing    *
           **************

This is overall a very nice program that demonstrates the ease of manipulating a vector as long as the functions are well written and in place.

Reference

Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000

Accelerated C++ Solution to Exercise 5-0 (Part 2 / 3)

This is Part 2 of the 3-part Solution to Exercise 5-0.

Exercise 5-0 (Part 2 / 3)

This is a very simple program with the purpose of splitting a line of text (i.e. a input string) into words (i.e. an output vector<string>).

The Problem

Start from scratch, we need to write a program that is able to read in a line of text, and be able to split it into words. The full description and explanation of the program can be found in Chapter 5 of the book.

Solution Strategy

The text book sample program for this (part 2) exercise is fairly simple. It only requires 3 files which I can group into a small project as usual – I will describe this in the Project section below.

There is also one core learning from this chapter regarding sequential container processing using either (1) index, or (2) iterator. Both methods are effectively the same. e.g. assuming v is a vector<string>, the two following codes have the same effect (of displaying container elements to the output console).

Sequential Container Processing with Index:

        for (vector<string>::size_type i = 0; i != v.size(); ++i)
            cout << v[i] << endl;

Sequential Container Processing with Iterator:

        for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
            cout << (*iter) << endl;

Remark

As the Author mentioned in the book, however:

  • Index is good for random container processing, whereas
  • Iterator is good for sequential container processing.

It is always a good idea to bear this in mind.

The Project

This is what the management tree looks like in Code::Block:

Acpp5p0p2MgntTree

C++ Source Files

  • main.cpp – this is the first program that is run during the implementation phase.
  • split.cpp – contains the split function.

C++ Header Files

  • split.h – declare the functions in the source file split.cpp.

Source Files

main.cpp

#include <iostream>  // cin, cout, endl, getline
#include <vector>    // vector
#include <string>    // string
#include "split.h"   // split

using std::vector;
using std::string;
using std::cin;
using std::cout;
using std::endl;
using std::getline;

// (5.7/90)
int main()
{
    string s;

    // read and split each line of input
    while (getline(cin, s))
    {
        vector<string> v = split(s);

        // write each word in v
        for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
            cout << (*iter) << endl;
    }
    return 0;
}

split.cpp

#include <string>    // string
#include <vector>    // vector
#include <cctype>    // isspace

using std::vector;
using std::string;
using std::isspace;

// scan a string of texts, split into words, return a vector that contains these words.
// (S5.6/88)
vector<string> split(const string& s)
{
    vector<string> ret;
    typedef string::size_type string_size;
    string_size i = 0;

    // invariant: we have processed characters original value of [i, i)
    while (i != s.size() )
    {
        // ignore leading blanks
        // invariant: characters in range [original i, current i)
        while (i != s.size() && isspace(s[i]))
            ++i;

        // find end of next word
        string_size j = i;

        // invariant: none of the characters in range [original j, current j) is a space
        while (j != s.size() && !isspace(s[j]))
            ++j;

        // if we found some non-whitespace characters
        if (i != j)
            // copy from s starting at i and taking j - i chars
            ret.push_back(s.substr(i, j - i));
            i = j;
    }
    return ret;
}

Header Files

split.h

#ifndef GUARD_SPLIT_H
#define GUARD_SPLIT_H

#include <vector>
#include <string>

std::vector<std::string> split(const std::string&);

#endif // GUARD_SPLIT_H

Test Results

I am going to enter the following 3 lines when the program starts up. I should see the individual words (separated by empty space) displayed on the screen straight after I hit the enter button.

  1. Hello world, how are we today?
  2. Nice, so the program works!
  3. Bye bye
Hello world, how are we today?
Hello
world,
how
are
we
today?
Nice, so the program works!
Nice,
so
the
program
works!
Bye bye
Bye
bye
^Z

The program works as expected!

Reference

Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000

Accelerated C++ Solution to Exercise 5-0 (Part 1 / 3)

This is Part 1 of the 3-part Solution to Exercise 5-0.

Exercise 5-0 (Part 1 / 3)

We extend the Solution to Exercise 4-0 (i.e. the student grading program) to include a capability that split the set of students (objects) into two groups – namely the passing students and failing students. This capability is enabled by a new “home-made” function called extract_fails. This function can be written in 4 different versions in which all 4 versions deliver the same result, with the slight difference in terms of efficiency. It goes from version 1 being the most primitive / inefficient, to version 4 being the most elegant / efficient. We will describe and test out each version one-by-one.

The Problem

The objective of this exercise is to enhance the Chapter 4 program so that we are able to determine, out of the entire Student_info object population, which ones belong to the passing group, and which ones belong to the failing group.

The Solution Strategy

There are many good ways to implement this Chapter 5 example program. Here is my strategy:

  1. Make a copy of the entire Chapter 4 project. i.e. the Project as per Solution to Exercise 4-0.
  2. Add a C++ source file extract_fails.cpp. It contains the 4 versions of the extract_fail function. To make it easy to test later, I simply create 4 functions. Namely: extract_fails_v1, extract_fails_v2, extract_fails_v3, extract_fails_v4. The function fgrade is also stored in this file.
  3. Add a corresponding C++ header file. i.e. extract_fails.h.
  4. Adjust the main function to make it suitable for testing. Note that extract_fail version 1, 2 and 3 use vector<Student_info> container, whereas version 4 uses list<Student_info> container. Because of this, I will have two versions of main program for the testing – i.e. no1_main() for testing out extract_fail version 1, 2, 3. no2_main() for testing out extract_fail version 4.

The Project

I partition the program into smaller chunks – i.e. in C++ source files and header files. This diagram below shows what the Code::Block Management Tree look like after successful creation of these files.

Acpp5p0p1MgntTree

Note that majority of these files are straight copies of the Chapter 4 Program. (i.e. Solution to Exercise 4-0). (the ones that are NOT circled in red)

In this exercise we add these files (circled in red as per snapshot diagram above): extract_fails.cpp, extract_fails.h. We also amend the main.cpp accordingly so that we are able to test out the newly written functions using tailored main program.

C++ Source Files

  • main.cpp – (Adjust accordingly) this is the first program that is run during the implementation phase.
  • extract_fails.cpp – (New!) contains the fgrade function, and the 4 versions extract_fail functions.
  • grade.cpp – (From Chapter 4) contains all functions relating to computing grades.
  • median.cpp – (From Chapter 4) contains all functions relating to computing median.
  • Student_info.cpp – (From Chapter 4) contains all functions relating to handling a Student_info object.

C++ Header Files

  • extract_fails.h – (New!) declare the functions as defined in extract_fails.cpp.
  • grade.h – (From Chapter 4) declare the functions as defined in grade.cpp.
  • median.h – (From Chapter 4) declare the functions as defined in median.cpp
  • Student_info.h – (From Chapter 4) declare the functions as defined in Student_info.cpp, plus defining the data structure of the Student_info (object) type.

Source Files

main.cpp (Adjust accordingly)

#include <algorithm>
#include <iomanip>
#include <ios>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include <list>
#include "grade.h"
#include "Student_info.h"
#include "extract_fails.h"

using std::cin;
using std::cout;
using std::endl;
using std::domain_error;
using std::max;
using std::setprecision;
using std::sort;
using std::streamsize;
using std::string;
using std::vector;
using std::list;

// There are two main programs here.
//   - The top one is used to test out extract_fails version 1, 2, and 3.
//   - The bottom one is for testing out extract_fails version 4.
// Simply replace the no1_main() or no2_main() with main() to activate the main program to run

// for testing extract_fails_v1 / v2 / v3 - that uses vector
int no1_main()
{
    vector<Student_info> students;
    Student_info record;

    // read and store all the student's data.
    while (read(cin, record))
        students.push_back(record);

    // Extract the failed students (use extract_fails_v1 / v2 / v3)
    vector<Student_info> students_failed = extract_fails_v3(students);

    // sort vectors
    sort(students.begin(),students.end(),compare);
    sort(students_failed.begin(),students_failed.end(),compare);

    // Report passing students
    cout << "These students have passed." << endl;
    for (vector<Student_info>::const_iterator i = students.begin();
         i != students.end(); ++i)
        cout << i->name << endl;

    // Report failing students
    cout << "These students have failed." << endl;
    for (vector<Student_info>::const_iterator i = students_failed.begin();
         i != students_failed.end(); ++i)
        cout << i->name << endl;

    return 0;
}


// for testing extract_fails_v4 - that uses list
int no2_main()
{
    list<Student_info> students;
    Student_info record;

    // read and store all the student's data.
    while (read(cin, record))
        students.push_back(record);

    // Extract the failed students
    list<Student_info> students_failed = extract_fails_v4(students);

    // sort lists
    students.sort(compare);
    students_failed.sort(compare);

    // Report passing students
    cout << "These students have passed." << endl;
    for (list<Student_info>::const_iterator i = students.begin();
         i != students.end(); ++i)
        cout << i->name << endl;

    // Report failing students
    cout << "These students have failed." << endl;
    for (list<Student_info>::const_iterator i = students_failed.begin();
         i != students_failed.end(); ++i)
        cout << i->name << endl;

    return 0;
}

extract_fails.cpp

#include "Student_info.h"
#include "grade.h"
#include <vector>
#include <list>

using std::vector;
using std::list;


// predicate to determine whether a student failed
// (S5.1/75)
bool fgrade(const Student_info& s)
{
    return grade(s) < 60;
}

// separate passing and failing student records
// version 1: maintain two vectors (pass and fail) within local scope
// Can use up lots of memory resource.
// (S5.1/76)
vector<Student_info> extract_fails_v1(vector<Student_info>& students)
{
    vector<Student_info> pass, fail;

    for (vector<Student_info>::size_type i = 0;
         i != students.size(); ++i)
         if (fgrade(students[i]))
            fail.push_back(students[i]);
         else
            pass.push_back(students[i]);

    students = pass;
    return fail;
}

// separate passing and failing student records
// version 2: maintain one vector (fail) within local scope. Update pass vector by reference.
// correct but potentially slow. (Still use up lots of memory resource but less than version 1)
// (S5.1.1/77)
vector<Student_info> extract_fails_v2(vector<Student_info>& students)
{
    vector<Student_info> fail;
    vector<Student_info>::size_type i = 0;

    // invariant: elements [0,i) of students represent passing grades
    while (i != students.size())
    {
        if (fgrade(students[i]))
        {
            fail.push_back(students[i]);
            students.erase(students.begin() + i);
        }
        else
            ++ i;
    }
    return fail;
}

// separate passing and failing student records
// version 3: iterators but no indexing; still potentially slow
// (S5.3/82)
vector<Student_info> extract_fails_v3(vector<Student_info>& students)
{
    vector<Student_info> fail;
    vector<Student_info>::iterator iter = students.begin();
    while (iter != students.end())
    {
        if (fgrade(*iter))
        {
            fail.push_back(*iter);
            iter = students.erase(iter);
        }
        else
            ++iter;
    }
    return fail;
}

// separate passing and failing student records
// version 4: use list instead of vector (essentially the same as version 3 otherwise)
// (S5.5/85)
list<Student_info> extract_fails_v4(list<Student_info>& students)
{
    list<Student_info> fail;
    list<Student_info>::iterator iter = students.begin();
    while (iter != students.end())
    {
        if (fgrade(*iter))
        {
            fail.push_back(*iter);
            iter = students.erase(iter);
        }
        else
            ++iter;
    }
    return fail;
}

grade.cpp

#include <stdexcept>
#include <vector>
#include "grade.h"
#include "median.h"
#include "Student_info.h"

using std::domain_error;
using std::vector;

// definitions for the grade functions from S4.1/52, S4.1.2/54, S4.2.2/63

// compute a student's overall grade from midterm and final exam
// grades and homework grade (S4.1/52)
double grade(double midterm, double final, double homework)
{
    return 0.2 * midterm + 0.4 * final + 0.4 * homework;
}

// compute a student's overall grade from midterm and final exam grades
// and vector of homework grades.
// this function does not copy its argument, because median (function) does it for us.
// (S4.1.2/54)
double grade(double midterm, double final, const vector<double>& hw)
{
    if (hw.size() == 0)
        throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
}

// this function computes the final grade for a Student_info object
// (S4.2.2/63)
double grade(const Student_info& s)
{
    return grade(s.midterm, s.final, s.homework);
}

median.cpp

// source file for the median function
#include <algorithm>
#include <stdexcept>
#include <vector>

using std::domain_error;
using std::sort;
using std::vector;

// compute the median of a vector<double>
double median(vector<double> vec)
{
    typedef vector<double>::size_type vec_sz;

    vec_sz size = vec.size();
    if (size == 0)
        throw domain_error("median of an empty vector");

    sort(vec.begin(),vec.end());

    vec_sz mid = size/2;

    return size % 2 == 0 ? (vec[mid] + vec[mid-1]) / 2 : vec[mid];
}

Student_info.cpp

#include "Student_info.h"

using std::istream;
using std::vector;

// we are interested in sorting the Student_info object by the student's name
bool compare(const Student_info& x, const Student_info& y)
{
    return x.name < y.name;
}

// read student's name, midterm exam grade, final exam grade, and homework grades
// and store into the Student_info object
// (as defined in S4.2.2/62)
istream& read(istream& is, Student_info& s)
{
    // read and store the student's name and midterm and final exam grades
    is >> s.name >> s.midterm >> s.final;

    // read and store all the student's homework grades
    read_hw(is, s.homework);
    return is;
}

// read homework grades from an input stream into a vector<double>
// (as defined in S4.1.3/57)
istream& read_hw(istream& in, vector<double>& hw)
{
    if (in)
    {
        // get rid of previous contents
        hw.clear();

        // read homework grades
        double x;
        while (in >> x)
            hw.push_back(x);

        // clear the stream so that input will work for the next student
        in.clear();
    }
    return in;
}

Header Files

extract_fails.h

#ifndef GUARD_EXTRACT_FAILS_H
#define GUARD_EXTRACT_FAILS_H

// extract_fails.h
#include <vector>
#include <list>
#include "Student_info.h"

bool fgrade(const Student_info&);
std::vector<Student_info> extract_fails_v1(std::vector<Student_info>&);
std::vector<Student_info> extract_fails_v2(std::vector<Student_info>&);
std::vector<Student_info> extract_fails_v3(std::vector<Student_info>&);
std::list<Student_info> extract_fails_v4(std::list<Student_info>&);

#endif // GUARD_EXTRACT_FAILS_H

grade.h

#ifndef GUARD_GRADE_H
#define GUARD_GRADE_H

//grade.h
#include <vector>
#include "Student_info.h"

double grade(double, double, double);
double grade(double, double, const std::vector<double>&);
double grade(const Student_info&);

#endif // GUARD_GRADE_H

median.h

#ifndef GUARD_MEDIAN_H
#define GUARD_MEDIAN_H

// median.h - final version
#include <vector>
double median(std::vector<double>);

#endif // GUARD_MEDIAN_H

Student_info.h

#ifndef GUARD_STUDENT_INFO_H
#define GUARD_STUDENT_INFO_H

// Student_info.h
#include <iostream>
#include <string>
#include <vector>

struct Student_info
{
    std::string name;
    double midterm, final;
    std::vector<double> homework;
};

bool compare(const Student_info&, const Student_info&);
std::istream& read(std::istream&, Student_info&);
std::istream& read_hw(std::istream&, std::vector<double>&);

#endif // GUARD_STUDENT_INFO_H

Test Program

We need to do four tests to test out the extract_fails function version 1, 2, 3, and 4 accordingly.

As seen in the main.cpp, I have made a note on how to do this:

// There are two main programs here.
//   - The top one is used to test out extract_fails version 1, 2, and 3.
//   - The bottom one is for testing out extract_fails version 4.
// Simply replace the no1_main() or no2_main() with main() to activate the main program to run

For clarity purpose I will list out exactly how to change the main.cpp accordingly for this program testing.

To test extract_fails version 1:

  1. Inside no1_main(), replace the extract_fails_XX to extract_fails_v1.
  2. Replace no1_main() with main().

To test extract_fails version 2:

  1. Inside no1_main(), replace the extract_fails_XX to extract_fails_v2.
  2. Replace no1_main() with main().

To test extract_fails version 3:

  1. Inside no1_main(), replace the extract_fails_XX to extract_fails_v3.
  2. Replace no1_main() with main().

To test extract_fails version 4:

  1. Inside no2_main(), replace the extract_fails_XX to extract_fails_v4.
  2. Replace no2_main() with main().

Notes

Note again, no1_main() focuses on vector<string>, whereas no2_main() focuses on list<string>.

I have also taken the liberty to test out the sort function (highlighted in S5.1.1/86 of the text book). i.e.

To sort a vector, do:

    // sort vectors
    sort(students.begin(),students.end(),compare);
    sort(students_failed.begin(),students_failed.end(),compare);

To sort a list, do:

    // sort lists
    students.sort(compare);
    students_failed.sort(compare);

Results

I have run through all 4 versions above and confirm that all give the same result. Some snapshots captured below.

Recall that the finalGrade = ( 20% x midTermGrade ) + (40% x finalTermGrade) + (40% x medianOfHomeworkGrades)

To simplify the test, I know that, if I provide the same value for midTermGrade, finalTermGrade, and homeworkGrades, then the finalGrade should technically be the same value. e.g. if all grades are 90, then the finalGrade is bound to be 90 also. The idea is that, I know the passing grade is 60 or above (and likewise, failing grade is less than 60). By providing the grades for a list of students, I can easily determine (using my head) whether the student should pass or fail. I can then compare with the test result computed by the program. If the program is working as expected, both set of test results should match. (And they indeed do).

Test 1

Aim of this test is to prove that the program is able to split the group of students into two groups – the passing and failing groups.

aaa 90 90 90 90 90
bbb 80 80 80 80 80
ccc 70 70 70 70 70
ddd 60 60 60 60 60
eee 50 50 50 50 50
fff 40 40 40 40 40
ggg 30 30 30 30 30
hhh 10 10 10 10 10
iii 0 0 0 0 0
^Z
^Z
These students have passed.
aaa
bbb
ccc
ddd
These students have failed.
eee
fff
ggg
hhh
iii

Test 2

Aim of this test is essentially the same as test 1, plus the ability to sort.

fff 40 40 40 40 40
bbb 80 80 80 80 80
ccc 70 70 70 70 70
iii 0 0 0 0 0
eee 50 50 50 50 50
aaa 90 90 90 90 90
hhh 10 10 10 10 10
ddd 60 60 60 60 60
ggg 30 30 30 30 30
^Z
^Z
These students have passed.
aaa
bbb
ccc
ddd
These students have failed.
eee
fff
ggg
hhh
iii

Test 3

Aim of this test is to show that the program runs okay when all students have passing grades.

aaa 90 90 90 90 90
bbb 80 80 80 80 80
ccc 70 70 70 70 70
ddd 60 60 60 60 60
^Z
^Z
These students have passed.
aaa
bbb
ccc
ddd
These students have failed.

Test 4

Aim of this test is to show that the program runs okay when all students have failing grades.

eee 50 50 50 50 50
fff 40 40 40 40 40
ggg 30 30 30 30 30
hhh 10 10 10 10 10
iii 0 0 0 0 0
^Z
^Z
These students have passed.
These students have failed.
eee
fff
ggg
hhh
iii

Test 5

Aim of this test is to show that the program runs okay even when we provide no input data at all.

^Z
These students have passed.
These students have failed.

Conclusion

  1. We have successfully put all the files and sub-programs together, implemented the algorithm in an IDE like Code::Block, in a “partitioned” manner (as suggested by the Author at S4.3/65).
  2. We have tested all 4 versions of the extract_fail function. Though I have not proved it here, we know that (according to the Author) that version 1 is least efficient and version 4 is most efficient (2 and 3 are somewhat in the middle).
  3. We have learnt abut iterator, vector and list. Our confident level with using C++, have gone up another notch.

Reference

Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000