Jake
Jake

Reputation: 41

Sort dynamically allocated 2D array by first column in C

I have a large 2D array of doubles I would like to sort by first column. The array requires dynamic memory allocation as it is large (2GB).

Below is a simplified example of my broken code using random numbers for example purposes.

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

int Double_Compare_Function ();

int main()
{
    srand(time(0));
    int p = 0; //property index
    int number_properties = 6; // number_properties
    long long n = 0; //node index
    long long number_nodes = 5; //number of nodes

    /* Declare array */
    double **properties_array;
    properties_array = malloc(number_nodes * sizeof(double*));
    for (n=0; n<number_nodes; n++) {
        properties_array[n] = malloc(number_properties * sizeof(double));
    }

    /* Fill array with numbers */
    for (n=0; n<number_nodes; n++) {
        for (p=0; p<number_properties; p++) {
            properties_array[n][p] = rand();
        }
    }

    printf("Initial array...\n");
    for (n=0; n<number_nodes; n++) {
        printf("%lli: ", n);
        for (p=0; p<number_properties; p++){
            printf("%.1f ", properties_array[n][p]);
        }
        printf("\n");
    }

    /* Sort array */
    qsort(properties_array, (int)number_nodes, number_properties*sizeof(double), Double_Compare_Function);

    printf("Sorted array...\n");
    for (n=0; n<number_nodes; n++) {
        printf("%lli: ", n);
        for (p=0; p<number_properties; p++){
            printf("%.1f ", properties_array[n][p]);
        }
        printf("\n");
    }

    return(0);
}

int Double_Compare_Function (const void * a, const void * b) {
    if (*(double*)a > *(double*)b) return 1;
    else if (*(double*)a < *(double*)b) return -1;
    else return 0;
}

Output

The program compiles without error (aside from random number generation warning). It appears to be pointing at memory I don't intend to.

Initial array...
0: 17189.0 13476.0 24803.0 23588.0 9169.0 13351.0
1: 20992.0 15638.0 23138.0 8580.0 32516.0 24064.0
2: 27139.0 23745.0 19237.0 19279.0 19262.0 25303.0
3: 19407.0 24529.0 23675.0 3102.0 23878.0 5831.0
4: 15299.0 3845.0 27278.0 17467.0 28106.0 6918.0
Sorted array...
0: 17189.0 13476.0 24803.0 23588.0 9169.0 13351.0
1: 19262.0 25303.0 13104405306123376000000000000000000000000.0 0.0 32516.0 24064.0
2: 27139.0 23745.0 1361751537953832600000000000000000000000000000000000000000000000000000.0 0.0 20992.0 15638.0
3: 19407.0 24529.0 23675.0 3102.0 23878.0 5831.0
4: 15299.0 3845.0 27278.0 17467.0 28106.0 6918.0

Question answered.

Upvotes: 1

Views: 256

Answers (3)

molbdnilo
molbdnilo

Reputation: 66441

The actual arguments for the comparisons are double**, not double*.
(You get a pointer to each element, not the element itself.)

If you want to sort on the first element, use something like this:

int Compare_Function (const void * a, const void * b) {
    double* array_a = *(double**)a;
    double* array_b = *(double**)b;
    return (int) (array_a[0] - array_b[0]);
}

Upvotes: 0

David C. Rankin
David C. Rankin

Reputation: 84569

Your problem is you cannot qsort an object made from pointer-to-pointer as an array because there is no requirement that the allocated address be contiguous in memory. Instead you need to declare and allocate a pointer-to-array of double [number_properties] and allocate for both nodes and properties in a single call to ensure the object is contiguous in memory, e.g.

    /* Declare array */
    double (*properties_array)[6];
    properties_array = malloc(number_nodes * number_properties * sizeof(double));
    if (!properties_array) {
        perror ("malloc-properties_array");
        return 1;
    }

(note: that is technically a pointer-to-VLA of double [number_properties] unless an integer constant is used to specify the number_properties -- which is unclear from the way you declare number_properties, but then use an integer constant in your code. Just be aware if you choose to use a VLA in your code that feature is an implementation defined feature as of C11)

