-
Notifications
You must be signed in to change notification settings - Fork 20k
Add MatrixRank
#4571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
vil02
merged 12 commits into
TheAlgorithms:master
from
anupomkar:feature/matrix-rank-algo
Oct 25, 2023
Merged
Add MatrixRank
#4571
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
f8cf317
feat: adding matrix rank algorithm
0f17227
fix: formatting
32608b0
fix: adding comments, refactor and handling edge cases
691303e
Merge branch 'master' into feature/matrix-rank-algo
anupomkar 8e67dc3
refactor: minor refactor
b0bb7b0
Merge branch 'master' into feature/matrix-rank-algo
anupomkar 269f7b6
enhancement: check matrix validity
b1b6f6d
refactor: minor refactor and fixes
d5aea77
Merge branch 'master' into feature/matrix-rank-algo
anupomkar 3a997a9
Merge branch 'master' into feature/matrix-rank-algo
anupomkar 4ed756e
Update src/main/java/com/thealgorithms/maths/MatrixRank.java
siriak 34a1853
feat: add unit test to check if input matrix is not modified while ca…
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
package com.thealgorithms.maths; | ||
|
||
/** | ||
* This class provides a method to compute the rank of a matrix. | ||
* In linear algebra, the rank of a matrix is the maximum number of linearly independent rows or columns in the matrix. | ||
* For example, consider the following 3x3 matrix: | ||
* 1 2 3 | ||
* 2 4 6 | ||
* 3 6 9 | ||
* Despite having 3 rows and 3 columns, this matrix only has a rank of 1 because all rows (and columns) are multiples of each other. | ||
* It's a fundamental concept that gives key insights into the structure of the matrix. | ||
* It's important to note that the rank is not only defined for square matrices but for any m x n matrix. | ||
* | ||
* @author Anup Omkar | ||
*/ | ||
public final class MatrixRank { | ||
|
||
private MatrixRank() { | ||
} | ||
|
||
private static final double EPSILON = 1e-10; | ||
|
||
/** | ||
* @brief Computes the rank of the input matrix | ||
* | ||
* @param matrix The input matrix | ||
* @return The rank of the input matrix | ||
*/ | ||
public static int computeRank(double[][] matrix) { | ||
vil02 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
validateInputMatrix(matrix); | ||
|
||
int numRows = matrix.length; | ||
int numColumns = matrix[0].length; | ||
anupomkar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
int rank = 0; | ||
|
||
boolean[] rowMarked = new boolean[numRows]; | ||
|
||
double[][] matrixCopy = deepCopy(matrix); | ||
|
||
for (int colIndex = 0; colIndex < numColumns; ++colIndex) { | ||
int pivotRow = findPivotRow(matrixCopy, rowMarked, colIndex); | ||
if (pivotRow != numRows) { | ||
++rank; | ||
rowMarked[pivotRow] = true; | ||
normalizePivotRow(matrixCopy, pivotRow, colIndex); | ||
eliminateRows(matrixCopy, pivotRow, colIndex); | ||
} | ||
} | ||
return rank; | ||
} | ||
|
||
private static boolean isZero(double value) { | ||
return Math.abs(value) < EPSILON; | ||
} | ||
|
||
private static double[][] deepCopy(double[][] matrix) { | ||
int numRows = matrix.length; | ||
int numColumns = matrix[0].length; | ||
double[][] matrixCopy = new double[numRows][numColumns]; | ||
for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) { | ||
System.arraycopy(matrix[rowIndex], 0, matrixCopy[rowIndex], 0, numColumns); | ||
} | ||
return matrixCopy; | ||
} | ||
|
||
private static void validateInputMatrix(double[][] matrix) { | ||
if (matrix == null) { | ||
throw new IllegalArgumentException("The input matrix cannot be null"); | ||
} | ||
if (matrix.length == 0) { | ||
throw new IllegalArgumentException("The input matrix cannot be empty"); | ||
} | ||
if (!hasValidRows(matrix)) { | ||
throw new IllegalArgumentException("The input matrix cannot have null or empty rows"); | ||
} | ||
if (isJaggedMatrix(matrix)) { | ||
throw new IllegalArgumentException("The input matrix cannot be jagged"); | ||
} | ||
} | ||
|
||
private static boolean hasValidRows(double[][] matrix) { | ||
for (double[] row : matrix) { | ||
if (row == null || row.length == 0) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
/** | ||
* @brief Checks if the input matrix is a jagged matrix. | ||
* Jagged matrix is a matrix where the number of columns in each row is not the same. | ||
* | ||
* @param matrix The input matrix | ||
* @return True if the input matrix is a jagged matrix, false otherwise | ||
*/ | ||
private static boolean isJaggedMatrix(double[][] matrix) { | ||
int numColumns = matrix[0].length; | ||
for (double[] row : matrix) { | ||
if (row.length != numColumns) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* @brief The pivot row is the row in the matrix that is used to eliminate other rows and reduce the matrix to its row echelon form. | ||
* The pivot row is selected as the first row (from top to bottom) where the value in the current column (the pivot column) is not zero. | ||
* This row is then used to "eliminate" other rows, by subtracting multiples of the pivot row from them, so that all other entries in the pivot column become zero. | ||
* This process is repeated for each column, each time selecting a new pivot row, until the matrix is in row echelon form. | ||
* The number of pivot rows (rows with a leading entry, or pivot) then gives the rank of the matrix. | ||
* | ||
* @param matrix The input matrix | ||
* @param rowMarked An array indicating which rows have been marked | ||
* @param colIndex The column index | ||
* @return The pivot row index, or the number of rows if no suitable pivot row was found | ||
*/ | ||
private static int findPivotRow(double[][] matrix, boolean[] rowMarked, int colIndex) { | ||
int numRows = matrix.length; | ||
for (int pivotRow = 0; pivotRow < numRows; ++pivotRow) { | ||
if (!rowMarked[pivotRow] && !isZero(matrix[pivotRow][colIndex])) { | ||
return pivotRow; | ||
} | ||
} | ||
return numRows; | ||
} | ||
|
||
/** | ||
* @brief This method divides all values in the pivot row by the value in the given column. | ||
* This ensures that the pivot value itself will be 1, which simplifies further calculations. | ||
* | ||
* @param matrix The input matrix | ||
* @param pivotRow The pivot row index | ||
* @param colIndex The column index | ||
*/ | ||
private static void normalizePivotRow(double[][] matrix, int pivotRow, int colIndex) { | ||
int numColumns = matrix[0].length; | ||
for (int nextCol = colIndex + 1; nextCol < numColumns; ++nextCol) { | ||
matrix[pivotRow][nextCol] /= matrix[pivotRow][colIndex]; | ||
} | ||
} | ||
|
||
/** | ||
* @brief This method subtracts multiples of the pivot row from all other rows, | ||
* so that all values in the given column of other rows will be zero. | ||
* This is a key step in reducing the matrix to row echelon form. | ||
* | ||
* @param matrix The input matrix | ||
* @param pivotRow The pivot row index | ||
* @param colIndex The column index | ||
*/ | ||
private static void eliminateRows(double[][] matrix, int pivotRow, int colIndex) { | ||
int numRows = matrix.length; | ||
int numColumns = matrix[0].length; | ||
for (int otherRow = 0; otherRow < numRows; ++otherRow) { | ||
if (otherRow != pivotRow && !isZero(matrix[otherRow][colIndex])) { | ||
for (int col2 = colIndex + 1; col2 < numColumns; ++col2) { | ||
matrix[otherRow][col2] -= matrix[pivotRow][col2] * matrix[otherRow][colIndex]; | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package com.thealgorithms.maths; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
|
||
import java.util.Arrays; | ||
import java.util.stream.Stream; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.Arguments; | ||
import org.junit.jupiter.params.provider.MethodSource; | ||
|
||
class MatrixRankTest { | ||
|
||
private static Stream<Arguments> validInputStream() { | ||
return Stream.of(Arguments.of(3, new double[][] {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}), Arguments.of(0, new double[][] {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}), Arguments.of(1, new double[][] {{1}}), Arguments.of(2, new double[][] {{1, 2}, {3, 4}}), | ||
Arguments.of(2, new double[][] {{3, -1, 2}, {-3, 1, 2}, {-6, 2, 4}}), Arguments.of(3, new double[][] {{2, 3, 0, 1}, {1, 0, 1, 2}, {-1, 1, 1, -2}, {1, 5, 3, -1}}), Arguments.of(1, new double[][] {{1, 2, 3}, {3, 6, 9}}), | ||
Arguments.of(2, new double[][] {{0.25, 0.5, 0.75, 2}, {1.5, 3, 4.5, 6}, {1, 2, 3, 4}})); | ||
} | ||
|
||
private static Stream<Arguments> invalidInputStream() { | ||
return Stream.of(Arguments.of((Object) new double[][] {{1, 2}, {10}, {100, 200, 300}}), // jagged array | ||
Arguments.of((Object) new double[][] {}), // empty matrix | ||
Arguments.of((Object) new double[][] {{}, {}}), // empty row | ||
Arguments.of((Object) null), // null matrix | ||
Arguments.of((Object) new double[][] {{1, 2}, null}) // null row | ||
); | ||
} | ||
|
||
@ParameterizedTest | ||
@MethodSource("validInputStream") | ||
void computeRankTests(int expectedRank, double[][] matrix) { | ||
int originalHashCode = Arrays.deepHashCode(matrix); | ||
int rank = MatrixRank.computeRank(matrix); | ||
int newHashCode = Arrays.deepHashCode(matrix); | ||
|
||
assertEquals(expectedRank, rank); | ||
assertEquals(originalHashCode, newHashCode); | ||
} | ||
|
||
@ParameterizedTest | ||
@MethodSource("invalidInputStream") | ||
void computeRankWithInvalidMatrix(double[][] matrix) { | ||
assertThrows(IllegalArgumentException.class, () -> MatrixRank.computeRank(matrix)); | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.