jian
jian

Reputation: 4867

qsort mulit criteria sort condition in c

Context: String array Sorting C and qsort with function comparator different

Full code on Compiler Explorer.

char *str[ROWS][COLS] = {{"Russia", "Boxing", "Mens", "Gold"},
                {"America", "Cycling", "Mens", "Gold"},
                {"New Zealand", "Swimming", "Womens", "Silver"},
                {"India", "Badminton", "Mens", "Bronze"}};

 qsort(str,ROWS,sizeof(*str),(compare_rows));

static int compare_rows(const void * a,
  const void * b) {
  const int sort_colmn = 0;
  char * x1 = ((char ** ) a)[sort_colmn];
  char * x2 = ((char ** ) b)[sort_colmn];

  if (x2 == "India") return 1;

  if (x1 > x2 && x2 != "India") return 1;
  else if (x1 == x2) return 0;
  else return -1;

}

I'm trying to replicate the following SQL sort:

select * from (values('Russia'),('America'),('New Zealand'),('India')) cte(a) 
order by a='India' desc, a asc;

Desired order:

+-------------+
|      a      |
+-------------+
| India       |
| America     |
| New Zealand |
| Russia      |
+-------------+

Upvotes: 0

Views: 73

Answers (2)

Fe2O3
Fe2O3

Reputation: 8354

Your 2D array str is a contiguous collection of (16) memory addresses, each 'ptr' being independent of the other... qsort operates in chunks, be that integers, pointers or structs.

To use qsort clearly, first bind your 4 'columns' together into single chunks:

typedef struct {
    char *nation;
    char *sport;
    char *gender;
    char *medal;
} res_t;

res_t results[] = {
    {"Russia", "Boxing", "Mens", "Gold"},
    {"America", "Cycling", "Mens", "Gold"},
    {"New Zealand", "Swimming", "Womens", "Silver"},
    {"India", "Badminton", "Mens", "Bronze"}
};

Now, results is an array of 4 structs. Now, you can invoke qsort().

qsort( results, sizeof(results)/sizeof(results[0]), sizeof( results[0] ), compare );

sizeof( results[0] ) is the size of one element, and sizeof(results)/sizeof(results[0]) is the (compiler calculated) number of elements ('rows').

Your special-purpose comparison function can be made much simpler (and effective)...

static int compare( const void *a, const void *b ) {
    res_t *p1 = (res_t *)a;
    res_t *p2 = (res_t *)b;

    char *alwaysFirst = "India";
    if( strcmp( p1->nation, alwaysFirst ) == 0 ) return -1;
    if( strcmp( p2->nation, alwaysFirst ) == 0 ) return  1;

    return strcmp( p1->nation, p2->nation );
}

Upvotes: 2

Adrian Mole
Adrian Mole

Reputation: 51864

In your compare_rows function, you are attempting to compare strings by comparing their addresses. You can't do that.

For example, the comparison x2 == "India" is unlikely to be true, unless x2 points to a 'merged' value of the string literal on the RHS of that comparison. Similar conditions exist for your other pointer comparisons.

To compare the strings pointed to by the given char* operands, you need to call the strcmp function:

static int compare_rows(const void* a, const void* b)
{
    const int sort_colmn = 0;
    // Note the added use of `const` for the pointers and casts ...
    const char* x1 = ((char*const*)a)[sort_colmn];
    const char* x2 = ((char*const*)b)[sort_colmn];

    if (strcmp(x1, "India") == 0) return -1; // Need to also add this test!
    if (strcmp(x2, "India") == 0) return 1;

    if (strcmp(x1, x2) > 0) return 1; // Comparing x2 to "India" here is redundant
    else if (strcmp(x1, x2) == 0) return 0;
    else return -1;
//  A much simpler way to encode the above three lines is just:
//  return strcmp(x1, x2);
}

Upvotes: 2

Related Questions