Matrix Unary Arithmetic and Transpose

Introduction to the unary operators

In this lesson, we’ll explore unary operators and their usage. Unary operators differ from binary operators, which we previously implemented, as they operate on a single object instead of two objects. Here’s a list of the unary operators we’ll be discussing:

  • Increment: The ++ operator (both pre- and post-increment ++M or M++)

  • Decrement: The -- operator (both pre- and post-decrement --M or M--)

  • Additive inverse: The minus - operator

  • Transpose: The logical NOT ! operator

  • Multiplicative inverse: The ~ operator

Here’s the playground where we’ll implement the unary operators:

Exercise playground

3

2 2
1 2 
4 5

2 2
7 8 
2 2

5 3 
4 6 5
3 3 4
5 8 9
4 5 3
7 2 3
Exercise playground

Let’s move toward the implementation of unary operators.

The increment (++) operator

The increment (++) operator is used to increment the value of an object by one.

Pre-increment

We’re already acquainted with increment operators, which are used to increase the value of an object by one. In the given code, the pre-increment operator (++M) is implemented for a Matrix object. It allows us to increment each element of the matrix by one, resulting in an updated Matrix object.

Here’s how we implement it:

Press + to interact
// ++Mi: Pre-increment
const Matrix Matrix::operator++()
{
Matrix result = (*this) + 1;
*this = result;
return *this;
}

  • Line 4: Matrix object called result is created and initialized with the sum of the current Matrix object, denoted as (*this) + 1.

  • Line 5: The current Matrix object, represented as *this, is assigned the value of the result Matrix, effectively updating its content.

  • Line 6: Finally, the incremented Matrix object, *this, is returned.

Instruction: Write the above code in Exercise playground to test the correct output.

Post-increment

The provided code snippet demonstrates the implementation of the post-increment operator (Mi++) in the Matrix class.

Press + to interact
// Mi++: Post-increment
const Matrix Matrix::operator++(int)
{
Matrix result = (*this);
*this = *this + 1;
return result; // The previous value is returned
}
  • Line 2: The operator is defined as a member function with the signature Matrix Matrix::operator++(int). The int parameter is used to distinguish the post-increment operator from the pre-increment operator.

  • Line 4: Inside the function, a copy of the current matrix (*this) is created and stored in the result variable. This copy represents the matrix before the increment operation.

  • Line 5: Then, the current matrix (*this) is incremented by 1 using the + operator. This operation modifies the matrix itself, increasing each element by 1.

  • Line 6: Finally, the function returns the result matrix, which represents the matrix before the increment operation. This behavior matches the expected behavior of the post-increment operator, where the value is incremented, but the original value is returned.

Note: It is important to note that the return type of the operator function is constant (const Matrix). This is done to prevent chaining multiple post-increment operators (Mi++++), which would involve incrementing a temporary result. Chaining like this is prohibited to avoid unnecessary and potentially problematic operations on temporary copies generated by the compiler.

By implementing the post-increment operator in the Matrix class, we can now use the Mi++ syntax to increment a matrix and obtain its previous value for further computations or assignments.

Tip: Write the above code in Exercise playground to test the correct output.

  • Add the above two operators (const Matrix operator++(int) and const Matrix operator--(int)) as prototypes in the header file (Matrix.h).

  • Implement the two operators in the cpp file (Matrix.cpp).

  • Uncomment lines 12–13 in your main.cpp file to test these two operators.

The decrement (--) operator

The decrement operator (--) is used to decrease the value of an object by one. Now, it’s your turn to implement the decrement operator yourself. The requirement is that it should decrease the value of every element of the matrix by one.

Tip: Try your implementation in Exercise playground.

If you have trouble getting to the solution, you can click the “Show solution” button to see how to implement it.

The additive inverse (-) operator

The unary minus (-) operator is used to negate or change the sign of an object’s value. In this code snippet, we’ll implement this operator for a Matrix object by multiplying each element of the matrix by -1.

Press + to interact
const Matrix Matrix::operator-()const
{
Matrix result(this->rows, this->cols);
for (int r = 0; r < this->rows; r++)
{
for (int c = 0; c < this->cols; c++)
{
result.Vs[r][c] = (this->Vs[r][c] *-1);
}
}
return result;
}
  • Line 3: Matrix object called result is created, having the same dimensions (rows and columns) as the current Matrix object.

  • Lines 4–10: Inside the nested loop, each element of the current Matrix object, denoted as this->Vs[r][c], is multiplied by -1 and assigned to the corresponding element of the result Matrix object, denoted by result.Vs[r][c].

  • Line 11: After completing the iteration, the result object, with all elements negated, is returned.

