24

Now, I have seen various examples, but I don't get what they mean.

Here's my structure

typedef struct profile{
    char gender[1];
    double soc;
       . . .
} PROFILE;

where soc is social security number that I'm going to be sorting by.

I know you need a compare function, but I don't know how to come up with the exact thing I need.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Julian Hernandez
  • 259
  • 1
  • 2
  • 3
  • 4
    `double` seems like a rather nonsensical type for a social security number. It should likely be `char [10]` (if you want to allow entry of not-strictly-numeric values) or `uint32_t`. – R.. GitHub STOP HELPING ICE May 24 '11 at 04:03
  • 1
    Don't let the naysayers bug you. `double` may not be ideal, but it's perfectly adequate for holding a 9-digit integer value. At least you won't run into the problem of rounded fractional representations. – Mark Ransom May 24 '11 at 04:11
  • 6
    @Mark Ransom: I hardly think nay-sayer is the appropriate term for pointing out incorrect design/code! Since when did a social security number have a fractional representation! – Mitch Wheat May 24 '11 at 04:13
  • @Mark Ransom: Social security numbers are like telephone numbers in that they are not really numbers at all but strings containing only digits. A char array is the most appropriate data type and a double is definitely wrong. – JeremyP May 24 '11 at 08:23
  • @Mitch Wheat, there's nothing wrong with pointing out that `double` isn't the best choice. It's not incorrect code though - there are no bugs that will result from that design choice, as long as the format of Social Security numbers does not change. I thought it was necessary to provide some balance. – Mark Ransom May 24 '11 at 13:38
  • @JeremyP, I agree that a char array would be a better data type, but the question wasn't about that aspect. I think it's going too far to say a double is "definitely wrong". – Mark Ransom May 24 '11 at 13:40
  • 1
    @Mark Ransom: I don't think there is any rule in Stack Overflow that prohibits the offering of unsolicited advice about topics not directly related to the question. If there is, I have breached it many times. Also, I disagree with you. Double is definitely wrong. – JeremyP May 24 '11 at 14:13
  • @JeremyP, nothing wrong with unsolicited advice, I do it too. It just struck me that the recommendations are a little too strong, implying that `double` simply won't work. You still seem to be saying that, and I'd like to know your rationale. The only lurking bug I've been able to imagine is losing the leading zeros. – Mark Ransom May 24 '11 at 15:07
  • 1
    @Mark Ransom: Yes, it will work, but it doesn't make much sense particularly when you look at the validation requirements for a US SSN. By the way, the British equivalent to an SSN is the NI number which actually does start with two alphas. – JeremyP May 24 '11 at 15:34

4 Answers4

39

Here is an example of using qsort for an array of structs in C

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

typedef struct {
    int price;
    int id;
} order;

int compare(const void *a, const void *b) {
  
    order *orderA = (order *)a;
    order *orderB = (order *)b;
  
    return (orderB->price - orderA->price);
}

int main() {
    order list[6];

    srand(time(NULL));

    printf("Before sorting\n");
    for (int i = 0; i < 6; i++) {   
        list[i].price = rand() % 10;
        list[i].id = i; 
        printf("Order id = %d Price = %d\n", list[i].id, list[i].price);            
    }

    qsort(list, 6, sizeof(order), compare);

    printf("AFTER sorting\n");
    for (int n = 0; n < 6; n++) {
        printf("Order id = %d Price = %d\n", list[n].id, list[n].price);    
    }       
    return 0;
}
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
koko.auth
  • 431
  • 4
  • 3
  • Very helpful, especially the `compare()`. :) – gsamaras Mar 07 '16 at 02:01
  • 2
    Your `compare` function is incorrect: the difference between prices may be larger than the range of type `int`. For example: `-3` and `INT_MAX` produces `INT_MAX + 3` for your expression that does not fit in type `int`, invokes undefined behavior and on most current hardware evaluates to a negative number although the comparison should return a positive number. – chqrlie Nov 11 '16 at 19:05
  • 1
    This orders from the biggest to the smallest. If you want the opposite you just have to change orderA and orderB on the return expression, correct? – Matheus Rotta Dec 01 '16 at 18:56
  • That's correct, i.e. you should change it to `return(orderA->price - orderB->price)` – volkan g Feb 04 '20 at 09:12
  • `return ( (*(order *)b).price > (*(order *)a).price ? 1 : -1);` concise one line may be easier to change order... – LinconFive Jan 12 '21 at 03:49
  • @LinconFive compare function should also be able to return 0 for equality. – qwr Jan 06 '23 at 22:13
  • 1
    A safe compare function would be would return `(orderA->price > orderB->price) - (orderA->price < orderB->price)`. See https://en.cppreference.com/w/c/algorithm/qsort for an example – qwr Jan 06 '23 at 22:15
