16

I'm just wondering if there's a way for me to pass an extra parameter to my comparator which will then be used in my qsort function?

For example I have these 2 comparators (one in ascending order, and one in descending)

qsort(entries, 3, sizeof(struct entry), compare_desc);

int compare_asc(const void *elem1, const void *elem2)
{
     return strcmp(elem1.name.last, elem2.name.last);
}


int compare_desc(const void *elem1, const void *elem2)
{
     return strcmp(elem2.name.last, elem1.name.last);
}

Is there a way so I can do something like this instead:

int compare(const void *elem1, const void *elem2, const char *order)
{
     if (strcmp(order, "asc") == 0)
         return strcmp(elem1.name.last, elem2.name.last);
     else if (strcmp(order, "desc") == 0)
         return strcmp(elem2.name.last, elem1.name.last);
}

Reason I ask is that my sort program has to take switches and if I have 2 different switches (+a, -a) for ascending and descending respectively, then I have to make 2 different comparator functions. If I add more, it gets more complicated. Is there a way to improve the design of this program?

EDIT: No global & extern variables allowed.

Jugo Monte
  • 161
  • 1
  • 4
  • 1
    I hope that's not what your code looks like - you can't access the `.name` member of a `void *`. – Chris Lutz Nov 18 '10 at 01:12
  • BTW, if you are worried about adding further options, note that you don't necessarily need to have the ascending and descending versions of every function—you can always reverse the entire array once sorted. – Arkku Nov 18 '10 at 01:21
  • 1
    There are good answers below but the key point is that the qsort() library code will only ever pass two values to your comparator. You have control over the function pointer you give to qsort() but not how the callback is performed. – Blastfurnace Nov 18 '10 at 02:03
  • I've also just met this problem, found quite a simple solution: int foo(int a,int b,int c){ return (a-b)*c; } int main(){ int arr[]={3,5,1,7,9}; int extraparam=6,i; for(i=0;i<5;++i){ printf("%d ",arr[i]); } printf("\n"); int cmp(const void *a,const void *b){ return foo(*(int*)a,*(int*)b,extraparam); } qsort(arr,5,sizeof(int),cmp); for(i=0;i<5;++i){ printf("%d ",arr[i]); } printf("\n"); } output: 3 5 1 7 9 1 3 5 7 9 Works quite nicely – Ariana Aug 23 '18 at 15:01
  • You *really* don't want to be executing a `strcmp()` on every comparison in a Quicksort, or even an extraneous `if`. You know ahead of time whether you want ascending or descending: provide the appropriate comparator. – user207421 Dec 06 '19 at 00:59

9 Answers9

11

Old question, but in case someone stumbles upon it...

There are non-standard versions of qsort() that let you pass an extra parameter to the callback function. glib offers qsort_r() while VC gives you qsort_s().

