Due: Tuesday Sep 29, before 9:00 pm
Objectives
- Practice analyzing a project's requirements.
- Implement an algorithm to compress sparse matrices.
- Implementing a singly linked list.
- Combining two data structures to satisfy a project's requirements.
- Practice C++ dynamic memory management, i.e. avoiding memory leaks, avoiding double free, protection against wrong accesses in memory (segmentation fault), etc.
- Practice writing test cases for a project.
Introduction
In many applications we need to use large matrices which most of their elements are zero. Such a matrix is called a sparse matrix. We do not need to store many zeros, because it consumes memory without being required. Researchers introduced formats to represent the sparse matrices without storing all zeros. One of the formats to represent sparse matrices is called Compressed Sparse Row (CSR). CSR format packs members of a matrix into an array. The following example shows how CSR is presenting a sparse matrix. The following example is from Wikipedia.

In the above matrix (4 x 6) we only need to store the non-zero values. We define three arrays, V stores the non-zero values, COL_INDEX stores column indices for the non-zero values respectively, and ROW_INDEX is described shortly after this. We can populate the arrays by scanning the matrix from left to right, row by row from top to bottom. Then, for the above example,
V = [ 10 20 30 40 50 60 70 80 ]
COL_INDEX = [ 0 1 1 3 2 3 4 5 ]
The ROW_INDEX array stores the information as follows, in the first index it stores 0, in the second index it stores the cumulative number of non-zero elements from first row in the sparse matrix, that would be 2. One may note that there are two non-zero values in the first row of the sparse matrix. The ROW_INDEX array stores the value 4 in its third position since from row 0 to row 1 in the sparse matrix there are 4 non-zero values. In general the ROW_INDEX array stores at every i-th position the cumulative number of non-zero members from row 0 to row (i-th – 1) from the sparse matrix. One may note that the i-th positions start at 0. This means at the last position of ROW_INDEX we store the total number of non-zero values.
ROW_INDEX = [ 0 2 4 7 8 ]
The following presents how we access every individual member of the matrix. To extract the members of a row in matrix, we use the following calculations,
row_start = ROW_INDEX[row]
row_end = ROW_INDEX[row + 1]
For example, to extract members of row 2 from the sparse matrix, row_start would be 4 (ROW_INDEX[2] = 4), and row_end would be 7 (ROW_INDEX[2+1] = 7). We can find the members of row 2 from the sparse matrix in the array V. The indices 4 to (7-1) are the non-zero values in the row 2 of matrix, i.e. 50, 60, and 70. For every one of those non-zero values we can find its column index in COL_INDEX. For example, the column index for 50 would be 2, and the column index for 70 would be 4.
We can calculate the sparsity ratio for a matrix, which shows how sparse the matrix is. The number of zeros divided by the total number of members in a matrix shows the sparsity ratio. The sparsity ratio allows us to determine how efficient is the used data structure in regard to space (memory). In this project we convert the ratio into percentage, e.g. if the ratio is 0.656, we convert it to 0.656 x 100 = 65.
Assignment
Your assignment is to implement a linked list data structure with head pointer along with support for Compressed Sparse Row (CSR) objects.
For this project, you are provided with the skeleton .h files and a sample driver:
- csr.h - Interface for the CSR and CSRList classes.
- csr.cpp - The skeleton for the CSR and CSRList classes implementation.
- driver.cpp - The sample driver/test program.
- driver.txt - The output of sample driver/test program.
You will implement the classes CSR and CSRList in csr.cpp.
Specifications
The CSR Class
The CSR class has three member variables m_values, m_col_index, and m_row_index which can be used for storing the information about a sparse matrix according to the Compressed Sparse Row algorithm. One may refer to the Introduction section on this page for the details about the algorithm. The member variables in the CSR class require dynamic memory allocation. The CSR class has also a member variable m_next which points to the next CSR object in a linked list. The class CSR can be used as a node in a linked list. The CSR class should not handle allocating or deallocating on its m_next member variable.
The CSRList Class
The class CSRList implements a singly linked list in which nodes are of the type CSR.
Note: If we use simply insertAtHead(...) to make insertion, the new list will be reversed. That is not what we need. Copy constructor makes an exact copy of rhs, i.e. the order of the CSR objects should be the same as rhs.
Additional Requirements
- To build a matrix from the array, we use the row-by-row scanning pattern. For example, if the array is {0,1,0,0,0,0,0,2}, the indices 0 to 3 make the first row of the matrix, and the indices 4 to 7 make the second row of the matrix for a matrix of 2 x 4.
- In constructing a CSR object, if the number of data members in the input array is less than (m x n), the function uses the array to initialize the matrix, and the rest of matrix members will be initialized to zero.
- In constructing a CSR object, if the number of data members in the input array is greater than (m x n), the function uses the array to initialize the matrix, and the rest of array data will be ignored.
- In constructing a CSR object, if the user decides to make a 0 x 0 matrix, that would be considered an empty object, even if user provides data in the array.
- In constructing a CSR object, if user specifies the matrix size, but does not provide data in the array it would be considered a matrix with all members set to zero.
- The class declarations (CSR, CSRList) and provided function implementations in csr.cpp may not be modified in any way. No additional libraries may be used.
- You are not allowed to use STL template containers in the CSR and CSRList classes.
- You are allowed to use STL template containers in your test functions.
- The class functions must be written in csr.cpp; in particular, they must not be written “in-line.”
- Private helper functions may be added, but must be declared in the private section of the CSR and CSRList classes. There is a comment indicating where private helper fuction declarations should be written.
Testing
- The test file name must be mytest.cpp; the file name must be in lower case, a file name like myTest.cpp is not acceptable.
- The test file must contain the declaration and implementation of your Tester class and the main() function as well as all your test cases, i.e. calls to your test functions.
- You are responsible for adequately testing your work before submission. The following section presents a non-exhaustive list of tests to perform on your implementation.
- You must write a separate function for every test case.
- Every test function must return true/false depending on passing or failing the test. Visual outputs are not accepted as test results.
- The dump() function should not be called in the test functions. The dump() function is only provided to facilitate debugging.
- Tests cannot be interactive. The test file mytest.cpp must compile and run to completion.
- An example of declaring, implementing, and calling a test function, and outputting the test results was provided in the driver.cpp file of project 0.
- The testing guidelines page provides information that helps you to write more effective test cases.
Note: Testing incrementally makes finding bugs easier. Once you finish a function and it is testable, make sure it is working correctly.
Testing CSR Class
- Test whether the compress(...) function works correctly for a normal case, e.g. it populates the member variables of the class with the expected values for an array of data.
- Test whether the compress(...) function works correctly for an error case, e.g. the user provides data which is less than the asking matrix size.
- Test whether the compress(...) function works correctly for an error case, e.g. the user asks for a 0 x 0 matrix but provides data.
- Test whether the overloaded equality operator works correctly for a normal case.
- Test whether the overloaded equality operator works correctly for an edge case, i.e. both objects are empty.
- Test whether the getAt(...) function throws an exception for an error case, i.e. the requested index numbers do not exist in the matrix.
Testing CSRList Class
- Test whether the overloaded assignment operator works correctly for a normal case.
- Test whether the overloaded assignment operator works correctly for an edge case, e.g. the case of assigning an empty object to an object that contains data.
- Test whether the getAt(...) function throws an exception for an error case, i.e. the list of matrices is empty.
- Test whether the getAt(...) works correctly for a normal case, it returns the expected answer.
Testing For Memory Leaks / Memory Errors
- Run your test program in valgrind; check that there are no memory leaks or errors.
Note: If valgrind finds memory errors, compile your code with the -g option to enable debugging support and then re-run valgrind with the -s and --track-origins=yes options. valgrind will show you the line numbers where the errors are detected and can usually tell you which line is causing the error. - Never ignore warnings. They are a major source of errors in a program.
What to Submit
You must submit the following files to the proj1 submit directory:
- csr.h
- csr.cpp
- mytest.cpp (Note: This file contains the declaration and implementation of your Tester class as well as all your test cases.)
If you followed the instructions in the Project Submission page to set up your directories, you can submit your code using the following command:
cp csr.h csr.cpp mytest.cpp ~/cs341proj/proj1/
Grading Rubric
For details of grading rubric please refer to Project Grading Policy