Course: JavaScript

Progress (0%)

Exercise: Square Matrix Class

Exercise 37 Easy

Prerequisites for the exercise

  1. JavaScript Object Prototypes
  2. JavaScript Object Constructors
  3. All previous chapters

Objective

Create a SquareMatrix class in JavaScript to create and easily work with square matrices.

Description

In the exercises, we created a couple of functions to accomplish some common matrix operations such as addition, multiplication, rotation and transposition.

Now, you have to encapsulate all those into one single utility — the SquareMatrix class.

Let's explore what bells and whistles do we need to give to this class.

The SquareMatrix class defines two instance properties:

  1. array — holds the array used to represent the underlying matrix.
  2. n — holds the length of the matrix (which is same in both dimensions, since we're dealing with square matrics).

Apart from these properties, the class defines a total of six instance methods, four of which follow from the previous exercises, as described below:

  1. add(squareMatrixInstance) — adds the calling matrix to squareMatrixInstance (another instance of SquareMatrix) and returns the sum. This method creates and returns a new matrix (instance of SquareMatrix). It doesn't mutate either the calling matrix or the squareMatrixInstance argument.
  2. multiply(squareMatrixInstance) — multiplies the calling matrix to squareMatrixInstance and returns back the product. Akin to sum(), this method creates and returns a new matrix, and doesn't mutate either the calling matrix or the squareMatrixInstance argument.
  3. copy() — copies the calling matrix and returns back the copy.
  4. fill(value) — replaces each element of the calling matrix with value. In other words, the calling matrix is 'filled' up with the given value. The calling matrix is mutated. Nothing is returned by this method.
  5. rotateBy90() — rotates the calling matrix clockwise by 90°. The calling matrix is mutated. Nothing is returned by this method.
  6. transpose() — tranposes the calling matrix. The calling matrix is mutated. Nothing is returned by this method as well.

Note that the first three methods — add(), multiply() and copy() — are non-mutating i.e. they don't modify the original calling matrix in any way. A new matrix is returned at the end.

Similarly, the last three methods — fill(), rotateBy90() and transpose() — are mutating i.e. they modify the original calling matrix. Nothing is returned at the end.

When the constructor function SquareMatrix() is called with a single integer argument n, an n x n array is created with each element initialized to 0, and this array stored in the array property. In the meanwhile, the property n is initialized as well.

Otherwise, it's assumed that the function is called with an array as argument, and henceforth, the array is used as-is to create the matrix i.e. the given array argument is set as the value of the array property and n is updated likewise.

Given all these details, you have to create this SquareMatrix class in JavaScript using the idea of constructors and prototypes.

View Solution

New file

Inside the directory you created for this course on JavaScript, create a new folder called Exercise-37-Square-Matrix-Class and put the .html solution files for this exercise within it.

Solution

The specification of SquareMatrix class, as defined above, is quite clear to give us a clue about how to implement it.

Let's recap it. We need to have two instance properties: array and n, and six instance methods: add(), multiply(), copy(), fill(), rotateBy90(), transpose().

First, let's define the constructor function SquareMatrix():

function SquareMatrix(matrixArray) {

   // If matrixArray is a number,
   // Create an n x n matrix
   if (typeof matrixArray === 'number') {
      var n = matrixArray;
      matrixArray = [];

      for (var i = 0; i < n; i++) {
         matrixArray.push([]);
         for (var j = 0; j < n; j++) {
            matrixArray[i].push(0); 
         }
      }
      
   }

   this.array = matrixArray;
   this.n = matrixArray.length;

}

In here, we check the type of the argument passed in: if it's a number, we store that number inside a local variable n and then create an n x n array, with all 0s, held on in the variable matrixArray.

In the end, we set the instance properties array and n to matrixArray and the length of matrixArray, respectively.

The constructor's done.

Next up, we need to define all of the six instance methods.

First, let's get done with the first three non-mutating methods, of which two (add() and multiply()) are already implemented in the previous exercises.

We'll start with add():

SquareMatrix.prototype.add = function(matrix) {
   // Initializations
   var matrixArray = this.array;
   var secondMatrixArray = matrix.array;
   var n = this.n;

   // Create a new matrix to represent the sum
   var sumMatrix = new SquareMatrix(n);
   var sumMatrixArray = sumMatrix.array;

   for (var i = 0; i < n; i++) {
      for (var j = 0; j < n; j++) {
         sumMatrixArray[i][j] = matrixArray[i][j] + secondMatrixArray[i][j];
      }
   }

   return sumMatrix;
}

Note that from the previous implementation of add(), in the JavaScript Arrays — Matrix Arithmetic Exercise, we've changed quite a lot of stuff here and there and even added some new stuff.

First of all, the local variable matrixArray holds the array of the calling matrix. Note that this is the same name (i.e matrixArray) that we used in the constructor function SquareMatrix().

When programming classes, it's always a good idea to remain consistent across variable naming inside the class' utilities. For instance, we could have used matrixArray in SquareMatrix() and then something like thisArray (or simply array) in add().

Being consistent not only helps in understanding the code much better and much quicker, but also makes sure that we don't mistakenly introduce bugs into our program by accessing a variable directly when we had to access a property on that variable instead.

Moving on, then we define a variable secondMatrixArray to hold the array of the given matrix argument. Finally, we save this.n inside the variable n.

Next up, we create a new n x n matrix to hold the sum, store that in sumMatrix, and then save the value of its array property in sumMatrixArray for quick access later on inside the for loop.

Notice how we append the name of every variable with the word 'Array' when that variable holds the array of a given SquareMatrix instance. This is an example of consistency.

Now, let's move on to multiply().

SquareMatrix.prototype.multiply = function(matrix) {
   // Initializations
   var matrixArray = this.array;
   var secondMatrixArray = matrix.array;
   var n = this.n;

   // Create a new matrix to represent the product
   var productMatrix = new SquareMatrix(n);
   var productMatrixArray = productMatrix.array;

   var sum;

   for (var i = 0; i < n; i++) {
      for (var j = 0; j < n; j++) {
         sum = 0;
         for (var k = 0; k < n; k++) {
            sum += matrixArray[i][k] * secondMatrixArray[k][j];
         }
         productMatrixArray[i][j] = sum;
      }
   }

   return productMatrix;
}

As you can see, the initializations here are the same as in add() and the naming of productMatrix and productMatrix is also quite similar to the sumMatrix and sumMatrixArray.

Moving on to copy():

SquareMatrix.prototype.copy = function(matrix) {
   // Initializations
   var matrixArray = this.array;
   var n = this.n;

   // Create a new matrix to hold the copy
   var copiedMatrix = new SquareMatrix(n);
   var copiedMatrixArray = copiedMatrix.array;

   for (var i = 0; i < n; i++) {
      for (var j = 0; j < n; j++) {
         copiedMatrixArray[i][j] = matrixArray[i][j];
      }
   }

   return copiedMatrix;
}

copy() is very easy to implement — just iterate over the calling matrix and copy each value on to the new matrix.

Hopefully, now you're getting the gist of the naming of variables and their behavior in these methods.

With this, we are done with the definition of our three non-mutating methods. It's time to address the remaining three, mutating, methods.

First, let's consider fill():

SquareMatrix.prototype.fill = function(value) {
   // Initializations
   var matrixArray = this.array;
   var n = this.n;
                  
   for (var i = 0; i < n; i++) {
      for (var j = 0; j < n; j++) {
         matrixArray[i][j] = value;
      }
   }

   // Return nothing
}

fill() simply iterates over the entire matrixArray and assigns the argument value to each position, effectively making every element of the calling matrix equal to value.

Now, over to rotateBy90():

SquareMatrix.prototype.rotateBy90 = function() {
   // Initializations
   var matrixArray = this.array;
   var n = this.n;
   
   // Copy the calling matrix using its copy() method
   var copiedMatrixArray = this.copy().array;
               
   for (var i = 0; i < n; i++) {
      for (var j = 0; j < n; j++) {
         matrixArray[i][j] = copiedMatrixArray[n - j - 1][i];
      }
   }

   // Return nothing
}

Notice how we've removed the first set of nested for loops (as we had previously in the final code snippet in JavaScript Arrays — Matrix Rotation Exercise) from the method's definition here and used the copy() method instead.

This is an example of something called code reuse.

We define the matrix copying implementation once inside the copy() method and then re-use from a given SquareMatrix instance method very conveniently.

The OOP paradigm is very good at code reuse — in fact, one of the main purposes of OOP is code reuse.

Finally, let's get done with transpose():

SquareMatrix.prototype.transpose = function() {
   // Initializations
   var matrixArray = this.array;
   var n = this.n;
                  
   for (var i = 0; i < n - 1; i++) {
      for (var j = i + 1; j < n; j++) {
         temp = matrixArray[i][j];
         matrixArray[i][j] = matrixArray[j][i];
         matrixArray[j][i] = temp;
      }
   }

   // Return nothing
}

Altogether, our code looks something as follows:

function SquareMatrix(matrixArray) {
   /* ... */
}


// Non-mutating methods

SquareMatrix.prototype.add = function(matrix) { /* ... */ }

SquareMatrix.prototype.multiply = function(matrix) { /* ... */ }

SquareMatrix.prototype.copy = function() { /* ... */ }


// Mutating methods

SquareMatrix.prototype.fill = function(value) { /* ... */ }

SquareMatrix.prototype.rotateBy90 = function() { /* ... */ }

SquareMatrix.prototype.transpose = function() { /* ... */ }

"I created Codeguage to save you from falling into the same learning conundrums that I fell into."

— Bilal Adnan, Founder of Codeguage