Tip: Write the above code in Exercise playground to test the correct output.

Transpose of a matrix

We’ll implement the logical NOT (!) operator to take the transpose of the given matrix as shown in the illustration below:

Press + to interact
Matrix transpose
Matrix transpose

In the Matrix class, the ! operator creates a new matrix that represents the transpose of the original matrix—swapping the rows and columns—and returns it.

Let’s look at the code below:

Press + to interact
const Matrix Matrix::operator!()const
{
Matrix Transpose(this->cols, this->rows);
for (int r = 0; r < this->rows; r++)
{
for (int c = 0; c < cols; c++)
{
Transpose.Vs[c][r] = this->Vs[r][c];
}
}
return Transpose;
}
  • Line 3: We create a new Matrix object called Transpose with the dimensions of the original matrix swapped (the number of columns becomes the number of rows, and vice versa).

  • Line 4-10: Inside the nested loop, we assign the value of the current element in the original matrix, represented as this->Vs[r][c], to the corresponding element in the transposed matrix, represented as Transpose.Vs[c][r]. This swapping of r and c ensures that the elements are correctly placed in the transposed matrix.

  • Line 11: Once the iteration is complete, we return the transposed matrix, which is stored in the Transpose object.

Multiplicative inverse of a matrix

We’ll implement the ~ operator to compute the inverse of a given matrix. The product of the matrix and its inverse will result in a 2×22 \times 2 identity matrixAA, as shown below:

Let’s say we have the following matrixAA:

Here's how to calculate A1A^{-1}:

  • We first need to calculate the determinant of AA.

Generally, if we have the following matrix:

A=[abcd]A =\begin{bmatrix} a & b \\ c & d \\ \end{bmatrix}

  • The determinant of a matrix involves multiplying the diagonal elements (aa and dd) and subtracting the product of the off-diagonal elements (bb and cc).

det(A)=adbc\text{det}(A) = ad - bc

  • If det(A)=0\text{det}(A) = 0, then the inverse of the matrix is not possible.

  • Now that we know that the det(A)0,\text{det}(A)\neq0, it’s confirmed that the inverse of matrix AA exists.

  • Next, we compute the adjoint of a 2×22\times2 matrix by swapping the positions of the diagonal elements and changing the signs of the off-diagonal elements.

               Adjoint(A)=[dbca]\text{Adjoint}(A) =\begin{bmatrix} d & -b \\ -c & a \\ \end{bmatrix}

             Adjoint(A)=Adjoint([2357])=[7352]\text{Adjoint}(A) =\text{Adjoint}\left(\begin{bmatrix} 2 & 3 \\ 5 & 7 \\ \end{bmatrix}\right)=\begin{bmatrix} 7 & -3 \\ -5 & 2 \\ \end{bmatrix}

  • Here’s the formula for finding the inverse of a matrix.

  • With det(A)=1\text{det}(A) = -1, the inverse of the matrix AA will be:

Here’s the implementation to find the inverse of a 2×22\times2 matrix.

Press + to interact
const Matrix Matrix::operator~()const
{
if (this->rows != 2 && this->cols != 2)
throw("invalid");
int determinant = (this->Vs[0][0] * this->Vs[1][1]) - (this->Vs[0][1] * this->Vs[1][0]);
if (determinant == 0)
throw(determinant);
Matrix inverse(this->rows, this->cols);
inverse.Vs[0][0] = this->Vs[1][1];
inverse.Vs[1][1] = this->Vs[0][0];
inverse.Vs[0][1] = this->Vs[0][1]*-1;
inverse.Vs[1][0] = this->Vs[1][0] * -1;
inverse = inverse / determinant;
return inverse;
}
  • Lines 3–4: We check if the input matrix is of size 2×22\times2.

  • Line 5: We calculate the determinant of the input matrix.

  • Line 6: We check if the determinant is nonzero.

  • Lines 8–13: We compute the inverse as stated in above formula.

  • Line 14: We return the computed inverse.

Tip: Write the above code in Exercise playground to test the correct output.