[Code Review] Numerical Data From Textarea Box to 2D Array

Okay. This is my first try of this Code Review invitation. Looking forward to learning ways to improve the code.


I would like to input real numbers from a textarea box and place them into a two-dimensional array.
The first number is an integer indicating the size of the SQUARE matrix.
The following numbers are all real numbers.
Scientific notation is NOT recognized and the user is informed of this limitation.

Here is the link to the working page: Eigenvalues and Eigenvectors Calculator

Here is the link directly to the external .js file that presently handles this task: http://www.akiti.ca/insysArealt.js
It is not pretty.

And here it is embedded in this post, if you don’t want to leave the forum:


<!--Define JavaScript functions.-->

function tinareal(rawtextareaString, inPar, sqAMatrix){			
  // Sub-routine for getting [A] matrix data from a textareabox:
  // N, the size of the N x N matrix, and the
  // A components.
  // Assumes input string comes from a textarea box and that all entries represent real numbers.

 var MAXDIM = 12;             		// Maximum size, N, of N x N matrix accepted by this script.

 var rawArray = new Array(); 		// Array for holding raw data entry from textarea box
 var rawArrayLength = 0; 		// The length of the raw array entered from textarea box
 var numCoeff = 0;  			//Indicates the number of data entries that have been entered
 var k = 0;   					// Array index
 var tempx = 0.0; 				// Dummy variable

 if (rawtextareaString.length == 0){
  alert("The length of textareaString is 0. No data has been entered. No further action taken.");
  inPar.inEr = 1;
  return;
 }

 // At this point, rawtextareaString is a big long string containing the entire field entered in the textarea box.
 // Must now clean up the data: remove leading and trailing spaces, identify the inputs, etc.

 // Check to see that string contains digits; if not, no point continuing
 if (rawtextareaString.search(/\\d/) == -1){
  alert("textareaString does not contain any digits. No further action taken.");
  inPar.inEr = 1;
  return;
 }

 // Check to see that string contains only digits, decimal points, "+" or "-" sign, or white space; if not, no point continuing
 if (rawtextareaString.search(/[^\\d\\.\\s\\+-]/) != -1){
  alert("textareaString contains an invalid character. Please edit data so that it is in the appropriate format. No further action taken.");
  inPar.inEr = 1;
  return;
 }

 // Check to see that string contains newline characters;
 // if not, then there are not AT LEAST two entries, and the algorithm won't work--no point continuing
  if (rawtextareaString.search(/\
/) == -1){
   alert("This utility requires at least two entries to be entered, but two entries have not been detected. Either fewer than two entries have been entered, or the data is not in the appropriate format. No further action taken.");
   inPar.inEr = 1;
   return;
 }

 // Do some rough clean up
 rawtextareaString = rawtextareaString.replace(/\\s*$/, ''); // Remove trailing whitespace
 rawtextareaString = rawtextareaString.replace(/^\\s*/, ''); // Remove leading whitespace

 // Divide the string up into its individual entries, which--presumably--are separated by whitespace
 rawArray = rawtextareaString.split(/\\s+/g);
 rawArrayLength = rawArray.length;

 // Check to see if at least two entries are present
  if (rawArrayLength < 2){
   alert("This utility requires AT LEAST two entries to be entered, but fewer than two entries have been detected. No further action taken.");
   inPar.inEr = 1;
   return;
  }

// A maximum of 145 entries may be entered (first entry for the matrix dimension, up to 144 for the a-coefficients).

if (rawArrayLength > 145){
   alert("This utility accepts input of up to 145 entries; however, more than 145 entries have been input. No further action taken.");
   inPar.inEr = 1;
   return;
}

 numCoeff = rawArrayLength - 1;

// Now check the individual data entries, confirm they are valid numbers, and place the entries in the array

 k = parseInt(rawArray[0]);
 if (!isNaN(k)){ // Degree field contains a valid number; otherwise ignore
   inPar.mDim = k;
  }//End if !isNaN
  else {
    alert("Invalid input for matrix dimension. No further action taken.");
    inPar.inEr = 1;
    return;
 } // End else

 if (k > MAXDIM){
      alert("This utility accepts matrices of dimension " + MAXDIM + " or less. However, a higher number has been input. No further action taken.");
      inPar.inEr = 1;
      return;
 }

 if (k <= 0 ){
   alert("Negative values for the matrix dimension are not accepted. No further action taken.");
   inPar.inEr = 1;
   return;
 }

 if (numCoeff != (k*k)){
   alert("The number of coefficients entered does not correspond to the matrix dimension entered. Check the data. No further action taken.");
   inPar.inEr = 1;
   return;
 }

// Input the elements of the A matrix: A[0][0], A[0][1], A[0][2], . . .  A[1][0], A[1][1], A[1][2], . . . etc.

 k = 1;
 for (var i = 0; i < inPar.mDim; i++) { // Examine the data fields
   for (var j = 0; j < inPar.mDim; j++) {
     tempx = parseFloat(rawArray[k]);
     if (!isNaN(tempx)){ // Field contains a valid number
      sqAMatrix[i][j] = tempx;
     }//End if !isNaN
     else {
      alert("Invalid input for entry " + k + ". No further action taken.");
      inPar.inEr = 1;
      return;
     } // End else
     k++;
   } // End for j
 } // End for i

 //  *****************************************************************
 // At this point, inPar.mDim should be the matrix dimension, N,
 // and sqAMatrix should be the square matrix A, of size N x N.
 //  *****************************************************************

return;
}  //End of tinareal