cleong
  • 7,242
  • 4
  • 31
  • 40
  • Note that there are different signatures on different platforms for both `qsort_r()` and `qsort_s()` (and most platforms only provide one of `qsort_r()` or `qsort_s()`). You have to know which function is available, and what its calling sequence is, and what the comparator calling sequence is. See [my answer](https://stackoverflow.com/a/46042467/15168) for the sordid details. – Jonathan Leffler Dec 01 '19 at 18:25
8

In your example case it is better to have two different comparators. If you had just the one, every comparison would unnecessarily have to determine the sort order, which you couldn't change mid-sort anyhow for any meaningful results. So instead of putting the if (ascending_sort) { } else { } inside the comparator, put it at your qsort call:

qsort(e, n, sizeof(*e), (strcmp(order, "asc") ? compare_desc : compare_asc));

Edit: Some tips if you add more comparators:

– remember that you don't need to re-write every comparator; you can have them call one another if you are sorting on multiple fields (and you can always invert the result of a comparator with -, e.g., compare_asc(a, b) can return -compare_desc(a, b)).

– it's easy to reverse the order of the entire array in the end so you don't need to double your number of comparators for supporting an option to reverse the entire sort order

– you can replace the trinary operator (? :) in my example with a function that returns the appropriate comparator as suggested in the comments below

Arkku
  • 41,011
  • 10
  • 62
  • 84
  • Although actually, we should probably say `int (*sort_type(const char *))(const void *, const void *) { if(!strcmp(rder, "asc") return compare_asc; return compare_desc; }` to simplify the sorting-call to `qsort(e, n, sizeof *e, sort_type("asc"));` (which will also make it easier to add new sorting types). – Chris Lutz Nov 18 '10 at 01:26
  • Yeah, that's a good alternative if there are more than two options. – Arkku Nov 18 '10 at 01:28
  • It's a good alternative even if there are only two, because it enables you to add more options or adjust how the options are read without having to modify every call to `qsort`. – Chris Lutz Nov 18 '10 at 01:29
  • Ok, I was basically assuming that there's only the one call to `qsort`. – Arkku Nov 18 '10 at 01:42
6

qsort_r() and qsort_s()

There are functions called qsort_r() or qsort_s() available in some implementations that do what you want — take a pointer to extra data that is passed to the comparator functions.

The BSD variant implementations (including macOS or Mac OS X) provide a version of qsort_r(), and so does the GNU C library. Unfortunately, the two variants have different signatures. That doesn't stop them being useful, but it does mean that the same source code cannot be used on the two platforms, and further that you have to make sure you understand which variant of qsort_r() is available on any machine where you try to use it.

Similarly, Microsoft provides a version of qsort_s() and the C11 standard defines a version of qsort_s() (as an optional function in Annex K, based on TR-24731), but the two differ in the signature again. Maybe it is fortunate that the Annex K functions are not widely implemented.

BSD qsort_r()

void qsort_r(void *base, size_t nel, size_t width, void *thunk,
             int (*compar)(void *, const void *, const void *));

GNU C library qsort_r()

void qsort_r(void *base, size_t nmemb, size_t size,
             int (*compar)(const void *, const void *, void *),
             void *arg);

Note that in BSD, the 'thunk' is equivalent to 'arg' in GNU, but these arguments appear in different places in the calling sequence to the qsort_r() function (before and after the comparator function pointer). Further, note that the 'thunk' is passed as argument 1 to the BSD comparator functions, but the 'arg' is passed as argument 3 to the GNU comparator functions.

Mnemonic for qsort_r: the context data is specified in relation to the comparator in the calling sequence in the same relation as the context is passed to the comparators in relation to the two values being compared. Context before pointer to comparator means context before values in call to comparator; context after pointer to comparator means context after values in call to comparator.

Annex K qsort_s()

errno_t qsort_s(void *base, rsize_t nmemb, rsize_t size,
               int (*compar)(const void *x, const void *y, void *context),
               void *context);

The Annex K qsort_s() is unique in returning a value; all the other variants do not return any value. Otherwise, for most practical purposes, it matches the GNU qsort_r() function.

Microsoft qsort_s()

void qsort_s(void *base, size_t num, size_t width,
             int (__cdecl *compare )(void *, const void *, const void *),
             void * context);

The rsize_t and size_t distinction is not very important when comparing the Annex K and Microsoft variants of qsort_s(), but in the Annex K qsort_s(), the context is passed as argument 3 to the comparator, but in the Microsoft qsort_s(), the context is passed as argument 1 to the comparator.

Summary

Functions called qsort_r() or qsort_s() provide the required functionality. However, you must check the platform specification for which function is present, and for the correct calling sequence for the arguments to the sorting function, and the correct calling sequence for the arguments to the comparators.

Nominally, you should check for the return type of the function too, but few programs would consider checking it, mainly because most variants of qsort() return no value.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
2

Without using a global variable, AFAIK in general you can't, you have to provide two different functions for the two sorting methods. Actually this is one of the reasons why in C++ functors (objects that provide an overloaded function-call operator) are often used.

Community
  • 1
  • 1
Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
2

What you should do is switch the arguments to qsort so that you pass a function pointer as appropriate.

Given your scenario, it might be something like

// selectively invoke qsort:
if(strcmp(var, "+a")){
    qsort(entries, 3, sizeof(struct entry), compare_asc);
}else{
    qsort(entries, 3, sizeof(struct entry), compare_desc);
}

Or alternatively you can do something like:

// declare a function pointer
int (*func)(const void*, const void*);

// sometime later decide which function to assign
// to the function pointer
if(strcmp(var, "+a")){
    func = compare_asc;
}else{
    func = compare_Desc;
}

// sometime later invoke qsort
qsort(entries, 3, sizeof(struct entry), compare_desc);
Mark Elliot
  • 75,278
  • 22
  • 140
  • 160
  • I would put the code to select the sorting function into a separate function (called, say `sort_type`), then just `qsort(entries, 3, sizeof *entries, sort_type(user_type));` – Chris Lutz Nov 18 '10 at 01:28
2

> Is there a way to improve the design of this program?

Don't do this -- this is not a design improvement, it's just an experiment.

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

int comparefx(const void *a, const void *b) {
    static int extra = 0;
    if (a == NULL) {
        extra = (int)b;
        return 0;
    }
    switch (extra) {
        case 24: puts("24"); return *(const int*)a + *(const int*)b; break;
        case 42: puts("42"); return *(const int*)b - *(const int*)a; break;
        default: puts("--"); return *(const int*)a - *(const int*)b; break;
    }
}

int main(void) {
    int entries[] = {4, 2, 8};

    qsort(entries, 3, sizeof *entries, comparefx);
    printf("%d %d %d\n", entries[0], entries[1], entries[2]);

    comparefx(NULL, (void*)42); /* set 'extra' parameter */
    qsort(entries, 3, sizeof *entries, comparefx);
    printf("%d %d %d\n", entries[0], entries[1], entries[2]);

    return 0;
}

It compiles "cleanly" with 3 compilers

$ gcc -std=c89 -pedantic -Wall 4210689.c
4210689.c: In function 'comparefx':
4210689.c:7: warning: cast from pointer to integer of different size

$ clang -std=c89 -pedantic -Wall 4210689.c
$ tcc -Wall 4210689.c
$ 

And it runs as expected

$ ./a.out
--
--
--
2 4 8
42
42
42
8 4 2
pmg
  • 106,608
  • 13
  • 126
  • 198
1

In simple cases, you can use a global variable.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
0

A lack of classes and closures pretty much means that you're stuck with writing a separate comparator for each different type of comparison you want.

One thing you could do is have each element of the array be a struct, containing value and sort_order fields. All the sort_order fields would be the same... but that's worse than just having 2 comparators.

Think of it this way: you end up writing all the same comparator code anyway. But instead of having a complex nested if/else with 8 cases, you have 8 functions. The difference is some extra function declarations.

EDIT: To reply to R's comments.. that's a good point. I had this before but I deleted it:

You can create a framework similar to Python's list.sort() function. Pretty much:

  • Create a struct with value and sortvalue fields.
  • Put initial values in value.
  • Write whatever code to transform the items into the sortvalue field.
  • Use a standard comparator on that, along with qsort.
  • When you're done, just take the elements out of value fields. They'll be sorted according to sortvalue but the values will be correct.

This is used in Python for example, where if you want to sort by say the 4th item in a tuple, you don't write a whole comparator (like lambda v1,v2: v1[3]-v2[3]), but instead just transform the inputs with a key function (lambda k: k[3]) and use a standard sort method. It would work in the case of the "billions of sorts", since your code can do whatever complicated operation with however many inputs to transform the values.

Claudiu
  • 224,032
  • 165
  • 485
  • 680
  • But what if there are billions of possible comparison functions dependent on an integer or floating-point number (e.g. some sort of weight or offset)? The lack of closures (or equivalent functionality) for `qsort` and the like is a major problem in C. In my opinion, the only solution (I don't consider global variables a solution) is to write your own sort framework if you need closure support and pray that it runs as fast or faster than the system's `qsort` on all your targets. – R.. GitHub STOP HELPING ICE Nov 18 '10 at 03:18
-1

Just use a lambda function for closure. Something like this C++ code:

string sortOrder="asc";   
qsort(entries, 3, sizeof(struct entry),    
[=](const void *elem1, const void *elem2) -> int{
        myCompare(elem1,elem2,sortOrde)             

});
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
O_Z
  • 1,515
  • 9
  • 11