user2253513
user2253513

Reputation:

Iterating through many arrays in C

I have a header file that has many arrays. Each array pair (i.e. x0, y0) are the same length, but different pairs have different lengths.

numPaths[] = 4;
pathLengths[] = {12, 11, 13, 10};
int x0[] ={4.0, 4.0, ...};
int x1[] ={224.0, 224.0, ...};
int x2[] ={446.0, 446.0, 446.0, ...};
int x3[] ={598.0, 598.0, ...};
int y0[] ={11.0, 11.0, 11.0, 15.0, ...};
int y1[] ={2.0, 2.0, 2.0, 6.0, 17.0, ...};
int y2[] ={1.0, 1.0, 1.0, 5.0, ...};
int y3[] ={4.0, 4.0, 4.0, 5.0, 10.0, ...};

I want to be able to iterate through this list of arrays and access data point information. I don't know how to do that, particularly because the path lengths are different. I'm thinking of pseudo code like this:

i=0;
n=0;
if i< numPaths:
    if n < pathLengths[i]:
        x_tot = x'[i]'[n]
        y_tot = x'[i]'[n]
    n++
    i++

Where the '[i]' is in quotation marks because the names of these arrays are strings.

Upvotes: 3

Views: 149

Answers (5)

Ahmed Masud
Ahmed Masud

Reputation: 22374

Okay as @Tommy has pointed out the syntax you used is not possible in C because it's not a reflexive language; I am going to try and help you with the set up the way it should be done in C and can be used in a dynamic way;

What you really want to do is set up a structure (I'll call it a point, but you can call it anything) like this:

struct point { double x; double y; };

NOW trivially you can have multiple arrays built of this structure:

struct point array0[] = { { 4.0, 11.0 }, { 4.0 , 11.0 }, ... };
struct point array1[] = { { 224.0, 2.0 }, { 224.0, 2.0 }, { }, ... };
/* etc... */

and yeah you can put them on multiple lines to make the syntax prettier than mine :-)

This will let you get the length of any given array this way:

size_t length_array0 = sizeof(array0) / sizeof(struct point);

However this is still not useful for your case because you can't access the correct array programmatically; this will require quite a bit of set up to do correctly. Doing it statically in the software is a big pain, it's better to set up a set of functions that can handle points, maps and an array of maps and read the maps from a file:

The proper way to go about it is to build a bunch of functions and structures which deal with each component and build everything up.

Here is a possible way to go about it:

We need some basic libraries:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>

Let's set up a basic point and a collection of them in a map

struct point { double x, y; };

struct map { 
    size_t count;           /* number of points in this map */
    struct point *points;   /* array of points in the map */
}; 

Now lets create some functions to allocate and deallocate them

struct map * map_init(size_t count)
{
        struct map *m = (struct map *) malloc(sizeof (struct map));
        if ( !m )
                return NULL;
        m->count = count;
        m->point = (struct point *) malloc (count * sizeof(struct point));
        if ( !m->point ) {
                free(m);
                m = NULL;
        }
        return m;
}


void map_free(struct map *m)
{
        if ( m ) {
                if ( m->points )
                        free (m->points);
                free(m);
        }
}

and a couple of functions to add new points to an allocated map at a particular index:

struct point *map_set_point(struct map *m, int index, struct point *p)
{
    if ( !m )
        return NULL;

    if ( index < m->count ) {
        m->points[index].x = p->x;
        m->points[index].y = p->y;
        return &m->points[index];
    }

    return NULL;
}

struct point *map_set_point_xy(struct map *m, int index, double x, double y)
{
    struct point p;
    p.x = x;
    p.y = y;
    return map_set_point(m, index, &p);
}

It's also a good idea to be able to get a point at an index back without having to do index range checking all over our code so we write a wrapper for that

struct point *map_get_point(struct map *m, int index)
{

    if ( !m || index < 0 || index >= m->count ) {
        return NULL;
    }

    return &m->points[index];
}

and for completeness we do the same for the number of points in a map:

size_t map_get_count(struct map *m)
{
    if ( m ) 
        return m->count;

    return ((size_t)-1);
}

It would also be nice to be able to run through all the points in a map without having to set up a for loop every-time so we create a call-back function that can iterate over all the points in a map and call one of our functions that we can define later