14

Your Soc should almost certainly not be of type double, but anyway here's an example of what a compare function needs to return:

int compare(const void *p1, const void *p2)
{
    const struct profile *elem1 = p1;    
    const struct profile *elem2 = p2;

   if (elem1->soc < elem2->soc)
      return -1;
   else if (elem1->soc > elem2->soc)
      return 1;
   else
      return 0;
}

Thanks for pointing out the const void *.

Here is a complete example (archived): Sorting Structures with the C qsort() Function

Mitch Wheat
  • 295,962
  • 43
  • 465
  • 541
  • 1
    -1 because you can't pass this to `qsort` without a hairy cast, the parameters should be `const`, and this can be written more simply as `return (int)(elem1->soc - elem2->soc);` – Chris Lutz May 24 '11 at 04:32
  • @Mitch - Then the poster is using a weird compiler. But you fixed your code, so I retracted my downvote. – Chris Lutz May 24 '11 at 04:48
  • 1
    @ChrisLutz: the *simple* subtraction method is incorrect in the general case. See my answer for counter examples. – chqrlie Nov 11 '16 at 19:07
  • The MS article can now be found at https://jeffpar.github.io/kbarchive/kb/073/Q73853/ And notice the compare doesn't use void pointers. – Alan Corey Oct 28 '18 at 22:25
8

The strict version of a comparator takes two constant void pointers:

int compare(const void *v1, const void *v2)
{
    const struct profile *p1 = v1;
    const struct profile *p2 = v2;
    if (p1->gender > p2->gender)
        return(+1);
    else if (p1->gender < p2->gender)
        return(-1);
    else if (p1->soc > p2->soc)
        return(+1);
    else if (p1->soc < p2->soc)
        return(-1);
    else
        return(0);
}

This compares the gender field first, then the soc field. This is how you handle any multipart comparison.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Your comparison function is sexist! (Also, the `soc` comparison could be done as `return (int)(p1->soc - p2->soc);`. The cast is unnecessary if the OP (sanely) changes the type of `soc` to an `int`.) – Chris Lutz May 24 '11 at 04:33
  • 2
    @Chris: au contraire - my function is perfectly genteel; ladies before gentlemen, don't you know? The subtraction mechanism works as long as there can be no overflow; the comparison mechanism works regardless. – Jonathan Leffler May 24 '11 at 04:56
  • 2
    Spotted by researching a recent question, I am surprised at @ChrisLutz suggestion with his rep. +1 for JL. – Weather Vane Nov 11 '16 at 19:06
  • Why do you have to create pointers of the type you expect `v1` and `v2` to be? (Why can't they be standard variables). Why have you set the pointers to `const`? (I tried it without `const` and it worked fine.) – Connor Mar 31 '23 at 11:39
  • 1
    @Connor: You can make local variables of the appropriate type but that makes an unnecessary copy of what might be a large structure. The code isn't going to modify what it looks at (and mustn't modify it) so the pointers should be to constant data. The const pointers in the interface are mandated by the standard. You can cast away const-ness, but why would you want to do so? It will work, but it isn't as safe. – Jonathan Leffler Mar 31 '23 at 13:14
4

To sort the array, use qsort() and pass a comparison function.

Here is one that produces the correct result for all possible values of the price member:

typedef struct profile {
    char gender[1];
    double soc;
    int price;
    ...
} PROFILE;

int compare_price(const void *a, const void *b) {
    const PROFILE *oa = a;
    const PROFILE *ob = b;

    return (oa->price > ob->price) - (oa->price < ob->price);
}

int compare_soc(const void *a, const void *b) {
    const PROFILE *oa = a;
    const PROFILE *ob = b;

    return (oa->soc > ob->soc) - (oa->soc < ob->soc);
}

Notes:

  • the simple subtraction of values produces incorrect results if the difference does not fit in the int type. For example -2 and INT_MAX cannot be correctly compared with the subtraction method. It would not work for floating point values either.

  • the above method can be used for all comparable types, including double except for NaN.

If you wish to handle NaN, here is how to group them at the end:

#include <math.h>

int compare_soc_nan_at_the_end(const void *a, const void *b) {
    const PROFILE *oa = a;
    const PROFILE *ob = b;

    if (isnan(oa->soc)) {
        return isnan(ob->soc) ? 0 : 1;
    } else
    if (isnan(ob->soc)) {
        return -1;
    } else {
        return (oa->soc > ob->soc) - (oa->soc < ob->soc);
    }
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189