// end of JavaScript-->


It breaks down every check of possible errors and data clean-up into a separate if condition.
For example, check if any entries, check if enough entries, check if too many entries, check if numbers present, check if non-numbers present (other than a decimal point), etc.
If anything wrong is detected in the data, the error flag “inEr” in the input parameter is set to 1 and the program returns from this routine, so the main program can indicate an error and quit.

It would be nice to be able to combine or eliminate any of these checks. I’d like the code to be as efficient as possible; I’d like to use the same .js file to handle 2D matirx input of MANY other matrix manipulation routines.

On a run through with jslint in order to polish up the obvious, some interesting issues occurred.

The following regular expression

if (rawtextareaString.search(/[^\\d\\.\\s\\+-]/) != -1) {

is considered to be confusing due to the inverted search sometimes disguising what’s going on. It’s not so much of a problem here, but typically things are made clearer when you specify what you are searching for, instead of what you are not.

The following is the more accepted way to explicitly state what you need from the start to the end of the string.


if (rawtextareaString.search(/^[\\d\\.\\s\\+\\-]+$/m) === -1) {

The m flag after the pattern says that you’re wanting to work with multi-line strings.
The minus sign is also escaped, to prevent potential issues when lesser mortals who follow after you add more values to the character group, who may be unaware of how the minus sign changes meaning when it’s no longer the very last item of the character group.

parseInt’s should also specify the radix parameter, so that user entries of 08 or 09 for the matrix size don’t end up being mistakenly misunderstood.


k = parseInt(rawArray[0] ,10);

Further simplifications can occur though, by shifting parts out to separate functions and making good use of the returning false from the function when things aren’t right.

The tinareal function can then be reduced down to the following:


function tinareal(rawtextareaString, inPar, sqAMatrix) {
    var squareMatrix = createMatrix(rawtextareaString, sqAMatrix.length);

    if (!squareMatrix) {
        inPar.inEr = 1;
        return false;
    }
    
    inPar.mDim = squareMatrix.length;
    sqAMatrix = squareMatrix; // there's a problem here though
}

Well, normally it would be able to be reduced down to the above, but the difficulty here is that the sqAMatrix variable must remain at the same dimensions that it was originally created as, that being as 12x12 array, so it’s not possible to create an array of a custom size and assign that to sqAMatrix.

The other difficulty though is that it’s not really appropriate for the code that creates the array, to assign the result to a passed-by-reference array. Passing by reference results in difficult to understand code.

For example, in realEig.js, what’s happening at this particular piece of code?

tinareal(textareaString, inputPar, A);

It’s not possible to determine that both inputPar and A are being dramatically changed without investigating everything that occurs within the tinareal function itself. The inputPar and A variables are modified by reference at different stages of the function.

What should ultimately happen is for the tinareal function to return the matrix, and for the the realEig.js script to assign its values based on that response.

realEig.js


[s][color="red"]tinareal(textareaString, inputPar, A);[/color][/s]
[color="green"]A = tinareal(textareaString);
if (A) {
    inputPar.mDim = A.length;
} else {
    inputPar.inEr = 1[/color]

However, the compRealEigSys.js code would need to also be updated too, because currently it fails when trying to use an array size smaller than 12.

So until that can occur, the tinreal function must make it as explicit as practical that the passed in variables are being updated based on the newly created matrix.


function tinareal(rawtextareaString, inPar, sqAMatrix) {
    var squareMatrix = createMatrix(rawtextareaString, sqAMatrix.length),
        matrixSize = 0,
        i,
        j;
    if (!squareMatrix) {
        inPar.inEr = 1;
        return false;
    }

    matrixSize = squareMatrix.length;
    inPar.mDim = matrixSize;
    for (i = 0; i < matrixSize; i += 1) {
        for (j = 0; j < matrixSize; j += 1) {
            sqAMatrix[i][j] = squareMatrix[i][j];
        }
    }
}

The updated createMatrix simplifies the understand of things, by offloading the the conversion of the string in to an array to a separate function, as well as moving off the validation of the matrix size to a separate function too. This also helps to keep each function nice and small, so that the focus of each function is kept to a small set of responsibilities.


// Sub-routine for getting [A] matrix data from a textareabox:
// Assumes input string comes from a textarea box and that all entries represent real numbers.
function createMatrix(rawtextareaString, MAXDIM) {
    var rawArray = [],
        matrixSize = 0,
        squareMatrix = [],
        row = 0,
        col = 0,
        value = 0.0;

    rawArray = convertStringToArrayForMatrix(rawtextareaString, MAXDIM);
    if (!rawArray) {
        return false;
    }

    matrixSize = rawArray.shift();
    matrixSize = validateMatrixSize(matrixSize, MAXDIM, rawArray.length);
    if (!matrixSize) {
        return false;
    }

    // Input the elements of the A matrix: A[0][0], A[0][1], A[0][2], . . .  A[1][0], A[1][1], A[1][2], . . . etc.
    for (row = 0; row < matrixSize; row += 1) {
        squareMatrix[row] = [];
        for (col = 0; col < matrixSize; col += 1) {
            value = parseFloat(rawArray.shift());
            if (!isNaN(value)) {
                squareMatrix[row][col] = value;
            } else {
                window.alert("Invalid input for entry " + (row * matrixSize + col + 1) + ". No further action taken.");
                return false;
            }
        }
    }

    return squareMatrix;
}

The rawArray.shift() method removes a value from the start of the array, in much the same way that using .pop() would remove a value from the end of an array.

That just leaves us with the convertStringToArrayForMatrix and validateMatrixSize functions, which perform the bulk of the checking that occurs:



function convertStringToArrayForMatrix(rawtextareaString, MAXDIM) {
    var rawArray = [],
        maxEntries = (1 + MAXDIM * MAXDIM);

    if (rawtextareaString.length === 0) {
        window.alert("The length of textareaString is 0. No data has been entered. No further action taken.");
        return false;
    }

    // At this point, rawtextareaString is a big long string containing the entire field entered in the textarea box.
    // Must now clean up the data: remove leading and trailing spaces, identify the inputs, etc.

    // Check to see that string contains digits; if not, no point continuing
    if (rawtextareaString.search(/\\d/) === -1) {
        window.alert("textareaString does not contain any digits. No further action taken.");
        return false;
    }

    // Check to see that string contains only digits, decimal points, "+" or "-" sign, or white space; if not, no point continuing
    if (rawtextareaString.search(/^[\\d\\.\\s\\+\\-]+$/m) === -1) {
        window.alert("textareaString contains an invalid character. Please edit data so that it is in the appropriate format. No further action taken.");
        return false;
    }

    // Check to see that string contains newline characters;
    // if not, then there are not AT LEAST two entries, and the algorithm won't work--no point continuing
    if (rawtextareaString.search(/\
/) === -1) {
        window.alert("This utility requires at least two entries to be entered, but two entries have not been detected. Either fewer than two entries have been entered, or the data is not in the appropriate format. No further action taken.");
        return false;
    }

    // Do some rough clean up
    rawtextareaString = rawtextareaString.replace(/\\s*$/, ''); // Remove trailing whitespace
    rawtextareaString = rawtextareaString.replace(/^\\s*/, ''); // Remove leading whitespace

    // Divide the string up into its individual entries, which--presumably--are separated by whitespace
    rawArray = rawtextareaString.split(/\\s+/g);

    // Check to see if at least two entries are present
    if (rawArray.length < 2) {
        window.alert("This utility requires AT LEAST two entries to be entered, but fewer than two entries have been detected. No further action taken.");
        return false;
    }

    // A maximum of 145 entries may be entered (first entry for the matrix dimension, up to 144 for the a-coefficients).
    if (rawArray.length > maxEntries) {
        window.alert("This utility accepts input of up to " + maxEntries + " entries; however, more than " + maxEntries + " entries have been input. No further action taken.");
        return false;
    }

    return rawArray;
}



// Now check the individual data entries, and confirm they are valid numbers
function validateMatrixSize(matrixSize, MAXDIM, arrayLength) {
    var numCoeff = arrayLength;

    matrixSize = parseInt(matrixSize, 10);
    if (isNaN(matrixSize)) {
        window.alert("Invalid input for matrix dimension. No further action taken.");
        return false;
    }
    if (matrixSize > MAXDIM) {
        window.alert("This utility accepts matrices of dimension " + MAXDIM + " or less. However, a higher number has been input. No further action taken.");
        return false;
    }
    if (matrixSize <= 0) {
        window.alert("Negative values for the matrix dimension are not accepted. No further action taken.");
        return false;
    }
    if (numCoeff !== (matrixSize * matrixSize)) {
        window.alert("The number of coefficients entered does not correspond to the matrix dimension entered. Check the data. No further action taken.");
        return false;
    }
    return matrixSize;
}