Accelerated C++ Solution to Exercise 6-8

Exercise 6-8

Write a single function that can be used to classify students based on criteria of your choice. Test this function by using it in place of the extract_fails program, and use it in the program to analyse student grades.

Some Background

In the Solution to Exercise 6-7 we created a new extractDidnt function by making a copy of the original “one-pass” extract_fails function, followed by making minor adjustments (such as replacing the predicate pgrade to did_all_hw). The downside of this approach (i.e. making a copy of a skeleton code and adjusting it) is that we may end up with many near-duplicated codes (with only minor differences). Having to maintain so many codes may become difficult to manage in long run. The author asks us to create a single (more generic) function that is capable to deal with multiple similar scenarios – so at the end of the day we only need to maintain 1 single piece of code, instead of (say) a hundred.

Solution

If we look at the extract_fail and extractDidnt codes that we wrote in Solution to Exercise 6-7, we note that the one most important difference is third argument called by the stable_partition function – pgrade vs did_all_hw (which are both predicates). Both of these are essentially some sort of criteria that return true or false.

The new extractOnCriteria function

Building on the skeleton extract_fail function, this is my version of that “one single (more generic)” function:

#include <vector>             // std::vector
#include <algorithm>          // std::remove_copy_if
#include "Student_info.h"     // Student_info

using std::vector;

// a more generic version of extract_fail code (originated (S6.3.2/119))
vector<Student_info> extractOnCriteria(vector<Student_info>& students,
                                       bool criteria(const Student_info&))
{
  vector<Student_info>::iterator iter =
      stable_partition(students.begin(), students.end(), criteria);

  vector<Student_info> extracted(students.begin(), iter);  // changed
  students.erase(students.begin(), iter);                  // changed

  return extracted;
}

This diagram summarises what this implementation does:

Acpp6p8Pic1

Note these adjustments:

  • The third argument (a predicate) is now renamed to something more generic – criteria.
  • Added a second parameter bool criteria(const Student_info&) to enable us to parse the criteria into the implementation. For example, when we call the extractOncriteria from the main program, we can parse either the pgrade or did_all_hw via the second argument.
  • The two lines above the return statement – note that the range is now from students.begin() to iter – instead of iter to students.end() as per the extract_fail function. (Why you may ask? Well, instead of trying to convince you, I would suggest you to just simply compare the above diagram with the ones in Solution to Exercise 6-7 – things will become clear then.)

The Project

Just to wrap things up and enable ease of testing, I now summarise the C++ source and header files here.

Acpp6p8MgntTree

Source File List

Header File List

Source Files

main.cpp

#include <iostream>                      // cin, cout, endl
#include <vector>                        // vector

#include "Student_info.h"                // Student_info

#include "extractOnCriteria.h"           // extractOnCriteria

#include "did_all_hw.h"                  // did_all_hw
#include "fgrade.h"                      // fgrade

#include "grade.h"                       // grade

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

int main()
{
  // read the student records
  Student_info student;
  vector<Student_info> students;
  while (read(cin, student))
    students.push_back(student);

  // identify students who did and didnt do all homework
  vector<Student_info> didnt = students;
  vector<Student_info> did = extractOnCriteria(didnt, did_all_hw);

  cout << "nThese students did all homework:" << endl;
  for (vector<Student_info>::const_iterator i = did.begin();
       i != did.end(); ++i)
    cout << (i->name) << endl;

  cout << "nThese students didn't do all homework:" << endl;
  for (vector<Student_info>::const_iterator i = didnt.begin();
       i != didnt.end(); ++i)
    cout << (i->name) << endl;


  // identify students who have passed or failed
  vector<Student_info> passed = students;
  vector<Student_info> failed = extractOnCriteria(passed, fgrade);

  cout << "nThese students have passed:" << endl;
  for (vector<Student_info>::const_iterator i = passed.begin();
       i != passed.end(); ++i)
    cout << (i->name) << " (" << grade(*i) << ")" << endl;

  cout << "nThese students have failed:" << endl;
  for (vector<Student_info>::const_iterator i = failed.begin();
       i != failed.end(); ++i)
    cout << (i->name) << " (" << grade(*i) << ")" << endl;

  return 0;
}

did_all_hw.cpp

#include <algorithm>           // find
#include "Student_info.h"      // Student_info

// Has the student done all the homework?
// (S6.2.1/110)
bool did_all_hw(const Student_info& s)
{
  return ((find(s.homework.begin(), s.homework.end(), 0)) == s.homework.end());
}

extractOnCriteria.cpp

#include <vector>             // std::vector
#include <algorithm>          // std::remove_copy_if
#include "Student_info.h"     // Student_info

using std::vector;

// a more generic version of extract_fail code (originated (S6.3.2/119))
vector<Student_info> extractOnCriteria(vector<Student_info>& students,
                                       bool criteria(const Student_info&))
{
  vector<Student_info>::iterator iter =
      stable_partition(students.begin(), students.end(), criteria);

  vector<Student_info> extracted(students.begin(), iter);  // changed
  students.erase(students.begin(), iter);                  // changed

  return extracted;
}

fgrade.cpp

#include "Student_info.h"  // Student_info
#include "grade.h"         // grade

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

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

did_all_hw.h

#ifndef GUARD_DID_ALL_HW_H
#define GUARD_DID_ALL_HW_H

// did_all_hw.h
#include "Student_info.h"  // Student_info

bool did_all_hw(const Student_info&);

#endif // GUARD_DID_ALL_HW_H

extractOnCriteria.h

#ifndef GUARD_EXTRACTONCRITERIA_H
#define GUARD_EXTRACTONCRITERIA_H

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

std::vector<Student_info>
extractOnCriteria(std::vector<Student_info>&,
                  bool criteria(const Student_info&));

#endif // GUARD_EXTRACTONCRITERIA_H

fgrade.h

#define GUARD_FGRADE_H

// fgrade.h

#include "Student_info.h"  // Student_info
#include "grade.h"         // grade

bool fgrade(const Student_info&);

#endif // GUARD_FGRADE_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
#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

As we see in the main program, we are going to use the extractOnCriteria twice – once against the fgrade predicate (to extract failed students), and once against the did_all_hw (to extract students who did all homework). We then display to confirm that the single extractOnCriteria function works! (and it does indeed).

pete 100 100 100 100 100
jon 90 90 0 0 0
mary 50 50 50 50 50
anna 40 40 0 0 0
gary 80 80 0 80 0
bob 100 100 100 0 0
ken 20 88 99 44 66
jay 99 39 40 80 0
bill 20 88 0 39 0
^Z
^Z

These students did all homework:
pete
mary
ken

These students didn't do all homework:
jon
anna
gary
bob
jay
bill

These students have passed:
pete (100)
bob (60)
ken (65.6)

These students have failed:
jon (54)
mary (50)
anna (24)
gary (48)
jay (51.4)
bill (39.2)

Reference

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