My task is to read data that's stored in string and double pairs, have the user input what it should be sorted by (in the form "n-v+") and then sort it with qsort. I'm having issues with writing my comparison function.
I'm getting the following errors:
Here's the part of code with the function
typedef struct {
char name[32];
double value;
} record;
int sign1=0; //1 is +, -1 is -
int tip1=0; //1 is n, 2 is v
int sign2=0; //1 is +, -1 is -
int tip2=0; //1 is n, 2 is v
//comparison function
int compa(const void*p,const void*d){ //1 means first, -1 means second
int result=0;
record first;
first=*p;
record second;
second=*d;
if(tip1==1){ //compare for n first
result=strcmp(first.name,second.name); //first goes smaller
if(result!=0) return sign1*result;
else { //then for v
if (first.value>second.value)result=-1;
else result=1;
return sign2*result;
}
}
else if (tip1==2){//compare for v first
if (first.value>second.value){
result=-1;
return sign1*result;}
else if (first.value<second.value) {
result=1;
return sign1*result;}
else{ //then for n
result=strcmp(first.name,second.name); //first goes smaller
if(result!=0) return sign2*result;
else return sign2;
}
}
}
I think it's probably best to write two pairs of functions. In each pair, one will be trivial, negating the result of calling the other one in the pair. For example, you have 'name ascending' and 'name descending'; you will probably implement 'name ascending' in full with prototype int cmp_name_asc(const void *p1, const void *p2);
, and then implement the other in the pair as:
int cmp_name_des(const void *p1, const void *p2)
{
return -cmp_name_asc(p1, p2);
}
and similarly for the pair of cmp_value_asc
and cmp_value_des
.
int cmp_value_des(const void *p1, const void *p2)
{
return -cmp_value_asc(p1, p2);
}
If you're really antsy about the mild difference in overhead, you can work around it with a static inline function that you call twice, once each in cmp_value_des()
and cmp_value_asc()
, but that is almost certainly not worthwhile.
That then leaves you with writing two functions:
int cmp_value_asc(const void *p1, const void *p2)
{
const record *r1 = p1;
const record *r2 = p2;
if (r1->value < r2->value)
return -1;
else if (r1->value > r2->value)
return +1;
else
return 0;
}
int cmp_name_asc(const void *p1, const void *p2)
{
const record *r1 = p1;
const record *r2 = p2;
return strcmp(r1->name, r2->name);
}
Then, when you're parsing the arguments, you control which of the four functions is called. This is much easier that futzing with those global variables, and making copies of the structures (avoid that whenever possible) and so on.
You were getting the 'warning: dereferencing ‘void *’ pointer
' messages because you have:
record first;
first=*p;
You'd need to use:
record first = *(const record *)p;
to get the right copying behaviour — convert the void *
to the correct pointer type and then dereference it. Except this copies the information; you shouldn't do that (it slows things down wholly unnecessarily). You should be using:
const record *firstp = (const record *)p;
except that the cast is not crucial in C (it would be in C++, if you were careless enough to sort this way in your C++ code — that would be a bad idea, too). Note too that this makes the initialization part of the variable definition, rather than as separate assignment later. That's usually a good technique — especially in C++.
Similarly, you were probably getting the 'error: void value not ignored as it ought to be
' errors on the same lines. Since p
is a void *
, *p
is a void
, and you were trying to use the result of a void
value — which you can't do.
The 'warning: control reaches end of non-void function [-Wreturn-type]
' warning is because your comparison function has the structure:
if (tip1 == 1)
{
…code that returns a value…
}
else if (tip1 == 2)
{
…code that returns a value…
}
and because there is neither an else
clause nor a return 0;
nor anything else after the else if
block, the function can, in principle, exit without returning a value, which is what the warning tells you.
I'll also observe you don't seem to use tip2
(should tip1 == 2
be tip2 != 0
?).
On further scrutiny, it seems like you might be intending to have two part comparisons, with sorting by name first and value second, or value first and name second, and for each of those comparisons, either ascending or descending. Is that correct?
If so, you can still create the small functions shown before, but make them static
since you won't be using them outside the comparison code. You can then decide to create function pointers to do the work:
static int (*compare1)(const void *p1, const void *p2) = cmp_name_asc;
static int (*compare2)(const void *p1, const void *p2) = cmp_value_des;
Your parsing code will select the correct function names for assignment to compare1
and compare2
. Your comparison function then becomes:
int cmpa(const void *p1, const void *p2)
{
int rc = (*compare1)(p1, p2); // Or int rc = compare1(p1, p2);
if (rc == 0)
rc = (*compare2)(p1, p2);
return rc;
}
It's a mild nuisance to have to use the file scope variables compare1
and compare2
, but much less of a nuisance than trying to do it all inline as in the code in the question.
It is pretty easy to extend this to sort by three criteria, or 1..N criteria (using an array of pointers to functions, and setting a pointer to null to indicate no more comparisons). The hardest part is setting the correct function pointers in compare1
and compare2
and equivalents.
Here is code showing most of the suggestions above (I'm not fussed about the minimal performance lost in the descending comparator functions):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
char name[32];
double value;
} record;
static int cmp_value_asc(const void *p1, const void *p2)
{
const record *r1 = p1;
const record *r2 = p2;
if (r1->value < r2->value)
return -1;
else if (r1->value > r2->value)
return +1;
else
return 0;
}
static int cmp_name_asc(const void *p1, const void *p2)
{
const record *r1 = p1;
const record *r2 = p2;
return strcmp(r1->name, r2->name);
}
static int cmp_name_des(const void *p1, const void *p2)
{
return -cmp_name_asc(p1, p2);
}
static int cmp_value_des(const void *p1, const void *p2)
{
return -cmp_value_asc(p1, p2);
}
static int (*compare1)(const void *p1, const void *p2) = cmp_name_asc;
static int (*compare2)(const void *p1, const void *p2) = cmp_value_des;
static int cmpa(const void *p1, const void *p2)
{
int rc = (*compare1)(p1, p2);
if (rc == 0)
rc = (*compare2)(p1, p2);
return rc;
}
static void dump_records(const char *tag, int num, const record *data);
int main(void)
{
record data[] =
{
/*
random -n 15 -c -T '{ "%C%v%2:8w", %[20:100]f },' |
awk '{ printf("%8s%s %-14s %5.2f %s\n", "", $1, $2, $3, $4, $5) }'
Plus three lines duplicated on name and then change the value;
plus three lines duplicated on value and then change the name.
*/
{ "Memrgi", 66.90 },
{ "Joeeeoahnm", 98.40 },
{ "Turner", 81.40 },
{ "Rebfno", 81.40 },
{ "Rebfno", 23.19 },
{ "Tecao", 66.30 },
{ "Guvoejnard", 62.40 },
{ "Zipnoib", 32.70 },
{ "Kerjruw", 49.60 },
{ "Vebin", 51.60 },
{ "Ghost", 51.60 },
{ "Reoe", 85.00 },
{ "Yepfenen", 84.60 },
{ "Yepfenen", 82.60 },
{ "Kopl", 94.80 },
{ "Soorwzeo", 15.40 },
{ "Soorwzeo", 85.40 },
{ "Nemigiat", 29.10 },
{ "Poisson", 79.40 },
{ "Sositpv", 79.40 },
{ "Giidahroet", 71.00 },
};
enum { NUM_DATA = sizeof(data) / sizeof(data[0]) };
dump_records("Before sorting", NUM_DATA, data);
compare1 = cmp_name_des;
compare2 = cmp_value_asc;
qsort(data, NUM_DATA, sizeof(data[0]), cmpa);
dump_records("After sorting by name descending, value ascending", NUM_DATA, data);
compare1 = cmp_value_des;
compare2 = cmp_name_asc;
qsort(data, NUM_DATA, sizeof(data[0]), cmpa);
dump_records("After sorting by value descending, name ascending", NUM_DATA, data);
compare1 = cmp_value_asc;
compare2 = cmp_name_asc;
qsort(data, NUM_DATA, sizeof(data[0]), cmpa);
dump_records("After sorting by value ascending, name ascending", NUM_DATA, data);
compare1 = cmp_name_asc;
compare2 = cmp_value_asc;
qsort(data, NUM_DATA, sizeof(data[0]), cmpa);
dump_records("After sorting by name ascending, value ascending", NUM_DATA, data);
return 0;
}
static void dump_records(const char *tag, int num, const record *data)
{
printf("%s (%d):\n", tag, num);
for (int i = 0; i < num; i++)
printf("%2d: %-12s %5.2f\n", i + 1, data[i].name, data[i].value);
}
Output from that program:
Before sorting (21):
1: Memrgi 66.90
2: Joeeeoahnm 98.40
3: Turner 81.40
4: Rebfno 81.40
5: Rebfno 23.19
6: Tecao 66.30
7: Guvoejnard 62.40
8: Zipnoib 32.70
9: Kerjruw 49.60
10: Vebin 51.60
11: Ghost 51.60
12: Reoe 85.00
13: Yepfenen 84.60
14: Yepfenen 82.60
15: Kopl 94.80
16: Soorwzeo 15.40
17: Soorwzeo 85.40
18: Nemigiat 29.10
19: Poisson 79.40
20: Sositpv 79.40
21: Giidahroet 71.00
After sorting by name descending, value ascending (21):
1: Zipnoib 32.70
2: Yepfenen 82.60
3: Yepfenen 84.60
4: Vebin 51.60
5: Turner 81.40
6: Tecao 66.30
7: Sositpv 79.40
8: Soorwzeo 15.40
9: Soorwzeo 85.40
10: Reoe 85.00
11: Rebfno 23.19
12: Rebfno 81.40
13: Poisson 79.40
14: Nemigiat 29.10
15: Memrgi 66.90
16: Kopl 94.80
17: Kerjruw 49.60
18: Joeeeoahnm 98.40
19: Guvoejnard 62.40
20: Giidahroet 71.00
21: Ghost 51.60
After sorting by value descending, name ascending (21):
1: Joeeeoahnm 98.40
2: Kopl 94.80
3: Soorwzeo 85.40
4: Reoe 85.00
5: Yepfenen 84.60
6: Yepfenen 82.60
7: Rebfno 81.40
8: Turner 81.40
9: Poisson 79.40
10: Sositpv 79.40
11: Giidahroet 71.00
12: Memrgi 66.90
13: Tecao 66.30
14: Guvoejnard 62.40
15: Ghost 51.60
16: Vebin 51.60
17: Kerjruw 49.60
18: Zipnoib 32.70
19: Nemigiat 29.10
20: Rebfno 23.19
21: Soorwzeo 15.40
After sorting by value ascending, name ascending (21):
1: Soorwzeo 15.40
2: Rebfno 23.19
3: Nemigiat 29.10
4: Zipnoib 32.70
5: Kerjruw 49.60
6: Ghost 51.60
7: Vebin 51.60
8: Guvoejnard 62.40
9: Tecao 66.30
10: Memrgi 66.90
11: Giidahroet 71.00
12: Poisson 79.40
13: Sositpv 79.40
14: Rebfno 81.40
15: Turner 81.40
16: Yepfenen 82.60
17: Yepfenen 84.60
18: Reoe 85.00
19: Soorwzeo 85.40
20: Kopl 94.80
21: Joeeeoahnm 98.40
After sorting by name ascending, value ascending (21):
1: Ghost 51.60
2: Giidahroet 71.00
3: Guvoejnard 62.40
4: Joeeeoahnm 98.40
5: Kerjruw 49.60
6: Kopl 94.80
7: Memrgi 66.90
8: Nemigiat 29.10
9: Poisson 79.40
10: Rebfno 23.19
11: Rebfno 81.40
12: Reoe 85.00
13: Soorwzeo 15.40
14: Soorwzeo 85.40
15: Sositpv 79.40
16: Tecao 66.30
17: Turner 81.40
18: Vebin 51.60
19: Yepfenen 82.60
20: Yepfenen 84.60
21: Zipnoib 32.70