From f98b585c6254584bf5cccb405fd4a0221a873014 Mon Sep 17 00:00:00 2001 From: Piyush Katyal <109459034+piyushk77@users.noreply.github.com> Date: Fri, 6 Oct 2023 05:39:51 +0530 Subject: [PATCH 1/6] feat: add row echelon matrix algorithm --- Maths/RowEchelon.js | 138 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 Maths/RowEchelon.js diff --git a/Maths/RowEchelon.js b/Maths/RowEchelon.js new file mode 100644 index 0000000000..6203edbaf3 --- /dev/null +++ b/Maths/RowEchelon.js @@ -0,0 +1,138 @@ +/** + * Given a two dimensional matrix, find its row echelon form. + * + * For more info: https://en.wikipedia.org/wiki/Row_echelon_form + * + * @param {number[[]]} matrix - Two dimensional array of rational numbers. + * @returns {number[[]]} - Two dimensional array of rational numbers (row echelon form). + * + * @example + * const matrix = [ + * [2,3,4,5,7], + * [9,8,4,0,9], + * [5,7,4,3,9], + * [3,4,0,2,1] + * ] + * + * const result = rowEchelon(matrix) + * + * // The function returns the corresponding row echelon form: + * // result: + * // [ + * // [1, 1.5, 2, 2.5, 3.5], + * // [0, 1, 2.54545, 4.09091, 4.09091], + * // [0, 0, 1, 1.57692, 1.36539], + * // [0, 0, 0, 1, -0.25] + * // ] + */ + +const isMatrixValid = (matrix) => { + let numRows = matrix.length + let numCols = matrix[0].length + for (let i = 0; i < numRows; i++) { + if (numCols !== matrix[i].length) { + return false + } + } + if ( + !Array.isArray(matrix) || + matrix.length === 0 || + !Array.isArray(matrix[0]) + ) { + return false + } + return true +} + +const checkNonZero = (currentRow, currentCol, matrix) => { + let numRows = matrix.length + for (let i = currentRow; i < numRows; i++) { + if (matrix[i][currentCol] !== 0) { + return true + } + } + return false +} + +const swapRows = (currentRow, withRow, matrix) => { + let numCols = matrix[0].length + let tempValue = 0 + for (let j = 0; j < numCols; j++) { + tempValue = matrix[currentRow][j] + matrix[currentRow][j] = matrix[withRow][j] + matrix[withRow][j] = tempValue + } +} + +const selectPivot = (currentRow, currentCol, matrix) => { + let numRows = matrix.length + for (let i = currentRow; i < numRows; i++) { + if (matrix[i][currentCol] !== 0) { + swapRows(currentRow, i, matrix) + return + } + } +} + +const scalarMultiplication = (currentRow, factor, matrix) => { + let numCols = matrix[0].length + for (let j = 0; j < numCols; j++) { + matrix[currentRow][j] *= factor + } +} + +const subtractRow = (currentRow, fromRow, matrix) => { + let numCols = matrix[0].length + for (let j = 0; j < numCols; j++) { + matrix[fromRow][j] -= matrix[currentRow][j] + } +} + +const formatResult = (matrix) => { + let precision = 5 + let numRows = matrix.length + let numCols = matrix[0].length + for (let i = 0; i < numRows; i++) { + for (let j = 0; j < numCols; j++) { + matrix[i][j] = parseFloat(matrix[i][j].toFixed(precision)) + } + } +} + +const rowEchelon = (matrix) => { + if (isMatrixValid(matrix) === false) { + return 'Input is not a valid 2D matrix.' + } + + let numRows = matrix.length + let numCols = matrix[0].length + let result = matrix + + for (let i = 0, j = 0; i < numRows && j < numCols; ) { + if (checkNonZero(i, j, result) === false) { + j++ + continue + } + + selectPivot(i, j, result) + let factor = 1 / result[i][j] + scalarMultiplication(i, factor, result) + + //..............make bottom elements zero............... + for (let x = i + 1; x < numRows; x++) { + factor = result[x][j] + if (factor === 0) { + continue + } + scalarMultiplication(i, factor, result) + subtractRow(i, x, result) + factor = 1 / factor + scalarMultiplication(i, factor, result) + } + formatResult(result) + i++ + } + return result +} + +export { rowEchelon } From d9e7ffa07ae1a0d0f66f71f52e04d899f98d7a04 Mon Sep 17 00:00:00 2001 From: Piyush Katyal <109459034+piyushk77@users.noreply.github.com> Date: Fri, 6 Oct 2023 05:45:25 +0530 Subject: [PATCH 2/6] test: add self-tests for row echelon algorithm --- Maths/test/RowEchelon.test.js | 84 +++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 Maths/test/RowEchelon.test.js diff --git a/Maths/test/RowEchelon.test.js b/Maths/test/RowEchelon.test.js new file mode 100644 index 0000000000..b3e2e1803c --- /dev/null +++ b/Maths/test/RowEchelon.test.js @@ -0,0 +1,84 @@ +import { rowEchelon } from '../RowEchelon' +describe('Determinant', () => { + const testCases = [ + [ + [ + [8, 1, 3, 5], + [4, 6, 8, 2], + [3, 5, 6, 8] + ], + [ + [1, 0.125, 0.375, 0.625], + [0, 1, 1.18182, -0.09091], + [0, 0, 1, -11.0769] + ] + ], + [ + [ + [6, 8, 1, 3, 5], + [1, 4, 6, 8, 2], + [0, 3, 5, 6, 8], + [2, 5, 9, 7, 8], + [5, 5, 7, 0, 1] + ], + [ + [1, 1.33333, 0.16667, 0.5, 0.83333], + [0, 1, 2.1875, 2.8125, 0.4375], + [0, 0, 1, 1.56, -4.28003], + [0, 0, 0, 1, -3.3595], + [0, 0, 0, 0, 1] + ] + ], + [ + [ + [1, 3, 5], + [6, 8, 2], + [5, 6, 8], + [7, 9, 9], + [5, 0, 6] + ], + [ + [1, 3, 5], + [0, 1, 2.8], + [0, 0, 1], + [0, 0, 0], + [0, 0, 0] + ] + ], + [ + [ + [8, 1, 3, 5], + [4, 6, 8, 2, 7], + [3, 5, 6, 8] + ], + 'Input is not a valid 2D matrix.' + ], + [ + [ + [0, 7, 8, 1, 3, 5], + [0, 6, 4, 6, 8, 2], + [0, 7, 3, 5, 6, 8], + [6, 8, 1, 0, 0, 4], + [3, 3, 5, 7, 3, 1], + [1, 2, 1, 0, 9, 7], + [8, 8, 0, 2, 3, 1] + ], + [ + [1, 1.33333, 0.16667, 0, 0, 0.66667], + [0, 1, 0.66667, 1, 1.33333, 0.33333], + [0, 0, 1, 1.2, 1.99999, -3.4], + [0, 0, 0, 1, 1.3, -1.4], + [0, 0, 0, 0, 1, -2.32854], + [0, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 0, 0] + ] + ] + ] + + test.each(testCases)( + 'Should return the matrix in row echelon form.', + (matrix, expected) => { + expect(rowEchelon(matrix)).toEqual(expected) + } + ) +}) From 5a7fb8792085a1b816ca32c197ff7fa4ac1bb93a Mon Sep 17 00:00:00 2001 From: Piyush Katyal <109459034+piyushk77@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:21:22 +0530 Subject: [PATCH 3/6] fix: replace rounding with float tolerance --- Maths/RowEchelon.js | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/Maths/RowEchelon.js b/Maths/RowEchelon.js index 6203edbaf3..9efefc0d5c 100644 --- a/Maths/RowEchelon.js +++ b/Maths/RowEchelon.js @@ -26,6 +26,8 @@ * // ] */ +const tolerance = 0.000001 + const isMatrixValid = (matrix) => { let numRows = matrix.length let numCols = matrix[0].length @@ -47,7 +49,7 @@ const isMatrixValid = (matrix) => { const checkNonZero = (currentRow, currentCol, matrix) => { let numRows = matrix.length for (let i = currentRow; i < numRows; i++) { - if (matrix[i][currentCol] !== 0) { + if (!isTolerant(0,matrix[i][currentCol], tolerance)) { return true } } @@ -88,20 +90,14 @@ const subtractRow = (currentRow, fromRow, matrix) => { } } -const formatResult = (matrix) => { - let precision = 5 - let numRows = matrix.length - let numCols = matrix[0].length - for (let i = 0; i < numRows; i++) { - for (let j = 0; j < numCols; j++) { - matrix[i][j] = parseFloat(matrix[i][j].toFixed(precision)) - } - } +const isTolerant = (a, b, tolerance) => { + const absoluteDifference = Math.abs(a - b); + return (absoluteDifference <= tolerance); } const rowEchelon = (matrix) => { - if (isMatrixValid(matrix) === false) { - return 'Input is not a valid 2D matrix.' + if (!isMatrixValid(matrix)) { + throw new Error('Input is not a valid 2D matrix.') } let numRows = matrix.length @@ -109,7 +105,7 @@ const rowEchelon = (matrix) => { let result = matrix for (let i = 0, j = 0; i < numRows && j < numCols; ) { - if (checkNonZero(i, j, result) === false) { + if (!checkNonZero(i, j, result)) { j++ continue } @@ -121,7 +117,7 @@ const rowEchelon = (matrix) => { //..............make bottom elements zero............... for (let x = i + 1; x < numRows; x++) { factor = result[x][j] - if (factor === 0) { + if (isTolerant(0,factor,tolerance)) { continue } scalarMultiplication(i, factor, result) @@ -129,7 +125,6 @@ const rowEchelon = (matrix) => { factor = 1 / factor scalarMultiplication(i, factor, result) } - formatResult(result) i++ } return result From 64d1afc58064a9d7e58b8f112559d351085ec51e Mon Sep 17 00:00:00 2001 From: Piyush Katyal <109459034+piyushk77@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:23:55 +0530 Subject: [PATCH 4/6] chore: use correct style --- Maths/RowEchelon.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Maths/RowEchelon.js b/Maths/RowEchelon.js index 9efefc0d5c..478c98db68 100644 --- a/Maths/RowEchelon.js +++ b/Maths/RowEchelon.js @@ -49,7 +49,7 @@ const isMatrixValid = (matrix) => { const checkNonZero = (currentRow, currentCol, matrix) => { let numRows = matrix.length for (let i = currentRow; i < numRows; i++) { - if (!isTolerant(0,matrix[i][currentCol], tolerance)) { + if (!isTolerant(0, matrix[i][currentCol], tolerance)) { return true } } @@ -91,8 +91,8 @@ const subtractRow = (currentRow, fromRow, matrix) => { } const isTolerant = (a, b, tolerance) => { - const absoluteDifference = Math.abs(a - b); - return (absoluteDifference <= tolerance); + const absoluteDifference = Math.abs(a - b) + return absoluteDifference <= tolerance } const rowEchelon = (matrix) => { @@ -117,7 +117,7 @@ const rowEchelon = (matrix) => { //..............make bottom elements zero............... for (let x = i + 1; x < numRows; x++) { factor = result[x][j] - if (isTolerant(0,factor,tolerance)) { + if (isTolerant(0, factor, tolerance)) { continue } scalarMultiplication(i, factor, result) From 2200c0e74e1a02bec53c5e8605f6e27a3d64d15e Mon Sep 17 00:00:00 2001 From: Piyush Katyal <109459034+piyushk77@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:27:11 +0530 Subject: [PATCH 5/6] fix: use error tolerance and segregate testcases --- Maths/test/RowEchelon.test.js | 37 ++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/Maths/test/RowEchelon.test.js b/Maths/test/RowEchelon.test.js index b3e2e1803c..5575bc6d39 100644 --- a/Maths/test/RowEchelon.test.js +++ b/Maths/test/RowEchelon.test.js @@ -1,6 +1,7 @@ import { rowEchelon } from '../RowEchelon' describe('Determinant', () => { - const testCases = [ + const tolerance = 0.000001 + test.each([ [ [ [8, 1, 3, 5], @@ -45,14 +46,6 @@ describe('Determinant', () => { [0, 0, 0] ] ], - [ - [ - [8, 1, 3, 5], - [4, 6, 8, 2, 7], - [3, 5, 6, 8] - ], - 'Input is not a valid 2D matrix.' - ], [ [ [0, 7, 8, 1, 3, 5], @@ -73,12 +66,24 @@ describe('Determinant', () => { [0, 0, 0, 0, 0, 0] ] ] - ] - - test.each(testCases)( - 'Should return the matrix in row echelon form.', - (matrix, expected) => { - expect(rowEchelon(matrix)).toEqual(expected) + ])('Should return the matrix in row echelon form.', (matrix, expected) => { + for (let i = 0; i < matrix.length; i++) { + for (let j = 0; j < matrix[i].length; j++) { + expect(rowEchelon(matrix)[i][j]).toBeCloseTo(expected[i][j], tolerance) + } } - ) + }) + + test.each([ + [ + [ + [8, 1, 3, 5], + [4, 6, 8, 2, 7], + [3, 5, 6, 8] + ], + 'Input is not a valid 2D matrix.' + ] + ])('Should return the error message.', (matrix, expected) => { + expect(() => rowEchelon(matrix)).toThrowError(expected) + }) }) From 39fed98097a0b5b44e25abbf22f688a3e6ba1ef1 Mon Sep 17 00:00:00 2001 From: Piyush Katyal <109459034+piyushk77@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:52:17 +0530 Subject: [PATCH 6/6] chore: add necessary explaining comments --- Maths/RowEchelon.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Maths/RowEchelon.js b/Maths/RowEchelon.js index 478c98db68..c773bb80a9 100644 --- a/Maths/RowEchelon.js +++ b/Maths/RowEchelon.js @@ -26,8 +26,10 @@ * // ] */ +// Set a tolerance value for floating-point comparisons const tolerance = 0.000001 +// Check if all the rows have same length of elements const isMatrixValid = (matrix) => { let numRows = matrix.length let numCols = matrix[0].length @@ -36,6 +38,8 @@ const isMatrixValid = (matrix) => { return false } } + + // Check for input other than a 2D matrix if ( !Array.isArray(matrix) || matrix.length === 0 || @@ -49,6 +53,7 @@ const isMatrixValid = (matrix) => { const checkNonZero = (currentRow, currentCol, matrix) => { let numRows = matrix.length for (let i = currentRow; i < numRows; i++) { + // Checks if the current element is not very near to zero. if (!isTolerant(0, matrix[i][currentCol], tolerance)) { return true } @@ -66,6 +71,9 @@ const swapRows = (currentRow, withRow, matrix) => { } } +// Select a pivot element in the current column to facilitate row operations. +// Pivot element is the first non-zero element found from the current row +// down to the last row. const selectPivot = (currentRow, currentCol, matrix) => { let numRows = matrix.length for (let i = currentRow; i < numRows; i++) { @@ -76,6 +84,7 @@ const selectPivot = (currentRow, currentCol, matrix) => { } } +// Multiply each element of the given row with a factor. const scalarMultiplication = (currentRow, factor, matrix) => { let numCols = matrix[0].length for (let j = 0; j < numCols; j++) { @@ -83,6 +92,7 @@ const scalarMultiplication = (currentRow, factor, matrix) => { } } +// Subtract one row from another row const subtractRow = (currentRow, fromRow, matrix) => { let numCols = matrix[0].length for (let j = 0; j < numCols; j++) { @@ -90,12 +100,14 @@ const subtractRow = (currentRow, fromRow, matrix) => { } } +// Check if two numbers are equal within a given tolerance const isTolerant = (a, b, tolerance) => { const absoluteDifference = Math.abs(a - b) return absoluteDifference <= tolerance } const rowEchelon = (matrix) => { + // Check if the input matrix is valid; if not, throw an error. if (!isMatrixValid(matrix)) { throw new Error('Input is not a valid 2D matrix.') } @@ -104,17 +116,22 @@ const rowEchelon = (matrix) => { let numCols = matrix[0].length let result = matrix + // Iterate through the rows (i) and columns (j) of the matrix. for (let i = 0, j = 0; i < numRows && j < numCols; ) { + // If the current column has all zero elements below the current row, + // move to the next column. if (!checkNonZero(i, j, result)) { j++ continue } + // Select a pivot element and normalize the current row. selectPivot(i, j, result) let factor = 1 / result[i][j] scalarMultiplication(i, factor, result) - //..............make bottom elements zero............... + // Make elements below the pivot element zero by performing + // row operations on subsequent rows. for (let x = i + 1; x < numRows; x++) { factor = result[x][j] if (isTolerant(0, factor, tolerance)) {