typedef int (*point_callback_func_t)(struct point *p);

int for_each_point_call(struct map *m, point_callback_func_t f, int *error)
{
    int k;
    int rv; /* return value from call back function */
    for (k = 0; k < m->count; k++) {
        if ((rv = f(&m->points[k])) != 0 ) {
            if (error != NULL) 
                *error =rv;
            break;
        }
    }
    return (k - m->count); /* 
                * if we go through all of the points this will be zero 
                * otherwise it will be a negative number  indicating how far 
                * we were able to go in the loop
                */
}

Now that we have a map let's create a set of them

struct mapset {
    size_t count;       /* number of maps in this set */
    struct map **maps;  /* a NULL terminated array of maps in this set */
};

But how will we initialize a complicated thing like this? It's not really a good idea to do this hardcoded, it would be nice if we can read this stuff from a text file so let's create a code base for loading maps from a file:

/* 
 * The read_maps_from_file  reads a text file of the format:

 MAP
 <M>
 <x1>, <y1>
 <x2>, <y2>
  ...
 <xM>, <yM>

 MAP
 <N>
 <x1>, <y1>
 <x2>, <y2>
  ...
 <xN>, <yN>


 and so on and can return a struct mapset filled with the map 
 coordinates

 A file with 3 maps would be:

--CUT-HERE--
 MAP
 3
 5.5, 2.2
 -3.5, 5.9
 2.0, -125.0

 MAP
 5
 2.2, -89.0
 551, 223.5
 7.5, -8.9
 7.8, 6.9
 4.3, -9.9

 MAP
 1
 2.5, 0.3

--CUT-HERE-- 

*/

We will need to be able to free a mapset in case of an error in load_mapset; you should read that function first; but we've declare it first so that it can be called from the load_mapset function.

void free_mapset(struct mapset *set) {
    int k;
    if ( set ) {
        if ( set->maps ) {
            for(k = 0; k < set->count; k++) {
                map_free(set->maps[k]);
            }
        }
        free(set);
    }
}

So lets look at loading a map from a file:

struct mapset *load_mapset(const char *filename)
{
    FILE * fp;
    struct mapset *set;
    size_t mapcnt = 0; /* number of maps in this set */

    char buf[1024]; /* line buffer */

    struct map *tmpmap;
    size_t tmpcnt;

    struct map **tmp_maps_arr;

    double x, y;

    int k;

    set = (struct mapset*) malloc(sizeof(struct mapset)); 

    if ( !set )
        goto build_error;

    set->count = 0;
    set->maps = NULL;

    fp = fopen("somefile.txt", "r");

    while(!feof(fp)) {

        fgets(buf, sizeof(buf), fp); /* read a line */

        if ( strcmp ( buf, "MAP") != 0 )  /* look for keyword 'MAP' */
            continue; /* nope, let's reloop */

        /* found 'MAP' read the next line to get the count */

        fgets(buf, sizeof(buf), fp); 

        if (feof(fp)) 
            goto build_error;

        sscanf(buf, "%lu", &tmpcnt); /* number of points in this map */

        tmpmap = map_init(tmpcnt);   /*  make a tmpmap of tmpcnt points */

        if ( !tmpmap )
            goto build_error; 

        for ( k = 0; k < tmpcnt; k++ ) {
            fgets(buf, sizeof(buf), fp);
            if (feof(fp)) 
                goto build_error;

            sscanf(buf, "%lf , %lf", &x, &y);
            map_set_point_xy(tmpmap, k, x, y); /* add x, y to index k */
        }

        /* try to increase the size of maps array */
        tmp_maps_arr= (struct map **) realloc(set->maps, sizeof(struct map *) * (1+set->count));
        if ( !tmp_maps_arr ) 
            goto build_error;

        set->maps  = tmp_maps_arr;
        set->maps[set->count] = tmpmap; /* save the pointer to the map */
        set->count++;
        tmpmap = NULL;
    }

    return set;

build_error:
    free_mapset(set);
    if (tmpmap) 
        map_free(tmpmap);

    return NULL;
}

AND That's the end of our library code for handling points, maps and set of maps;;

