2

I'm wondering what the most compact and efficient way to multiple 2 double[][] arrays matrices using streams. The approach should follow matrix multiplication rules as illustrated here:

Matrix Multiplication: How to Multiply Two Matrices Together

Here's one way to do it using for loops (this is the first matrix):

final int nRows = this.getRowDimension();
final int nCols = m.getColumnDimension();
final int nSum = this.getColumnDimension();

final double[][] outData = new double[nRows][nCols];
// Will hold a column of "m".
final double[] mCol = new double[nSum];
final double[][] mData = m.data;

// Multiply.
for (int col = 0; col < nCols; col++) {
    // Copy all elements of column "col" of "m" so that
    // will be in contiguous memory.
    for (int mRow = 0; mRow < nSum; mRow++) {
        mCol[mRow] = mData[mRow][col];
    }

    for (int row = 0; row < nRows; row++) {
        final double[] dataRow = data[row];
        double sum = 0;
        for (int i = 0; i < nSum; i++) {
            sum += dataRow[i] * mCol[i];
        }
        outData[row][col] = sum;
    }
}

The procedure should fit the following test data:

double[][] md1 = {{4d, 8d}, {0d, 2d}, {1d, 6d}};
double[][] md2 = {{5d, 2d, 5d, 5d}, {9d, 4d, 5d, 5d}};

double[][] mb1 = {{4d, 8d}, {0d, 2d}, {1d, 6d}};
double[][] mb2 = {{5d}, {9d}};
Ole
  • 41,793
  • 59
  • 191
  • 359

3 Answers3

10

A more compact and readable solution is to create a Stream over the rows of the first matrix, map each row to the result of multiplying it with the second matrix column and collect that back into a double[][].

public static void main(String[] args) {
    double[][] m1 = {{4, 8}, {0, 2}, {1, 6}};
    double[][] m2 = {{5, 2}, {9, 4}};

    double[][] result = Arrays.stream(m1)
            .map(r -> IntStream.range(0, m2[0].length)
                    .mapToDouble(i -> IntStream.range(0, m2.length)
                            .mapToDouble(j -> r[j] * m2[j][i]).sum())
                    .toArray())
            .toArray(double[][]::new);

    System.out.println(Arrays.deepToString(result));
    // [[92.0, 40.0], [18.0, 8.0], [59.0, 26.0]]
}

This will calculate m1 * m2 and the result will be in result. For the multiplication of each row, we can't create a Stream with Arrays.stream of the second matrix since this would create a Stream over the rows when we need a Stream over the columns. To counteract that, we simply go back to using an IntStream over the indexes.

Community
  • 1
  • 1
Tunaki
  • 132,869
  • 46
  • 340
  • 423
1

I created a BiFunction that performs the multiplication using IntStream.range(). If anyone has anything more compact I would love to see it. Here it is:

public static BiFunction<ArrayMatrix, ArrayMatrix, ArrayMatrix> multiply(boolean parallel) {
    return (m1, m2) -> {
        // checkMultiplicationCompatible(m1, m2);
        final int m1Rows = m1.getRowDimension();
        final int m2Rows = m2.getRowDimension();
        final int m1Cols = m1.getColumnDimension();
        final int m2Cols = m2.getColumnDimension();

        double[][] a1 = m1.getData();
        double[][] a2 = m2.getData();

        final double[][] result = new double[m1Rows][m2Cols];

        // Buffer for the tranpose of each md2 column
        final double[] transpose = new double[m1Rows];

        range(0, m2Cols).forEach(m2Col -> {
            range(0, m2Rows).forEach(m2Row -> {
                transpose[m2Row] = a2[m2Row][m2Col];
            });
            range(0, m1Rows).forEach(row -> {
                final double[] dataRow = a1[row];
                double sum = 0;
                for (int m1Col = 0; m1Col < m1Cols; m1Col++) {
                    sum += dataRow[m1Col] * transpose[m1Col];
                }
                result[row][m2Col] = sum;
            });
        });
        return new ArrayMatrix(result, false);
    };
}
Community
  • 1
  • 1
Ole
  • 41,793
  • 59
  • 191
  • 359
1

You can use three nested IntStreams to multiply two matrices. Outer stream iterates over the rows of the first matrix and the inner stream iterates over the columns of the second matrix to build the result matrix. The innermost stream obtains the entries of the result matrix. Each entry is the sum of the products obtained by multiplying the i-th row of the first matrix and the j-th column of the second matrix:

/**
 * Matrix multiplication
 *
 * @param m rows of 'a' matrix
 * @param n columns of 'a' matrix
 *          and rows of 'b' matrix
 * @param p columns of 'b' matrix
 * @param a first matrix 'm×n'
 * @param b second matrix 'n×p'
 * @return result matrix 'm×p'
 */
public static double[][] matrixMultiplication(
        int m, int n, int p, double[][] a, double[][] b) {
    return IntStream.range(0, m)
            .mapToObj(i -> IntStream.range(0, p)
                    .mapToDouble(j -> IntStream.range(0, n)
                            .mapToDouble(k -> a[i][k] * b[k][j])
                            .sum())
                    .toArray())
            .toArray(double[][]::new);
}

// test
public static void main(String[] args) {
    double[][] md1 = {{4d, 8d}, {0d, 2d}, {1d, 6d}};
    double[][] md2 = {{5d, 2d, 5d, 5d}, {9d, 4d, 5d, 5d}};
    double[][] md3 = matrixMultiplication(3, 2, 4, md1, md2);

    Arrays.stream(md3).map(Arrays::toString).forEach(System.out::println);
    //[92.0, 40.0, 60.0, 60.0]
    //[18.0, 8.0, 10.0, 10.0]
    //[59.0, 26.0, 35.0, 35.0]

    //// //// //// //// //// //// //// ////

    double[][] mb1 = {{4d, 8d}, {0d, 2d}, {1d, 6d}};
    double[][] mb2 = {{5d}, {9d}};
    double[][] mb3 = matrixMultiplication(3, 2, 1, mb1, mb2);

    Arrays.stream(mb3).map(Arrays::toString).forEach(System.out::println);
    //[92.0]
    //[18.0]
    //[59.0]
}

See also: Parallelized Matrix Multiplication