Multi-dimensional arrays with different number of elements

Hi, first time here.

I've searched through forums and I always find the same formula of X rows times Y columns (for 2D arrays)
But never a mention to the possibility of having an array made of several arrays, each of them containing a different number of variables.

So, my question is: do ALL sub matrixes must have the same number of elements?
int pinMatrix[3][1] = { {1}, {2}, {3} }; // no problem

Is it acceptable to use this? ("5" being the most elements in one of the sub-arrays)
int pinMatrix[3][5] = { {1,1}, {2,2,2,2,2}, {3} }; // Works but seems wrong to me

What'd be the correct way to store different sized arrays?

Thanks a lot!

Maybe you can elaborate what "wrong code" means. If I replace X by 5, the code happily compiles.

If you really want the first dimension to be of different sizes, you first need to define a number of arrays like

int pin1[] = {1, 1};
int pin2[] = {2, 2, 2, 2, 2};
int pin3[] = {3, 3};

and next you can use an array of pointers

int *pinMatrix[]=
{
  pin1,
  pin2,
  pin3,
};

This however brings another problem; in pinMatrix you can't determine the size of the individual arrays. So a two-dimensional iteration is doomed to fail.

To solve that, you can use an array of structs; the struct combines a pointer to the pinX with the number of elements in pinX.

struct PINS
{
  int *pins;
  uint16_t numElements;
};

PINS pinMatrix[] =
{
  {pin1, sizeof(pin1) / sizeof(pin1[0])},
  {pin2, sizeof(pin2) / sizeof(pin2[0])},
  {pin3, sizeof(pin3) / sizeof(pin3[0])},
};

Instead of typing stuff like sizeof(pin1) / sizeof(pin1[0]) all the time, you can define a macro and place it at the top of the code

#define NUMELEMENTS(x) (sizeof(x) / sizeof(x[0]))

The below code demonstrates how to iterate the two dimensions

#define NUMELEMENTS(x) (sizeof(x) / sizeof(x[0]))

int pin1[] = {1, 2};
int pin2[] = {9, 8, 7, 6, 5};
int pin3[] = {3, 4};

struct PINS
{
  int *pins;
  uint16_t numElements;
};

PINS pinMatrix[] =
{
  {pin1, NUMELEMENTS(pin1)},
  {pin2, NUMELEMENTS(pin2)},
  {pin3, NUMELEMENTS(pin3)},
};

void setup()
{

  Serial.begin(57600);
  Serial.print("number of rows in pinMatrix = "); Serial.println(NUMELEMENTS(pinMatrix));
  Serial.println();

  for (uint16_t rowCnt = 0; rowCnt < NUMELEMENTS(pinMatrix); rowCnt++)
  {
    Serial.print("number of columns in pinMatrix["); Serial.print(rowCnt);
    Serial.print("] = "); Serial.println(pinMatrix[rowCnt].numElements);
    Serial.print("content = ");
    for (uint16_t colCnt = 0; colCnt < pinMatrix[rowCnt].numElements; colCnt++)
    {
      Serial.print(pinMatrix[rowCnt].pins[colCnt]); Serial.print(", ");

    }
    Serial.println();
    Serial.println();

  }
}

void loop()
{
  // put your main code here, to run repeatedly:

}
2 Likes

int pinMatrix[3][1] <<==== makes no sense

It basically says pinMatrix has 3 elements made of 1 element each. You might as well

int pinCount[ 1 ];

instead of

int pinCount;

Array dimensions need to be > 1.

For sure learn pointers and gain a better view of data addressing in C/C++. It's likely more simple than you think.

do ALL sub matrixes must have the same number of elements?

Yes.

There are other ways to implement more complex data, but multi-dimensional arrays have fixed dimensions. (See “struct”)

sterretje:
Maybe you can elaborate what "wrong code" means. If I replace X by 5, the code happily compiles.

Sorry for the lack of clarity. I meant "dirty" or "not optimized".
It works for me too, but putting that "5" while knowing that some of the arrays had less than 5 elements... It was clear there should be a better way.
(By the way, I changed the "X" to "5" on my post probably after your reply, but without knowing someone already replied. Sorry if it looks weird now. Should I change the "5" back to "X" for clarity? It may help others)

I'm trying to use the help of the more knowledgeable people here to get my code beyond "it works". And having you three telling me "USE STRUCTS" confirms I came to the right place.

Also, man, that's some clean example. Thanks a lot.

GoForSmoke:
int pinMatrix[3][1] <<==== makes no sense

Definitely not my best post. Sorry, I used the laziest example ever. So lazy it was wrong. I should have used 3x2 at the very least.
Thanks for the encouragement, by the way.

westfw:
Yes.

There are other ways to implement more complex data, but multi-dimensional arrays have fixed dimensions. (See “struct”)

Thanks to you too for confirming what I couldn't find confirmation of.

As an explanation, I did my (re)search on all variations of ARRAYS and not a single time did I come across "structs". Not knowing they existed, I didn't search properly.
Switching coding languages is (sometimes) harder than starting from scratch.
I read an older post discussing the fact that the elements of an array couldn't be pointers. But even there, no mention of structs.

Struct and pointers it is.

Thanks, really.

An often used example of what you're getting at is making an array of different length constant C strings (each is a char array) to keep in flash.

const uint8_t keys = 16;