You can grab a complete copy of this code from: http://pastebin.com/i2m624yX

The code should compile as is and should work teehee (although I haven't done much checking, let me know how well it works for you, if not we'll see what happens)

A NOTE ON use of GOTOs in the load_setmap function

this function uses gotos to get out of the sitations where it's unable to build a proper map for whatever reason; and before any puritan structural programmers jump down my throat for using gotos I would like you to examine the code and verify that it is indeed the cleanest way to program error handling using the approach given therein

Further Notes

There is a lot going on in this code; but this is working code that; should serve as a general approach.. There are more interesting ways of approaching the problem with linked lists etc. but I wanted you too a structured approach more than anything else;

One point of interest is the iterate_over_map function which takes a function pointer as an argument and can run through all the points and call back your own function; If you don't understand what's going on there drop me a note here and I'll elaborate; But please study this, it is a reasonable way to set up things in C that are easy to follow once you go through each function; and most of the structure of the functions and their uses is self explanatory.

Another NOTE: I haven't bothered creating prototypes and separate headers yet because I wanted you to see the flow of building the logic up

The code should read fairly easily top to bottom with logical build up; I spread the structures out into the code to keep them close to the functions that deal with them; normally you would put those in their own files and have corresponding header files for inclusions elsewhere but that would've taken away from the logical buildup I am hoping to convey. That can be learnt elsewhere.

Pop a note if you get confused.

Upvotes: 2

perreal
perreal

Reputation: 97938

numPaths[] = 4;
pathLengths[] = {12, 11, 13, 10};
/* int x0[] ={4.0, 4.0, ...}; ... */
int **pairs = {x0, y0, x1, y1, x2, y2, x3, y3};
for (int i = 0; i < 4; i++)
    process_pair(&x_tot, &y_tot, pairs[i*2], pairs[i*2+1], pathLengths[i]);

void process_pair(int *x_tot, int *y_tot, int *x, int *y, int n) {
  for (int i = 0; i < n; i++) {
    x_tot[0] += x[i];
    y_tot[0] += y[i];
  }
}

Upvotes: 0

Richard Walters
Richard Walters

Reputation: 1474

In addition to the individual arrays x0, x1, etc., have an array of structures which hold the size and address of each array:

int x0[] ={4.0, 4.0, ...};
int x1[] ={224.0, 224.0, ...};
int x2[] ={446.0, 446.0, 446.0, ...};
int x3[] ={598.0, 598.0, ...};

#define ARRAY_SIZE(n) (sizeof(n) / sizeof(*n))

struct {
    int* array;
    size_t length;
} x[] = {
    {x0, ARRAY_SIZE(x0)},
    {x1, ARRAY_SIZE(x1)},
    {x2, ARRAY_SIZE(x2)},
    {x3, ARRAY_SIZE(x3)}
};

Then you can iterate through the x array and access each individual array along with its size.

Upvotes: 0

Dr.Tower
Dr.Tower

Reputation: 995

You could use an array of arrays:

numPaths = 4;
pathLengths[] = {12, 11, 13, 10};
int x0[] ={4.0, 4.0, ...}; 
int x1[] ={224.0, 224.0, ...};
. . .
int *xs[] = {x0, x1, x2, x3};
int *ys[] = {y0, y1, y2, y3};

int i, j;
for (i = 0; i < numPaths; ++i){
    for (j = 0; j< pathLenghts[i] ++j){
        x_tot = xs[i][j];
        y_tot = ys[i][j];
    }
}

Upvotes: 0

Tommy
Tommy

Reputation: 100622

C isn't a reflective language. The fact that x0 was called x0 and x1 was called x1 has been thrown away by the compiler. So there's no way for a piece of code to perform the operation 'find the array with name x1', which is most literally what your code would attempt to do. C's preprocessor is also probably a little too blunt for doing this cleanly during compilation.

So the best thing you can do is something like:

int *xs[] = {x0, x1, x2, x3};

for(int c < numPaths)
{
    int *thisX = xs[c];

    ... read from thisX[0], thisX[1], etc...
}

So what you've done is told the compiler to take the addresses of x0, x1, x2, etc, and put them into an array. That way you can look up the address of the array based on the index, then access the array from there.

Upvotes: 5

Related Questions