23

I'm assuming that the good old qsort function in stdlib is not stable, because the man page doesn't say anything about it. This is the function I'm talking about:

   #include <stdlib.h>
   void qsort(void *base, size_t nmemb, size_t size,
              int(*compar)(const void *, const void *));  

I assume that if I change my comparison function to also include the address of that which I'm comparing, it will be stable. Is that correct?

Eg:

int compareFoos( const void* pA, const void *pB ) {
    Foo *pFooA = (Foo*) pA;
    Foo *pFooB = (Foo*) pB;

    if( pFooA->id < pFooB->id ) {
        return -1;
    } else if( pFooA->id > pFooB->id ) {
        return 1;
    } else if( pA < pB ) {
        return -1;            
    } else if( pB > pA ) {
       return 1;
    } else {
       return 0;
    }
}   
alk
  • 69,737
  • 10
  • 105
  • 255
twk
  • 16,760
  • 23
  • 73
  • 97
  • 1
    I don't understand why you would compare the pointers. And what do you mean by stable (excuse my ignorance). Maybe you could elaborate in your question. – jmatthias Feb 25 '09 at 04:13
  • 4
    By stable he means that is items a compares equal to item b, and a initially comes before b in the array, it will *still* come before b in the sorted array. Term of Art in sorting circles, and the reason for the hack of comparing the addresses. Very neat. – dmckee --- ex-moderator kitten Feb 25 '09 at 04:15
  • 3
    Very neat idea, @dmckee, but unfortunately not stable since twk is using current addresses rather than starting addresses :-) – paxdiablo Feb 25 '09 at 04:19
  • @paxdiablo: Not only is it not stable; it also invokes undefined behavior by violating the constraints of the comparison function. In particular, it could cause some implementations of `qsort` to go into an infinite loop or even perform out-of-bound writes when permuting the array. – R.. GitHub STOP HELPING ICE Apr 24 '12 at 02:23
  • Honestly, just use an external, stable sort function :) – Mahmoud Al-Qudsi Apr 24 '12 at 02:37
  • You can get away with just redefining the comparison. Bitshift up whatever comparison you want to make sufficient number of bits and then add starting index which end up in least significant bits. – mathreadler Aug 29 '18 at 07:50

3 Answers3

33

No, you cannot rely on that unfortunately. Let's assume you have the array (two fields in each record used for checking but only first field used for sorting):

B,1
B,2
A,3

A non-stable sort may compare B,1 with A,3 and swap them, giving:

A,3
B,2
B,1

If the next step were to compare B,2 with B,1, the keys would be the same and, since B,2 has an address less than B,1, no swap will take place. For a stable sort, you should have ended up with:

A,3
B,1
B,2

The only way to do it would be to attach the starting address of the pointer (not its current address) and sort using that as well as the other keys. That way, the original address becomes the minor part of the sort key so that B,1 will eventually end up before B,2 regardless of where the two B lines go during the sorting process.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 3
    Ah, good call. I knew my spider sense was tingling for a reason. – twk Feb 25 '09 at 04:21
  • and even if you would compare using the original addresses, it's not guaranteed to work: nothing says that qsort has to do that second compare of the two equal values anymore. for an unstable algorithm, the sequence in the second snapshot is already sorted completely. – Johannes Schaub - litb Feb 25 '09 at 04:49
  • 1
    @litb -- I'm not sure what you mean. With the compare function I posted, there are no "equal" values. – twk Feb 25 '09 at 04:52
  • Don't think I agree with that, @litb. If you added starting address to the compare function (equivalent to adding the 1/2/3 above), snapshot 2 wouldn't be sorted. – paxdiablo Feb 25 '09 at 04:55
  • dammit. you're right of course. i looked at it without considering the compare function but just looking how BBBB and BBBB is the same :) now it makes even sense to tired litb that it would work then :) – Johannes Schaub - litb Feb 25 '09 at 05:04
  • 1
    'Sokay, @litb, advantage of working on the other side of the planet from everyone else is that I'm in full flight when everyone else is getting tired :-) – paxdiablo Feb 25 '09 at 05:14
13

The canonical solution is to make (i.e. allocate memory for and fill) an array of pointers to the elements of the original array, and qsort this new array, using an extra level of indirection and falling back to comparing pointer values when the things they point to are equal. This approach has the potential side benefit that you don't modify the original array at all - but if you want the original array to be sorted in the end, you'll have to permute it to match the order in the array of pointers after qsort returns.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
1

This does not work because during the sort procedure, the ordering will change and two elements will not have consistent output. What I do to make good old-fashioned qsort stable is to add the initial index inside my struct and initialize that value before passing it to qsort.

typedef struct __bundle {
    data_t some_data;
    int sort_score;
    size_t init_idx;
} bundle_t;

/*
 .
 .
 .
 .
*/

int bundle_cmp(void *ptr1, void *ptr2) {
    bundle_t *b1, *b2;
    b1 = (budnel_t *) ptr1;
    b2 = (budnel_t *) ptr2;
    if (b1->sort_score < b2->sort_score) {
        return -1;
    }
    if (b1->sort_score > b2->sort_score) {
        return 1;
    }
    if (b1->init_idx < b2->init_idx) {
        return -1;
    }
    if (b1->init_idx > b2->init_idx) {
        return 1;
    }
    return 0;
}

void sort_bundle_arr(bundle_t *b, size_t sz) {
    size_t i;
    for (i = 0; i < sz; i++) {
        b[i]->init_idx = i;
    }
    qsort(b, sz, sizeof(bundle_t), bundle_cmp);
}
milaniez
  • 1,051
  • 1
  • 9
  • 21