Now your qsort compare can be written:

int Double_Compare_Function (const void * a, const void * b) {
    if (*(double * const *)a > *(double * const *)b) return 1;
    else if (*(double * const *)a < *(double * const *)b) return -1;
    else return 0;
}

Example Use/Output

$ ./bin/doublecmp
Initial array...
0: 2058999144.0 1013160096.0 499880968.0 1375376710.0 1398189150.0 579626176.0
1: 35952305.0 349854458.0 1000340925.0 1397136257.0 2028006902.0 877319625.0
2: 579560718.0 745830077.0 766399485.0 1052819099.0 1279742925.0 80594279.0
3: 390212763.0 603717917.0 1542566382.0 654797188.0 957950686.0 807072250.0
4: 1163825233.0 1748173998.0 261624942.0 152991913.0 269595164.0 2130895736.0
Sorted array...
0: 35952305.0 349854458.0 1000340925.0 1397136257.0 2028006902.0 877319625.0
1: 390212763.0 603717917.0 1542566382.0 654797188.0 957950686.0 807072250.0
2: 579560718.0 745830077.0 766399485.0 1052819099.0 1279742925.0 80594279.0
3: 1163825233.0 1748173998.0 261624942.0 152991913.0 269595164.0 2130895736.0
4: 2058999144.0 1013160096.0 499880968.0 1375376710.0 1398189150.0 579626176.0

Shorthand Compare Function

Note, you can also write the same double-compare function as a single return of the result of your conditionals, e.g.

int Double_Compare_Function (const void * a, const void * b)
{
    return (*(double * const *)a > *(double * const *)b) -
           (*(double * const *)a < *(double * const *)b);
}

Upvotes: 1

KamilCuk
KamilCuk

Reputation: 141463

You would want to sort the pointers.

Also you are missing #include <time.h>

Below I added the missing include, limited printing to only the first element of the array and limited the rand() output with % 100, so the numbers are smaller.

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

int Double_Compare_Function(const void *, const void *);

int number_properties = 6; // number_properties

int main()
{
    srand(time(0));
    int p = 0; //property index
    long long n = 0; //node index
    long long number_nodes = 5; //number of nodes

    /* Declare array */
    double **properties_array;
    properties_array = malloc(number_nodes * sizeof(double*));
    for (n=0; n<number_nodes; n++) {
        properties_array[n] = malloc(number_properties * sizeof(double));
    }

    /* Fill array with numbers */
    for (n=0; n<number_nodes; n++) {
        for (p=0; p<number_properties; p++) {
            properties_array[n][p] = rand() % 100;
        }
    }

    printf("Initial array...\n");
    for (n=0; n<number_nodes; n++) {
        printf("%lli: ", n);
        for (p=0; p<1; p++){
            printf("%.1f ", properties_array[n][p]);
        }
        printf("\n");
    }

    /* Sort array */
    qsort(properties_array, number_nodes, sizeof(double*),
    Double_Compare_Function);

    printf("Sorted array...\n");
    for (n=0; n<number_nodes; n++) {
        printf("%lli: ", n);
        for (p=0; p<1; p++){
            printf("%.1f ", properties_array[n][p]);
        }
        printf("\n");
    }

    return(0);
}

int Double_Compare_Function (const void * a, const void * b) {
  const double * const *z = a;
  const double * const *y = b;
  // the z is a pointer to an array of doubles
  // let's get the first element
  const double k = (*z)[0];
  const double m = (*y)[0];
  return k > m ? 1 : k < m ? -1 : 0;
}

The Double_Compare_Function receives two pointers that have the same type as properties_array - it's a double**. You need to dereference is twice. As each properties_array elements points to value returned by malloc(number_properties * sizeof(double)), I wanted to use the [0] to make it verbose I mean the first element of an array. The (*z)[0] is just the same as **z.

Example execution:

Initial array...
0: 3.0
1: 94.0
2: 35.0
3: 24.0
4: 71.0
Sorted array...
0: 3.0
1: 24.0
2: 35.0
3: 71.0
4: 94.0

Upvotes: 1

Related Questions