Search code examples
arrayscmemory-efficient

memory efficient storage of a constant depopulated 2D array in C


In a two dimensional array of constant integer values, each row may have a different number of valid columns. The number or rows and the number of columns in each row are known and constant. They could be placed in a struct of structs together with the values as follows:

        typedef struct {
            uint32_t numColumns;
            uint32_t column[100];
        } row_T;
       
        typedef struct {
            uint32_t numRows;
            row_T    row[100];
        } array_T;

The array is initialized with constant values (I have no preference whether this is done at compile time or at run time):

        array_T a  = {0};

        a.numRows = 2;
        a.row[0].numColumns = 2; // two values in row 0
        a.row[0].column[0] = 7;
        a.row[0].column[1] = 17;
        a.row[1].numColumns = 5; // five values in row 1
        a.row[1].column[0] = 100;
        a.row[1].column[1] = 2;
        a.row[1].column[2] = 5;
        a.row[1].column[3] = 12;
        a.row[1].column[4] = 4;

Stepping through the array is then possible by:

        for (uint32_t i=0; i<a.numRows; i++) {
            for (uint32_t j=0; j<a.row[i].numColumns; j++) {
                //do something with a.row[i].column[j];
            }
        }

The problem is that the number of columns in each row may vary dramatically. Therefore defining a large enough array is a waste of memory.

Defining types for rows of different length, e.g.

        typedef struct {
            uint32_t numColumns;
            uint32_t column[5];
        } row_5_T;
       
        typedef struct {
            uint32_t numColumns;
            uint32_t column[2];
        } row_2_T;

and

        typedef struct {
            uint32_t numRows;
            row_2_T row_0;
            row_5_T row_1;
        } array_2_T;

is memory efficient but does not allow using a loop counter as an index for fetching the row data.

Finding the memory location of each entry in such struct, based on the row and column indexes (i,j), would be possible but inconvenient:

1+(sum of numColumns+1 for columns 0..i-1)+1+j

Suggestions for any elegant solution are welcome.


Solution

  • We can store the data in a single master array of the element type (apparently uint32_t) with an array indicating where each row starts within the master array.

    For an array with NRows rows with each row containing NColumns[i] columns:

    • Allocate space for the offsets: size_t *Offset = malloc(NRows * sizeof *Offset);.
    • Calculate the offsets: Offset[0] = 0; for (size_t r = 0; r < NRows; ++r) Offset[r] = Offset[r-1] + NColumns[r-1];.
    • Allocate space for the master array: uint32_t *Array = malloc((Offset[NRows-1] + NColumns[NRows-1]) * sizeof *Array);.
    • To iterate through the array:
    for (size_t r = 0; r < NRows; ++r)
        for (size_t c = 0; c < NColumns[r-1]; ++c)
            ... // Here array element r, c is in Array[Offset[r] + c].
    

    We could also set a pointer to the start of the current row:

    for (size_t r = 0; r < NRows; ++r)
    {
        uint32_t *Row = Array + Offset[r];
        for (size_t c = 0; c < NColumns[r-1]; ++c)
            ... // Here array element r, c is in Row[c].
    }