Converting a 2D Array of 2D Arrays into a single 2D Array

arrays, c#, matrix, multidimensional-array

Solution

I started with a simple Matrix class to hold the `double[,]`s. Nothing too fancy, just a simple array-of-arrays with a row and column count and array accessor.

class Matrix<T>
{
    public int Rows { get; private set; }
    public int Cols { get; private set; }

    private T[,] mat;

    public Matrix(int rowCount, int colCount)
    {
        Rows = rowCount;
        Cols = colCount;
        mat = new T[Rows, Cols];
    }

    public T this[int r, int c]
    {
        get { return mat[r, c]; }
        set { mat[r, c] = value; }
    }
}

Your second case looks more difficult (and like a better test of correctness) than the first, so I set up a metamatrix to match that.

public static Matrix<double[,]> BuildMetaMatrix()
{
    Matrix<double[,]> m = new Matrix<double[,]>(2, 2);

    m[0, 0] = new double[,]
    {
        { 1 }
    };

    m[0, 1] = new double[,]
    {
        { 3, 3, 3 },
        { 3, 3, 3 },
        { 3, 3, 3 }
    };

    m[1, 0] = new double[,]
    {
        { 2, 2 },
        { 2, 2 }
    };

    m[1, 1] = new double[,]
    {
        {4, 4, 4, 4},
        {4, 4, 4, 4},
        {4, 4, 4, 4},
        {4, 4, 4, 4}
    };

    return m;
}

For convenience, I made a Place function that puts one matrix into another one at the given location.

static void Place(double[,] src, double[,] dest, int destR, int destC)
{
    for (int row = 0; row < src.GetLength(ROW_DIM); row++)
    {
        for (int col = 0; col < src.GetLength(COL_DIM); col++)
        {
            dest[row + destR, col + destC] = src[row, col];
        }
    }
}

The magic numbers fed into `GetLength()` were just asking for mistakes, so I defined some constants for them (`ROW_DIM = 0` and `COL_DIM = 1`). I decided to handle the padding by figuring out how wide a column is and how tall a row is and skipping any extra elements after `Place()`ing the sub-matrix in. A `GetRowHeight()` and `GetColWidth()` method figure out the values.

public static int GetRowHeight(Matrix<double[,]> m, int row)
{
    int maxSeen = 0;

    for (int col = 0; col < m.Cols; col++)
    {
        if (m[row, col].GetLength(ROW_DIM) > maxSeen)
        {
            maxSeen = m[row, col].GetLength(ROW_DIM);
        }
    }

    return maxSeen;
}

public static int GetColWidth(Matrix<double[,]> m, int col)
{
    int maxSeen = 0;

    for (int row = 0; row < m.Rows; row++)
    {
        if (m[row, col].GetLength(COL_DIM) > maxSeen)
        {
            maxSeen = m[row, col].GetLength(COL_DIM);
        }
    }

    return maxSeen;
}

A `Flatten()` function loops through all the sub-matrices, `Place()`ing them at the appropriate row and column in a new matrix. It updates the next row and column after each `Place()` using the `GetRowHeight()` and `GetColWidth()` functions.

Matrix<double> Flatten(Matrix<Matrix<double>> src)
{
    // (7, 6) == (this.TotalRows(), this.TotalColumns())
    // from your code.
    Matrix<double> dest = new Matrix<double>(7, 6);

    int nextRow = 0;
    int nextCol = 0;

    for (int row = 0; row < src.Rows; row++)
    {
        for (int col = 0; col < src.Rows; col++)
        {
            dest.Place(src[row, col], nextRow, nextCol);
            nextCol += GetColWidth(src, col);
        }
        nextRow += GetRowHeight(src, row);
        nextCol = 0;
    }

    return dest;
}

A little glue to test it out...

static void Main(string[] args)
{
    Matrix<double[,]> src = BuildMetaMatrix();
    double[,] dest = Flatten(src);

    Print(dest);
    Console.ReadLine();
}

static void Print(double[,] matrix)
{
    for (int row = 0; row < matrix.GetLength(ROW_DIM); row++)
    {
        for (int col = 0; col < matrix.GetLength(COL_DIM); col++)
        {
            Console.Write(matrix[row, col] + "\t");
        }
        Console.Write("\n");
    }
}

...and you get an output just like your second case with all the oddly fitting matrices and `0`s in the empty places.*

1       0       3       3       3       0
0       0       3       3       3       0
0       0       3       3       3       0
2       2       4       4       4       4
2       2       4       4       4       4
0       0       4       4       4       4
0       0       4       4       4       4

*The destination matrix gets its values initialized to `default(double)`, which happens to be `0` (the value you wanted). If you need something other than `default(double)` for the empty places, you can probably get them by iterating over the new matrix and writing the new default value everywhere before `Flatten()`ing the metamatrix.

(Thanks to Jeff Mercado for pointing out that multidimensional arrays' `GetLength()` method can be used to find their dimensions.)

Problem

So I have an object called, for lack of a better word, `MatricesMatrix` which is a `Matrix` of Matrices (everything is stored as a `double[,]`). I want to strip all of the values from the inner matrices into one big Matrix. Here is what I have so far: ``` public Matrix ConvertToMatrix() { //Figure out how big the return matrix should be int totalRows = this.TotalRows(); int totalColumns = this.TotalColumns(); Matrix returnMatrix = new Matrix(totalRows, totalColumns); List<object> rowElementsList = new List<object>(); //"outer" index means an index of the MatricesMatrix //"inner" index means an index of a Matrix within the Matrices Matrix //outer row loop for (int outerRowIndex = 0; outerRowIndex < NumberOfRows; outerRowIndex++) { //outer column loop for (int outerColumnIndex = 0; outerColumnIndex < NumberOfColumns; outerColumnIndex++) { Matrix currentMatrix = GetElement(outerRowIndex, outerColumnIndex); object element = null; //inner row loop for (int innerRowIndex = 0; innerRowIndex < currentMatrix.NumberOfRows; innerRowIndex++) { //inner column loop for (int innerColumnIndex = 0; innerColumnIndex < currentMatrix.NumberOfColumns; innerColumnIndex++) { element = currentMatrix.GetElement(innerRowIndex, innerColumnIndex); } } returnMatrix.SetElement(outerRowIndex, outerColumnIndex, (double)element); } } return returnMatrix; } ``` Note that I have determined programmatically the total number of rows and columns the returnMatrix needs to have. Here are some more guidelines and output cases: - Each element of the big matrix should be in the same position relative to the other elements of the big matrix that came from the Matrix inside of MatricesMatrix that the element came from. - Each "matrix" (no longer in matrix form) inside of the big matrix should be in the same position relative to the other matrices inside of the big matrix as it was inside of the MatricesMatrix (with no overlapping, and 0's in any spaces left empty). CASE 1 Given this input: a `MatricesMatrix(2,2)` with `[0,0] = (2x2 matrix), [0,1] = (2x3 matrix), [1,0] = (2x2 matrix), and [1,1] = (2x3 matrix)`. That is, Output must be: CASE 2 Given this input: a `MatricesMatrix(2,2)` with `[0,0] = (1x1 matrix), [0,1] = (3x3 matrix), [1,0] = (2x2 matrix), and [1,1] = (4x4 matrix)`. That is, Output should be something like: Any assistance would be greatly appreciated! UPDATE: Here is a unit test for Case 1 that should pass: ``` [TestMethod] public void MatricesMatrix_ConvertToMatrixTest() { Matrix m1 = new Matrix(2); Matrix m2 = new Matrix(2, 3); Matrix m3 = new Matrix(2); Matrix m4 = new Matrix(2, 3); double[] m1Row1 = { 1, 1 }; double[] m1Row2 = { 1, 1 }; double[] m2Row1 = { 2, 2, 2 }; double[] m2Row2 = { 2, 2, 2 }; double[] m3Row1 = { 3, 3 }; double[] m3Row2 = { 3, 3 }; double[] m4Row1 = { 4, 4, 4 }; double[] m4Row2 = { 4, 4, 4 }; m1.SetRowOfMatrix(0, m1Row1); m1.SetRowOfMatrix(1, m1Row2); m2.SetRowOfMatrix(0, m2Row1); m2.SetRowOfMatrix(1, m2Row2); m3.SetRowOfMatrix(0, m3Row1); m3.SetRowOfMatrix(1, m3Row2); m4.SetRowOfMatrix(0, m4Row1); m4.SetRowOfMatrix(1, m4Row2); MatricesMatrix testMatricesMatrix = new MatricesMatrix(2, 2); testMatricesMatrix.SetElement(0, 0, m1); testMatricesMatrix.SetElement(0, 1, m2); testMatricesMatrix.SetElement(1, 0, m3); testMatricesMatrix.SetElement(1, 1, m4); Matrix expectedResult = new Matrix(4, 5); double[] expectedRow1 = { 1, 1, 2, 2, 2 }; double[] expectedRow2 = { 1, 1, 2, 2, 2 }; double[] expectedRow3 = { 3, 3, 4, 4, 4 }; double[] expectedRow4 = { 3, 3, 4, 4, 4 }; expectedResult.SetRowOfMatrix(0, expectedRow1); expectedResult.SetRowOfMatrix(1, expectedRow2); expectedResult.SetRowOfMatrix(2, expectedRow3); expectedResult.SetRowOfMatrix(3, expectedRow4); Matrix actualResult = testMatricesMatrix.ConvertToMatrix(); (actualResult == expectedResult).Should().BeTrue(); } ```

Original source