Example: Matrix

Let’s look at an example from your text that create a very simple matrix container as a vector-of-vectors.

We’ll write our own version of what the text does.

Listing 8 simple_matrix.cpp
#include <iostream>
#include <vector>

// a type alias -- we can now use RealVec to mean a vector of doubles

using RealVec = std::vector<double>;

// a RealMatrix is a vector of vectors of doubles

using RealMatrix = std::vector<RealVec>;

int main() {

    RealMatrix M = {
                     {  1.0, 25.0, 0.0,  4.1},
                     { -1.0,  2.0, 4.5, -3.1},
                     { 10.0, -6.0, 3.2,  4.0}
                    };

    // add another row

    M.push_back({ 20.0, 2.0, 0.0, 5.0});

    // let's compute the trace (sum over the diagonal)

    double trace = 0.0;

    for (int i = 0; i < M.size(); ++i) {
        trace += M[i][i];
    }

    std::cout << "trace = " << trace << std::endl;

    // now the sum of all the off-diagonal terms

    double off_diag_sum = 0.0;

    for (int r = 0; r < M.size(); ++r) {
        for (int c = 0; c < M[r].size(); ++c) {
            if (r != c) {
                off_diag_sum += M[r][c];
            }
        }
    }

    std::cout << "off-diagonal sum = " << off_diag_sum << std::endl;

}

Tip

Notice that we use some type aliases to make it easy to reuse this datatype.

Some comments:

  • For our matrix M, M[0] is the first row. It is a vector, and the columns in that row are all stored together in that vector. This is an example of row major ordering of the data.

  • To access an element at row r and column c, we do: M[r][c]. The first indexing, M[r] gives us the vector that holds row r ‘s data. Then we just index that vector with [c] to get the position corresponding to the column we want.

  • When we push_back() onto the matrix, we are adding a whole new row.

  • When we loop over elements, we first loop over rows (outer loop) and then we loop over the columns in that row – that data is contiguous, so this looping will make the best use of memory cache.

  • We do nothing here to enforce that every row has the same number of elements – this can potentially be unsafe if we try to access beyond the limits of a row. We’ll fix this later.

  • Both of our operations involve sums over elements – we are careful to first initialize the sum to 0 before adding to it.