Project 2: Who wants to be a course coordinator?
Due: Tuesday, March 5, before 9:00 pm
Addenda
- An updated version of GraphTest1.cpp and sample output file have been posted under Test Programs.
- A sample graph implementation (GraphTest1.cpp) with errors has been added to the Test Programs.
-
When testing iterators, the two tests (size and values) are
independent.
Example 1: Suppose the test program returns {1, 4, 5, 7} for the neighbors of vertex v, but the correct set (returned by the baseline implementation) is {1, 3, 4, 5, 7}. The program would fail the size test but pass the values test since all the values it returns are neighbors of v.
Example 2: Test program returns {1, 2, 3, 4} but the correct neighbors are {1, 2, 3, 11}. The program would pass the size test but fail the values test (since 4 is not a valid neighbor).
Example 3: Test program returns {1, 2, 3, 4} but the correct neighbors are {2, 4, 8, 16, 32}. The program would fail_ both tests.
Objectives
The objective of this programming assignment is for you to become familiar with some of the templated data structures in the C++ Standard Template Library (STL).
Introduction
In this project, you will complete a program to grade an implementation of the Graph class from Project 1. In order to test whether an implementation is correct, we need some way to know how a correct implementation would function. For example, suppose we construct a Graph object with the implementation we are testing and want to determine whether the m_nz, m_ci, and m_re arrays have been populated correctly. It would be nice if we had an implementation of Graph that we knew to be correct; then we could construct the same graph in our correct implementation and just compare the arrays in the the two objects.
We aren't allowed to have two different classes with exactly the same name. So how can we possibly run two versions of the class and compare them? Well, we could give one of them a different name. Or we can just put one of them in a different namespace. Our correct implementation will just be a re-working of the Graph class, contained in a namespace called Baseline:
In addition to being contained in a namespace, there are some other important differences between Baseline::Graph and the original Graph class:
- It uses STL vectors instead of C-style arrays and, therefore, does not directly use dynamic memory allocation.
- Since it does not use dynamic memory allocation, it does not need a destructor, copy constructor, or assignment operator.
- Since vectors automatically resize themselves, you do not need to explicitly expand them in addEdge().
- Since the vector container provides an insert() method, you will not have to shift elements of m_nz and m_ci when inserting a new edge.
Once you have implemented Baseline::Graph, you will be able to use it and a test implementation of Graph in the same program. For example:
Once you've got Baseline::Graph working and have a version of Graph to test, you're ready to start writing the required tests in the Grader class.
Assignment
For this project, we will start you off with several source files:
- BaselineGraph.h — declarations for the Baseline::Graph class.
- BaselineGraph.cpp — beginning of the implementation of Baseline::Graph (really just the namespace set-up).
- test1bg.cpp — the same program as test1.cpp from Project 1, modified to work with Baseline::Graph.
- Grader.h — declaration of the Grader class.
- Grader.cpp — beginning of the implementation of Grader. Stubbed member functions; implementation of an overloaded operator<< for use with Error, a small utility class defined in Grader.h.
- errors.txt — descriptions of the error conditions that must be tested for by Grader. Each error has a point deduction value, an error id, and a text description.
- Graph.h — declarations for the Graph class.
- driver.cpp — a short driver program demonstrating the use of the Grader class.
First, you need to complete the implementation of Baseline::Graph. The requirements for the methods in Baseline::Graph are the same as in Project 1 and will not be repeated here. You can use test1bg.cpp for basic testing of the class. There is no test2bg.cpp because the main purpose of test2.cpp in Project 1 was to to test the copy constructor and assignment operator, which are not needed for this implementation.
Next, implement the methods of Grader. Most of the work is in implementing the required test functions:
Method | Description of tests |
---|---|
testCSRExact() | Tests exact correctness of the m_re, m_nz, and m_ci arrays. Since m_nz and m_ci should be ordered by increasing column index, there is a single correct configuration of the arrays. The test should check correctness of the arrays after any edge is inserted. |
testCSRUnordered() | Tests correctness of the m_re, m_nz, and m_ci arrays, without regard to ordering within each row. That is, if the values for each row are correct but are not ordered by increasing column index, this test should pass. |
testNbIterator() | Tests that the NbIterator produces the correct set of neighbors for every vertex in the graph. |
testEgIterator() | Tests that the EgIterator produces a valid listing of the edges of the graph. All edges must be included and none may be repeated. |
testExceptions() | Tests that an out_of_range exception is thrown for every operations specified to do so in the Project 1 description. Must check if some other exception is thrown, or no exception at all. |
Each of these test functions can generate Error objects, defined in Grader.h. Each Error object consists of a point deduction, error id, and error description. Whenever an Error is produced, it is pushed onto a queue, a private variable of the Grader class. Somewhat counter-intuitively, there are error types that correspond to passing a test (i.e. “no error”); this allows success messages to be pushed into the error queue and printed at the end of the test program. The complete list of errors that the test functions can produce is described in errors.txt.
Grader has three methods in addition to the test functions:
Method | Description of method |
---|---|
Grader(string name="") | The Grader constructor; accepts an optional descriptive name for the object. |
printAllErrors() | Print all the errors in the error queue and the total point deductions; leaves the queue empty. |
resetErrorQueue() | Removes all entries from the error queue. |
To test your implementation of Grader, you'll need a version of Graph.cpp from Project 1. Your own submission should be sufficient to get started. It is not difficult to modify your Project 1 code to produce specific errors. However, you will be provided with some sample Graph implementations with which to test your code after the final submission deadline for Project 1.
Specifications
Requirement: You may not modify Graph.h or BaselineGraph.h. You may add private helper functions to the Grader class, but you may not change anything else in Grader.h.
Requirement: Your Baseline::Graph class must satisfy all the requirements of Project 1 except those specifically excluded due to the use of vectors rather than C arrays (e.g. no destructor, copy constructor, or assignment operator).
Requirement: You must use at least one STL container class in addition to vector and queue, and the the third container must be used within one of the test methods of Grader. Good candidates for the third container are set or multiset.
Requirement: The testCSRExact() and testCSRUnordered() functions shall test the correctness of the arrays whenever an edge is added to the graph. If at any point, one of the arrays is incorrect, then the implementation fails the test and the appropriate Error shall be pushed onto the queue.
Requirement: No error (identified by the unique error id) shall be pushed into the error queue more than once. Suppose testCSRExact() checks the correctness of m_nz after each call to addEdge() and finds that the array is incorrect after each insertion; the error with id 102 should still only be pushed onto the queue once.
Requirement: Contradictory errors shall not be pushed onto the Error queue. For example, only one of errors 510, 511 and 512 can occur; only one should appear in the queue.
Requirement: “No error” errors (e.g. 100, 200, 300, etc.) shall be pushed onto the queue whenever appropriate.
Requirement: You must use the provided operator<< to output Error objects.
Test Programs
The following programs may be used to test your implementations. These are only basic test programs; it is your responsibility to thoroughly test your programs.
-
GraphTest1.cpp
is a test Graph.cpp file. Use it along
with driver.cpp, Grader.cpp,
and Baseline::Graph.cpp to test your project
implementation. Sample output is
in gt1.txt. Build the
program with the command:
g++ driver.cpp Grader.cpp BaselineGraph.cpp GraphTest1.cpp -o driver Note: The original test files are still available as GraphTest1_old.cpp and gt1_old.txt. -
test1bg.cpp can be
used to test Baseline::Graph. Sample output is provided
in test1bg.txt and
Valgrind output is
in test1bg_val.txt.
Build the test program with the command:
g++ BaselineGraph.cpp test1bg.cpp -o test1bg -
driver.cpp can be
used to test Grader (along with Baseline::Graph
and an implementation of Graph). Sample output with no
errors is
in driver_noerr.txt
and output with errors in the m_nz and m_ci is
in driver_exactErr.txt.
Build the test program with the command:
g++ driver.cpp Grader.cpp BaselineGraph.cpp Graph.cpp -o driver
These and all the provided project files are available on GL:
Implementation Notes
-
In Project 1, the neighbor iterator is not required to list the neighbors in any particular order. Therefore, you cannot assume that Baseline::Graph and Graph will list them in the same order, so stepping both iterators and comparing the values when you dereference them is not a valid test.
One way you might deal with this is to insert the neighbors into two set containers. set stores its elements in a standard order: if you iterate over two sets containing the same elements, the elements will be listed in the same order. For example, the code below produces the following output:
1 1 2 2 3 3 set A; set B; A.insert(1); A.insert(2); A.insert(3); B.insert(2); B.insert(3); B.insert(1); set ::iterator Ait, Bit; for (Ait = A.begin(), Bit = B.begin(); Ait != A.end(); Ait++, Bit++) { cout << *Ait << " " << *Bit << endl; } The find() and count() methods of the set container useful may also be useful.
- The order in which the edge iterator lists the edges of the graph is not specified, so you must be able to test correctness without assuming a particular order. Again, the set container may be useful. The multiset container is similar to set except that it allows for values to repeated. This might be useful for, say, detecting whether an edge had been listed more than once.
- Be careful when comparing edges from the edge iterator. Remember that (u, v) is the same edge as (v, u).
What to Submit
You must submit the following files to the proj2 directory.
- Grader.h
- Grader.cpp
- BaselineGraph.cpp
If you followed the instructions in the Project Submission page to set up your directories, you can submit your code using this Unix command command: