Reputation: 37
I was set with a task to perform matrix multiplication using file input. The actual math for the multiplication process is no issue; it's the storing of data into two-dimensional arrays that left me puzzled.
This is the data file that I have to use to create my two-dimensional arrays:
matrix
row
1
2
-2
0
row
-3
4
7
2
row
6
0
3
1
matrix
row
-1
3
row
0
9
row
1
-11
row
4
-5
The rules are quite simple: The start of a new matrix will be indicated with "matrix" and the start of a new row will be indicated with "row" followed by the numbers assigned to each column of that row.
For context, here is my matrix multiplication method:
static int[][] mult(int[][] a, int[][] b) {
int aRow = a.length;
int aCol = a[0].length;
int bRow = b.length;
int bCol = b[0].length;
if (bRow != aCol) {
throw new IllegalArgumentException("Matrix A is not multipliable by Matrix B");
}
int[][] product = new int[aRow][bCol];
for (int i = 0; i < product.length; i++) {
for (int j = 0; j < product[i].length; j++) {
for (int k = 0; k < aCol; k++) {
product[i][j] += a[i][k] * b[k][j];
}
}
}
return product;
}
And here is the class with the main method where I'm trying to store data from the above text file into two-dimensional arrays (trying to store the first matrix into the 2d array named "a" and the second matrix into the 2d array named "b"):
public static void main(String[] args) throws FileNotFoundException {
Scanner scanner = new Scanner(new File("/Users/Krish/IdeaProjects/Lessons/src/Lesson34/MatrixData.txt"));
String text[] = new String[100];
int index = -1;
while (scanner.hasNext()) {
text[++index] = scanner.nextLine();
}
int[][] a = {{}};
int[][] b = {{}};
int[][] product = MatrixMult.mult(a, b);
for (int i = 0; i < product.length; i++) {
for (int j = 0; j < product[i].length; j++) {
System.out.print(product[i][j] + "\t");
}
System.out.println();
}
scanner.close();
}
I know I have to do something like the following, but honestly, I have no idea how, and would really appreciate some help/guidance:
for (int i = 0; i <= index; i++) {
Scanner line = new Scanner(text[i]);
int n = 0;
while (line.hasNextInt()) {
n = line.nextInt();
for (int j = 0; j < a.length; j++) {
for (int k = 0; k < a[j].length; k++) {
a[j][k] = n;
}
}
}
}
Upvotes: 1
Views: 1002
Reputation: 168
I suggest a solution without for loops, and full input validation. instead of loops, you can use streams of java 8 The validation phase includes: regex matching, size & dimensions check.
The solution includes the following steps:
- Reading input matrices.
- Validating matrices.
- Converting the input into 3-d int array, with 2 cells.
(Each cell contains a 2-d int array matrix)
- Multiplying the matrices.
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.stream.IntStream;
import org.apache.commons.io.FileUtils;
/**
* This class demonstrates multiplication of 2 matrices.
* Including:
* - Reading input matrices from file.
* - Validating matrices input format (using Regex).
* - Converting input to 3-d array with 2 matrices (using streams).
* - Validating matrices sizes & dimensions.
* - multiplication of matrices (using streams).
*/
public class CreateTwo2dArraysFromATextFile {
final private static String FILE_PATH = "matrices.txt";
final private static String ENCODING = "UTF-8";
final private static String INPUT_FORMAT = "^(-?\\s*matrix\\s*(-?\\s+row(\\s+-?(\\d+))+)+){2}$";
final private static String MATRIX_TITLE = "matrix";
final private static String ROW_TITLE = "row";
final private static String MATRIX_DELIMITER = "\r\n";
final private static String ROW_DELIMITER = "\r\n";
public static void main(String[] args) throws IOException {
int[][][] matrices = fetchMatrices();
validateMatrices(matrices[0], matrices[1]);
displayMatrices(matrices);
displayMatricesMultiplicationResult(matrices[0], matrices[1]);
}
/**
* - Read 2 matrices from input file
* - Validate input format
* - Extract 2 matrices from the input file
* @return 2 matrices in 3-d int array format
* @throws IOException
*/
private static int[][][] fetchMatrices() throws IOException{
String input = FileUtils.readFileToString(new File(getFile(FILE_PATH)), ENCODING);
validateInputFormat(input);
System.out.println("Input from " + FILE_PATH);
System.out.println(input);
return getMatrices(input);
}
private static void validateMatrices(int[][] m1, int[][] m2) {
StringBuilder errors = collectInputErrors(m1, m2);
if(errors != null) {
throw new RuntimeException(errors.append("\nCannot multiply matrices, becuase the input is invalid").toString());
}
}
private static void displayMatrices(int[][][] matrices) {
System.out.println("\nMatrices in 3-d int array format:");
System.out.println(Arrays.deepToString(matrices));
}
private static void displayMatricesMultiplicationResult(int[][] m1, int[][] m2) {
System.out.println("\nMatrices Multiplication result:");
int[][] multResult = multiplyMatrices(m1, m2);
System.out.println(Arrays.deepToString(multResult));
}
private static String getFile(String fileName){
return Thread.currentThread().getContextClassLoader().getResource(fileName).getPath();
}
private static boolean isValidInput(String input) {
return input != null && input.matches(INPUT_FORMAT);
}
private static void validateInputFormat(String input) {
if(!isValidInput(input)) {
throw new RuntimeException("Invalid input format: " + input);
}
}
/**
* Attempt to detect the following validation errors:
* - The number of columns in m1 or m2 is not identical across all of the rows
* (There is at least one row with number of columns, which is different than the number of columns of all of the rows)
* - Matrices multiplication size constraints: the number of columns in m1, must be equals to the number of rows in m2.
* @param m1 first matrix
* @param m2 second matrix
* @return error messages if validation violations are detected.
* Otherwise, null will be retrieved.
*/
private static StringBuilder collectInputErrors(int[][] m1, int[][] m2) {
StringBuilder errors = new StringBuilder();
int invalidSizeRowIndex1 = getInValidSizeMatrixRowIndex(m1);
int invalidSizeRowIndex2 = getInValidSizeMatrixRowIndex(m2);
if(invalidSizeRowIndex1 != -1 || invalidSizeRowIndex2 != -1) {
errors.append("Invalid matrices size detected:");
}
if(invalidSizeRowIndex1 != -1) {
errors.append(getInvalidMatrixMessage(
"first",invalidSizeRowIndex1 + 1,
m1[invalidSizeRowIndex1].length, m1[invalidSizeRowIndex1 - 1].length));
}
if(invalidSizeRowIndex2 != -1) {
errors.append(getInvalidMatrixMessage(
"second",invalidSizeRowIndex2 + 1,
m2[invalidSizeRowIndex2].length, m2[invalidSizeRowIndex2 - 1].length));
}
int invalidDimensionRowIndex = getDimensionViolationIndex(m1, m2);
if(invalidSizeRowIndex1 == -1 && invalidSizeRowIndex2 == -1 && invalidDimensionRowIndex == -1) {
return null;
}
if(invalidDimensionRowIndex != -1 ) {
errors.append("\nInvalid matrices dimensions detected:");
errors.append(getInvalidMatrixMessage(
"first",invalidDimensionRowIndex + 1,
m1[invalidDimensionRowIndex].length, m2.length));
}
return errors;
}
private static String getInvalidMatrixMessage(String matrixTitle, int invalidRowIndex, int columnSize, int expectedColumnSize) {
return String.format("In the %s matrix, at the %d 'th row, a column with size of %d , is invalid. (expected column size is: %d)",
matrixTitle, invalidRowIndex, columnSize, expectedColumnSize);
}
/**
* Get the index of the first row in m1, that violates the matrices multiplication size constraints
* Matrix multiplication is possible iff the number of columns in m1 equals to the number of rows in m2.
* @param m1 first matrix
* @param m2 second matrix
* @return the first row index in m1 with column size
* which is different than the number of rows in m2.
* If there is no such row, then (-1) will be retrieved.
*
*/
private static int getDimensionViolationIndex(int[][] m1, int[][] m2) {
return IntStream.range(0, m1.length).filter(i -> m1[i].length != m2.length).findFirst().orElse(-1);
}
/**
* Get the index of the first row with invalid columns size (If exist)
* @param m matrix
* @return the first index of row,
* which has number of columns that is different than the previous row.
* If there is no such row, then (-1) will be retrieved.
*/
private static int getInValidSizeMatrixRowIndex(int[][] m) {
return IntStream.range(1, m.length).filter(i -> m[i].length != m[i-1].length).findFirst().orElse(-1);
}
/**
* Extract 2 matrices in 3-d int array format, using streams
* @param input
* @return 3-d int array,
* where the first cell is the first 2-d matrix
* and the second cell is the second 2-d matrix
*/
private static int[][][] getMatrices(String input) {
return Arrays.asList(input.split(MATRIX_TITLE))
.stream().filter(e -> !e.equals(""))
.map(k-> Arrays.stream(k.split(MATRIX_TITLE))
.map(r -> r.split(MATRIX_DELIMITER + ROW_TITLE))
.flatMap(r -> Arrays.stream(r))
.filter(e -> !e.equals(""))
.map(r-> Arrays.stream(r.split(ROW_DELIMITER))
.filter(e -> !e.equals(""))
.mapToInt(Integer::parseInt).toArray()
).toArray(int[][]::new)).toArray(int[][][]::new);
}
/**
* Multiply 2 matrices
* @param m1 first matrix
* @param m2 second matrix
* @return m1 X m2
*/
private static int[][] multiplyMatrices(int[][] m1, int[][] m2) {
return Arrays.stream(m1).map(r ->
IntStream.range(0, m2[0].length).map(i ->
IntStream.range(0, m2.length).map(j -> r[j] * m2[j][i]).sum()
).toArray()).toArray(int[][]::new);
}
}
Note: if you would like to change your input format. such as delimiters you can change the relevant constants:
For example for input in this format: matrix row -2 0 1 3 row -3 5 1 2 row 0 4 3 1 matrix row -1 3 4 row 0 4 9 row 1 -11 5 row 4 -5 7 use this configuration: MATRIX_DELIMITER = " " ROW_DELIMITER = " "
for input given is separated lines (like the input which is described in your example) use this configuration: MATRIX_DELIMITER = "\r\n" ROW_DELIMITER = "\r\n"
Upvotes: 0
Reputation: 37
public static void main(String[] args) throws FileNotFoundException {
/*
* -3 43
* 18 -60
* 1 -20
*/
Scanner scanner = new Scanner(new File("/Users/Krish/IdeaProjects/Lessons/src/Lesson34/MatrixData"));
String[] text = new String[100];
int index = -1;
while (scanner.hasNext()) {
text[++index] = scanner.nextLine();
}
scanner.close();
int matrixCount = 0;
int rowCount = 0, colCount = 0;
int aRows = 0, aCols = 0;
int bRows, bCols;
for (int i = 0; i <= index; i++) {
switch (text[i]) {
case "matrix":
if (++matrixCount == 2) {
aRows = rowCount;
aCols = colCount;
}
rowCount = 0;
colCount = 0;
break;
case "row":
rowCount++;
colCount = 0;
break;
default:
colCount++;
break;
}
}
bRows = rowCount;
bCols = colCount;
int[][] a = new int[aRows][aCols];
int[][] b = new int[bRows][bCols];
matrixCount = 0;
int rowIndex = -1, colIndex = -1;
for (int i = 0; i <= index; i++) {
switch (text[i]) {
case "matrix":
matrixCount++;
rowIndex = -1;
colIndex = -1;
break;
case "row":
rowIndex++;
colIndex = -1;
break;
default:
colIndex++;
if (matrixCount == 1) {
a[rowIndex][colIndex] = Integer.parseInt(text[i]);
} else {
b[rowIndex][colIndex] = Integer.parseInt(text[i]);
}
break;
}
}
int[][] product = MatrixMult.mult(a, b);
for (int i = 0; i < product.length; i++) {
for (int j = 0; j < product[i].length; j++) {
System.out.print(product[i][j] + "\t");
}
System.out.println();
}
}
Upvotes: 0
Reputation: 1247
This should do the trick (implementation using static arrays):
public class Main {
private static final String MATRIX_WORD = "matrix";
private static final String ROW_WORD = "row";
public static void main(String[] args) throws FileNotFoundException {
int[][][] allMatrix = getAllMatrix(args[0]);
for (int[][] currentMatrix : allMatrix) {
for (int i = 0 ; i < currentMatrix.length; i++) {
for (int j = 0; j < currentMatrix[i].length; j++) {
System.out.print(currentMatrix[i][j] + " ");
}
System.out.println();
}
System.out.println("\n\n");
}
}
private static int[][][] getAllMatrix(String fileName) throws FileNotFoundException {
int[][][] allMatrix = new int[0][0][0];
int[][] currentMatrix = new int[0][0];
String line;
try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
while ((line = br.readLine()) != null) {
switch (line) {
case MATRIX_WORD:
allMatrix = Arrays.copyOf(allMatrix, allMatrix.length + 1);
allMatrix[allMatrix.length - 1] = currentMatrix;
currentMatrix = new int[0][0];
break;
case ROW_WORD:
currentMatrix = Arrays.copyOf(currentMatrix, currentMatrix.length + 1);
currentMatrix[currentMatrix.length - 1] = new int[0];
break;
default:
currentMatrix[currentMatrix.length - 1] = Arrays.copyOf(currentMatrix[currentMatrix.length - 1],
currentMatrix[currentMatrix.length - 1].length + 1);
currentMatrix[currentMatrix.length - 1][currentMatrix[currentMatrix.length - 1].length - 1] = Integer.parseInt(line);
break;
}
}
allMatrix = Arrays.copyOf(allMatrix, allMatrix.length + 1);
allMatrix[allMatrix.length - 1] = currentMatrix;
} catch (IOException e) {
e.printStackTrace();
}
return allMatrix;
}
}
I've used Arrays.copyof()
to extend the current array (let it allow more space for the elements).
For your input file the output is :
1 2 -2 0
-3 4 7 2
6 0 3 1
-1 3
0 9
1 -11
4 -5
I'm sure there is space for improvements in this algorithm, however it should give the right results.
Upvotes: 0
Reputation: 78
I recommend you to use Java Collections instead of arrays and read matrix in this way. For example, you read "matrix" value from the input stream and call this method:
private int[][] readMatrix(final BufferedReader reader) {
List<List<Integer>> matrix = new ArrayList<>();
int rowNumber = -1;
while(reader.hasNext()) {
String value = reader.readLine();
if ("row".equals(value)) {
++rowNumber;
matrix.add(new ArrayList<Integer>());
} else {
int intValue = Integer.parseInt(value);
matrix.get(rowNumber).add(intValue);
}
}
// convert to an array
int[][] array = new int[matrix.size()][];
for (int i = 0; i < matrix.size(); ++i) {
List<Integer> row = matrix.get(i);
array[i] = row.toArray(new int[row.size()]);
}
return array;
}
Upvotes: 2