const char key000[] PROGMEM  = "memccpy";
const char key001[] PROGMEM  = "memchr";
const char key002[] PROGMEM  = "memcmp";
const char key003[] PROGMEM  = "memcpy";
const char key004[] PROGMEM  = "memmem";
const char key005[] PROGMEM  = "memmove";
const char key006[] PROGMEM  = "memrchr";
const char key007[] PROGMEM  = "memset";
const char key008[] PROGMEM  = "strcat";
const char key009[] PROGMEM  = "strchr";
const char key010[] PROGMEM  = "strcmp";
const char key011[] PROGMEM  = "strcpy";
const char key012[] PROGMEM  = "strlen";
const char key013[] PROGMEM  = "strlwr";
const char key014[] PROGMEM  = "strstr";
const char key015[] PROGMEM  = "strupr";

PGM_P const key_list[ keys ] PROGMEM = {
  key000,key001,key002,key003,key004,key005,key006,key007,
  key008,key009,key010,key011,key012,key013,key014,key015
};


PGM_P wordPtr;

key_list[0] points to key000, etc. Each element is 2 bytes, the overhead for different length char arrays done this way. The example actually takes more room but it's for a fast keyword match function example and there is another sketch that generates source for new lists.

As ever, you have options. As you can see above, pointers and structure can help. Don't turn away from simple solutions too soon though. If your Arduino has enough space, consider blowing it on a simple 2D array even if it feels wasteful- it's not as if saving memory does you any good if you weren't actually going to use it.

There's advantages to having a simple solution- less places for bugs to hide. Of course there are times when a more elegant design is appropriate but I'd suggest that you think carefully before ditching a "dirty" method that has clearer code and does the job.

I did my (re)search on all variations of ARRAYS and not a single time did I come across "structs". Not knowing they existed, I didn't search properly.

Yes; some modern languages take a lot of the "data structures" that you would have to manually define as "structures" of some sort (C/C++ "struct", Pascal "record", Java/C++ "Class") and build them into the language - for example Pythons "lists" and "dictionaries."
It's important, at least on small memory systems, to understand that the more complex data structures have different sorts of "overhead" than simple multi-dimensional fixed-size arrays. If you want to have each row have a different number of columns, then something in the program has to remember how many columns are in each row, and where that row starts in memory, and so on (for the fixed size, you can derive those from simple math on constants known at compile time.) Depending on the circumstances, the overhead can be more than the space saved by the more complex data structure.
For example, consider:

char chararray[5][4] = {"this", "is", "a", "test"};
char *strarray[4] =  {"this", "is", "a", "test"};

The first array is trivially 20 bytes long, with several byes wasted for "is" and "a"
The second example, avoids wasting string space - it takes only 3 bytes for "is" and 2 for "a", but it uses 8 bytes (on an AVR) for the pointers, for a total of 23 bytes... And the code to access each case will be ... different.
I shudder every time I see a Python program that uses a dictionary for some trivial translation case that could have been done with a simple array, or even algorithmically. Like:

   octal2binary = {"0":0, "1":1, "2":2, "3":3, "4":4, "5":5,  "6":6, "7":7}
   n = n*8 + octal2binary[nextc]

GoForSmoke:
An often used example of what you're getting at is making an array of different length constant C strings (each is a char array) to keep in flash.

PGM_P const key_list[ keys ] PROGMEM = {

key000,key001,key002,key003,key004,key005,key006,key007,
  key008,key009,key010,key011,key012,key013,key014,key015
};




key_list[0] points to key000, etc.

That is exactly one of my problems switching language. I'd automatically go for a loop to "push" key000 through key015. I'm not saying one way is better than the other, of course. It's just the way I'm used to do it.
Anyway, it's almost zero work writing a sketch or something to make the list, then copy paste it.

Thanks again!

wildbill:
As ever, you have options. As you can see above, pointers and structure can help. Don't turn away from simple solutions too soon though. If your Arduino has enough space, consider blowing it on a simple 2D array even if it feels wasteful- it's not as if saving memory does you any good if you weren't actually going to use it.

There's advantages to having a simple solution- less places for bugs to hide. Of course there are times when a more elegant design is appropriate but I'd suggest that you think carefully before ditching a "dirty" method that has clearer code and does the job.

Agreed. It's just that I was using a simplified example, trying to get the gist of arrays in Arduino.
In my project, I have 35 frames (key000 - key035) with 108 leds (Hex colors) each.
That's why I'm (was?) a little concerned with optimizing the code.

westfw:
Yes; some modern languages take a lot of the "data structures" that you would have to manually define as "structures" of some sort (C/C++ "struct", Pascal "record", Java/C++ "Class") and build them into the language - for example Pythons "lists" and "dictionaries."
It's important, at least on small memory systems, to understand that the more complex data structures have different sorts of "overhead" than simple multi-dimensional fixed-size arrays. If you want to have each row have a different number of columns, then something in the program has to remember how many columns are in each row, and where that row starts in memory, and so on (for the fixed size, you can derive those from simple math on constants known at compile time.) Depending on the circumstances, the overhead can be more than the space saved by the more complex data structure.

Yep. I just realized I'm (extremely) raw on the differences between compiling vs interpreted languages.
Still, extremely funny so far. I never stopped to think the world ain't built on OSs doing the work for us. It's cool to take care of the little guy (the board) and thinking of its parts as such, not simply concepts.
Sorry, went off-topic.

Thanks for the monitoring and examples, once again!

You can load some kind of OS on an AVR but it's better to write your own bugs.