tags:

views:

660

answers:

5

Is there an easy way to reference a column in a 2-D array as a separate 1-D array in plain old C (not C++ or C#)? It's easy to do this for a row. Asssume I have 2 functions:

double doSomethingWithARow( double theRow[3] );
double doSomethingWithACol( double theCol[100] );

Then, I might use the first one like this:

double matrix[100][3];
double result;

// pass a single row to a function as an array
// this essentially passes the 3-element array at row 48 to the function
for( int i=0; i < 100; i++ )
{
   result = doSomethingWithARow( matrix[i] );
}

What I want it a way to access a column easily.

for( int j=0; j < 3; j++ )
{
   result = doSomethingWithACol( ??????????? );
}

The only thing I've come up with so far is transforming the matrix to swap the rows with the columns. But this code is supposed to be as efficient as possible in terms of memory and speed. With all of the convoluted ways to reference pointers in C, it seems like there should be a way to do this.

A: 

Since the "columns" as you call them are stored discontiguously in memory, there's no real way to pull this off directly.

You can, however, create an array of pointers, and store references to the indexes of the other array in that one. You'd need to loop through all of the elements in your array, so it's probably not a better solution than any other. Depending on how often you need to access the array by column it might be worthwhile, though.

Welbog
+5  A: 

Well, you'd have to pass the size of a row, and the number of rows:

 double doSomethingWithACol(double *matrix, size_t colID, size_t rowSize, size_t nRows);

Now you can make use of the fact that matrix[i][j] = matrix + i * rowSize + j;

Alternatively, you can also use the following signature:

 double doSomethingWithACol(double *colPtr, size_t rowSize, size_t nRows);

Here, you'll have to pass the pointer to the first element of the column that you want to process, instead of the pointer to the first row.


Example code: This code sums the elements in the second column (compile with gcc -o main -Wall -Wextra -pedantic -std=c99 test.c):

#include <stdio.h>
#include <stdlib.h>

double colSum1(double *matrix, size_t colID, size_t rowSize, size_t nRows)
{
  double *c = NULL, *end = matrix + colID + (nRows * rowSize);
  double sum = 0;

  for (c = matrix + colID; c < end; c += rowSize) {
    sum += *c;
  }

  return sum;
}

double colSum2(double *colPtr, size_t rowSize, size_t nRows)
{
  double *end = colPtr + (nRows * rowSize);
  double sum = 0;

  for (; colPtr < end; colPtr += rowSize) {
    sum += *colPtr;
  }

  return sum;
}

int
main(void)
{
  double matrix[4][3] = {
    {0,  1, 2},
    {3,  4, 5},
    {6,  7, 8},
    {9, 10, 11}
  };

  printf("%f\n", colSum1(*matrix, 1, 3, 4));
  printf("%f\n", colSum2(&matrix[0][1], 3, 4));
  printf("%f\n", colSum2(matrix[0] + 1, 3, 4));

  return EXIT_SUCCESS;
}
Stephan202
You beat me to it....
Michael Todd
This will not do what you think. It is completely wrong to pass in a double **. It will cause a segfault in the application. I explain more in my answer.
Brian R. Bondy
The double ** is not necessary; I noticed that when I wrote my example code. It has nothing to do with segfaults, however.
Stephan202
It sure is cleaner, but does not work for arrays with arbitrary dimensions. So it depends on the situation which approach is to be preferred.
Stephan202
@Stephan202: good point +1.
Brian R. Bondy
this is a useful and often done way. but please note that it is also undefined behavior. (read http://groups.google.com/group/comp.lang.c/msg/327b816c5ea5d97b for why)
Johannes Schaub - litb
i give +1 because it's a useful technique anyway :p
Johannes Schaub - litb
A: 

You can't really do that, because arrays in C are stored such that the elements of each row are stored together. That means a row of an array is a continuous block of memory, and as far as C is concerned it might as well be an independent array itself. It doesn't work the same way with columns because the elements of a column are not continuous in memory; rather they are spaced at intervals of N bytes, where each row is N bytes long. This means that you could efficiently access the various elements of a column of a 2D array by using pointer arithmetic, but there's no way to actually make a column into an array itself other than by copying the elements over into a new array.

David Zaslavsky
A: 

No, there isn't. There cannot be, since in C, an array is a consecutive part of memory, and it is trivial that rows and columns cannot be consecutive at the same time.

That being said, it is fairly easy to jump from one cell of a column to the next, if you know the length of the rows. Take the following example:

void processColumn(double *array, int colIdx, int rowLen, int rowCnt) {
    for (int i = colIdx; i < rowCnt * rowLen; i += rowLen) {
       // do whatever you want
    }
}

#define N 5
#define M 10

double array[N*M];

processColumn(array, 3, N, M);
David Hanak
+2  A: 

A nice typesafe way to do this without specifying the dimensions as a separate parameters is as follows:

#define ROWS 100
#define COLUMNS 30 

void doSomethingToAllRows(double (*row)[ROWS][COLUMNS], int col, double val)
{
    for(size_t i = 0; i < ROWS; ++i)
     (*row)[i][col] = val;
}

void doSomethingToAllColumns(double (*col)[ROWS][COLUMNS], int row, double val)
{
    for(size_t i = 0; i < COLUMNS; ++i)
     (*col)[row][i] = val;
}

int main(int argc, char **argv)
{
    double matrix[ROWS][COLUMNS];

    /* Modify each column of the 10th row with the value of 3 */
    doSomethingToAllColumns(&matrix, 10, 3); 

    /* Modify each row of the 10th column with the value of 3 */
    doSomethingToAllRows(&matrix, 10, 3);

    return 0;
}

It is completely wrong to pass a double ** for this reason:

void test()
{
  double **a;
  int i1 = sizeof(a[0]);//i1 == 4 == sizeof(double*)

  double matrix[ROWS][COLUMNS];
  int i2 = sizeof(matrix[0]);//i2 == 240 == COLUMNS * sizeof(double)
}

If you passed in a double ** then accessed it like an array you would cause a crash, segfault or undefined behavior.

Brian R. Bondy
Your functions use references whereas the original poster specified C. Other than that it's a good solution if you're desperate to avoid using a container.
Andrew Grant
@Andrew Grant: Good point, I changed it to pointers.
Brian R. Bondy
+1, because this code is to be preferred to my solution if the dimensions of the arrays used in the program are fixed.
Stephan202