Skip to content

Commit 3823ede

Browse files
authored
feat: add row echelon matrix algorithm (TheAlgorithms#1454)
* feat: add row echelon matrix algorithm * test: add self-tests for row echelon algorithm * fix: replace rounding with float tolerance * chore: use correct style * fix: use error tolerance and segregate testcases * chore: add necessary explaining comments
1 parent a24450a commit 3823ede

File tree

2 files changed

+239
-0
lines changed

2 files changed

+239
-0
lines changed

Maths/RowEchelon.js

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/**
2+
* Given a two dimensional matrix, find its row echelon form.
3+
*
4+
* For more info: https://en.wikipedia.org/wiki/Row_echelon_form
5+
*
6+
* @param {number[[]]} matrix - Two dimensional array of rational numbers.
7+
* @returns {number[[]]} - Two dimensional array of rational numbers (row echelon form).
8+
*
9+
* @example
10+
* const matrix = [
11+
* [2,3,4,5,7],
12+
* [9,8,4,0,9],
13+
* [5,7,4,3,9],
14+
* [3,4,0,2,1]
15+
* ]
16+
*
17+
* const result = rowEchelon(matrix)
18+
*
19+
* // The function returns the corresponding row echelon form:
20+
* // result:
21+
* // [
22+
* // [1, 1.5, 2, 2.5, 3.5],
23+
* // [0, 1, 2.54545, 4.09091, 4.09091],
24+
* // [0, 0, 1, 1.57692, 1.36539],
25+
* // [0, 0, 0, 1, -0.25]
26+
* // ]
27+
*/
28+
29+
// Set a tolerance value for floating-point comparisons
30+
const tolerance = 0.000001
31+
32+
// Check if all the rows have same length of elements
33+
const isMatrixValid = (matrix) => {
34+
let numRows = matrix.length
35+
let numCols = matrix[0].length
36+
for (let i = 0; i < numRows; i++) {
37+
if (numCols !== matrix[i].length) {
38+
return false
39+
}
40+
}
41+
42+
// Check for input other than a 2D matrix
43+
if (
44+
!Array.isArray(matrix) ||
45+
matrix.length === 0 ||
46+
!Array.isArray(matrix[0])
47+
) {
48+
return false
49+
}
50+
return true
51+
}
52+
53+
const checkNonZero = (currentRow, currentCol, matrix) => {
54+
let numRows = matrix.length
55+
for (let i = currentRow; i < numRows; i++) {
56+
// Checks if the current element is not very near to zero.
57+
if (!isTolerant(0, matrix[i][currentCol], tolerance)) {
58+
return true
59+
}
60+
}
61+
return false
62+
}
63+
64+
const swapRows = (currentRow, withRow, matrix) => {
65+
let numCols = matrix[0].length
66+
let tempValue = 0
67+
for (let j = 0; j < numCols; j++) {
68+
tempValue = matrix[currentRow][j]
69+
matrix[currentRow][j] = matrix[withRow][j]
70+
matrix[withRow][j] = tempValue
71+
}
72+
}
73+
74+
// Select a pivot element in the current column to facilitate row operations.
75+
// Pivot element is the first non-zero element found from the current row
76+
// down to the last row.
77+
const selectPivot = (currentRow, currentCol, matrix) => {
78+
let numRows = matrix.length
79+
for (let i = currentRow; i < numRows; i++) {
80+
if (matrix[i][currentCol] !== 0) {
81+
swapRows(currentRow, i, matrix)
82+
return
83+
}
84+
}
85+
}
86+
87+
// Multiply each element of the given row with a factor.
88+
const scalarMultiplication = (currentRow, factor, matrix) => {
89+
let numCols = matrix[0].length
90+
for (let j = 0; j < numCols; j++) {
91+
matrix[currentRow][j] *= factor
92+
}
93+
}
94+
95+
// Subtract one row from another row
96+
const subtractRow = (currentRow, fromRow, matrix) => {
97+
let numCols = matrix[0].length
98+
for (let j = 0; j < numCols; j++) {
99+
matrix[fromRow][j] -= matrix[currentRow][j]
100+
}
101+
}
102+
103+
// Check if two numbers are equal within a given tolerance
104+
const isTolerant = (a, b, tolerance) => {
105+
const absoluteDifference = Math.abs(a - b)
106+
return absoluteDifference <= tolerance
107+
}
108+
109+
const rowEchelon = (matrix) => {
110+
// Check if the input matrix is valid; if not, throw an error.
111+
if (!isMatrixValid(matrix)) {
112+
throw new Error('Input is not a valid 2D matrix.')
113+
}
114+
115+
let numRows = matrix.length
116+
let numCols = matrix[0].length
117+
let result = matrix
118+
119+
// Iterate through the rows (i) and columns (j) of the matrix.
120+
for (let i = 0, j = 0; i < numRows && j < numCols; ) {
121+
// If the current column has all zero elements below the current row,
122+
// move to the next column.
123+
if (!checkNonZero(i, j, result)) {
124+
j++
125+
continue
126+
}
127+
128+
// Select a pivot element and normalize the current row.
129+
selectPivot(i, j, result)
130+
let factor = 1 / result[i][j]
131+
scalarMultiplication(i, factor, result)
132+
133+
// Make elements below the pivot element zero by performing
134+
// row operations on subsequent rows.
135+
for (let x = i + 1; x < numRows; x++) {
136+
factor = result[x][j]
137+
if (isTolerant(0, factor, tolerance)) {
138+
continue
139+
}
140+
scalarMultiplication(i, factor, result)
141+
subtractRow(i, x, result)
142+
factor = 1 / factor
143+
scalarMultiplication(i, factor, result)
144+
}
145+
i++
146+
}
147+
return result
148+
}
149+
150+
export { rowEchelon }

Maths/test/RowEchelon.test.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { rowEchelon } from '../RowEchelon'
2+
describe('Determinant', () => {
3+
const tolerance = 0.000001
4+
test.each([
5+
[
6+
[
7+
[8, 1, 3, 5],
8+
[4, 6, 8, 2],
9+
[3, 5, 6, 8]
10+
],
11+
[
12+
[1, 0.125, 0.375, 0.625],
13+
[0, 1, 1.18182, -0.09091],
14+
[0, 0, 1, -11.0769]
15+
]
16+
],
17+
[
18+
[
19+
[6, 8, 1, 3, 5],
20+
[1, 4, 6, 8, 2],
21+
[0, 3, 5, 6, 8],
22+
[2, 5, 9, 7, 8],
23+
[5, 5, 7, 0, 1]
24+
],
25+
[
26+
[1, 1.33333, 0.16667, 0.5, 0.83333],
27+
[0, 1, 2.1875, 2.8125, 0.4375],
28+
[0, 0, 1, 1.56, -4.28003],
29+
[0, 0, 0, 1, -3.3595],
30+
[0, 0, 0, 0, 1]
31+
]
32+
],
33+
[
34+
[
35+
[1, 3, 5],
36+
[6, 8, 2],
37+
[5, 6, 8],
38+
[7, 9, 9],
39+
[5, 0, 6]
40+
],
41+
[
42+
[1, 3, 5],
43+
[0, 1, 2.8],
44+
[0, 0, 1],
45+
[0, 0, 0],
46+
[0, 0, 0]
47+
]
48+
],
49+
[
50+
[
51+
[0, 7, 8, 1, 3, 5],
52+
[0, 6, 4, 6, 8, 2],
53+
[0, 7, 3, 5, 6, 8],
54+
[6, 8, 1, 0, 0, 4],
55+
[3, 3, 5, 7, 3, 1],
56+
[1, 2, 1, 0, 9, 7],
57+
[8, 8, 0, 2, 3, 1]
58+
],
59+
[
60+
[1, 1.33333, 0.16667, 0, 0, 0.66667],
61+
[0, 1, 0.66667, 1, 1.33333, 0.33333],
62+
[0, 0, 1, 1.2, 1.99999, -3.4],
63+
[0, 0, 0, 1, 1.3, -1.4],
64+
[0, 0, 0, 0, 1, -2.32854],
65+
[0, 0, 0, 0, 0, 1],
66+
[0, 0, 0, 0, 0, 0]
67+
]
68+
]
69+
])('Should return the matrix in row echelon form.', (matrix, expected) => {
70+
for (let i = 0; i < matrix.length; i++) {
71+
for (let j = 0; j < matrix[i].length; j++) {
72+
expect(rowEchelon(matrix)[i][j]).toBeCloseTo(expected[i][j], tolerance)
73+
}
74+
}
75+
})
76+
77+
test.each([
78+
[
79+
[
80+
[8, 1, 3, 5],
81+
[4, 6, 8, 2, 7],
82+
[3, 5, 6, 8]
83+
],
84+
'Input is not a valid 2D matrix.'
85+
]
86+
])('Should return the error message.', (matrix, expected) => {
87+
expect(() => rowEchelon(matrix)).toThrowError(expected)
88+
})
89+
})

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy