qsort

J

John Smith

I'm trying to figure out qsort(). I haven't seen any practical examples,
only synopsis. In the code below, the array is not sorted. Can someone
give me some help?

#include <stdio.h>
#include <stdlib.h>
int compare(const void* a, const void* b);

int main(void)
{
int idx;
int array[] = {243, 12, 99, 106, 122, 77, 242};

qsort(array, 7, 4, &compare);
for(idx=0; idx<7; ++idx)
printf("%d\t", array[idx]);
printf("\n");

return 0;
}

int compare(const void* a, const void* b)
{
if(a < b) return -1;
if(a == b) return 0;
if(a > b) return 1;
}
 
E

Eric Sosman

John said:
I'm trying to figure out qsort(). I haven't seen any practical examples,
only synopsis. In the code below, the array is not sorted. Can someone
give me some help?

#include <stdio.h>
#include <stdlib.h>
int compare(const void* a, const void* b);

int main(void)
{
int idx;
int array[] = {243, 12, 99, 106, 122, 77, 242};

qsort(array, 7, 4, &compare);

The `&' is harmless, but unnecessary. The `4' may
be true for your C implementation, but is not universal:
each C implementation chooses its own "best" size for
`int', and different implementations choose differently.
For portability, write `sizeof array[0]' instead.
for(idx=0; idx<7; ++idx)
printf("%d\t", array[idx]);
printf("\n");

return 0;
}

int compare(const void* a, const void* b)
{
if(a < b) return -1;
if(a == b) return 0;
if(a > b) return 1;
}

Here's your difficulty. The comparison function's
arguments are not two array elements, but pointers to
two array elements. You are comparing the pointers, but
you want to compare the pointed-to objects. Here is one
way to do it:

int compare(const void *a, const void *b) {
int u = *(const int*)a;
int v = *(const int*)b;
if (u < v) ...
 
A

Andrey Tarasevich

John said:
I'm trying to figure out qsort(). I haven't seen any practical examples,
only synopsis. In the code below, the array is not sorted. Can someone
give me some help?

#include <stdio.h>
#include <stdlib.h>
int compare(const void* a, const void* b);

int main(void)
{
int idx;
int array[] = {243, 12, 99, 106, 122, 77, 242};

qsort(array, 7, 4, &compare);
for(idx=0; idx<7; ++idx)
printf("%d\t", array[idx]);
printf("\n");

return 0;
}

int compare(const void* a, const void* b)
{
if(a < b) return -1;
if(a == b) return 0;
if(a > b) return 1;
}

Inside your 'compare' implementation you are comparing pointers instead
of comparing the values pointed by those pointers. You are supposed to
do the latter, not the former. For example, you can do it as follows

int compare(const void* a, const void* b)
{
int ia = *(const int*) a;
int ib = *(const int*) b;
return (ia > ib) - (ia < ib);
}

Also, it makes more sense to form arguments of 'qsort' by using
'sizeof', instead of specifying concrete values explicitly

qsort(array, sizeof array / sizeof *array, sizeof *array, &compare);
 
P

pete

John said:
I'm trying to figure out qsort(). I haven't seen any practical examples,
only synopsis. In the code below, the array is not sorted. Can someone
give me some help?

#include <stdio.h>
#include <stdlib.h>
int compare(const void* a, const void* b);

int main(void)
{
int idx;
int array[] = {243, 12, 99, 106, 122, 77, 242};

qsort(array, 7, 4, &compare);
for(idx=0; idx<7; ++idx)
printf("%d\t", array[idx]);
printf("\n");

return 0;
}

int compare(const void* a, const void* b)
{
if(a < b) return -1;
if(a == b) return 0;
if(a > b) return 1;
}

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

#define NELM (sizeof array / sizeof *array)

int compare(const void* a, const void* b);

int main(void)
{
int idx;
int array[] = {243, 12, 99, 106, 122, 77, 242};

qsort(array, NELM, sizeof *array, compare);
for (idx = 0; idx < NELM; ++idx) {
printf("%d\t", array[idx]);
}
putchar('\n');
return 0;
}

int compare(const void* a, const void* b)
{
const int aa = *(int*)a;
const int bb = *(int*)b;

return bb > aa ? -1 : aa > bb;
}
 
P

pete

Robert said:
int compare(const void* a, const void* b)
{
const int aa = *(int*)a;
should be:
const int aa = *(const int *)a;

It makes no difference.
You wind up with const int aa, either way.
return aa - bb;

would be the usual idiom.

(aa - bb) is entirely unacceptable
as a generic compar function expression.
If aa equals INT_MAX and bb equals -1,
then you have undefined behavior.
 
C

Charlie Gordon

John Smith said:
I'm trying to figure out qsort(). I haven't seen any practical examples,
only synopsis. In the code below, the array is not sorted. Can someone
give me some help?
int compare(const void* a, const void* b)
{
if(a < b) return -1;
if(a == b) return 0;
if(a > b) return 1;
}

The compare function is incorrect.
Other replies have given correct alternatives.
Here is my question :

What is the semantics of comparing void* for anything but equality ?
It is non standard to subtract void pointers (but a dubious gcc extension)
What about comparison ? Is it also an extension or is it defined in the
standard ?

Chqrlie.
 
E

Eric Sosman

Charlie said:
The compare function is incorrect.
Other replies have given correct alternatives.
Here is my question :

What is the semantics of comparing void* for anything but equality ?
It is non standard to subtract void pointers (but a dubious gcc extension)
What about comparison ? Is it also an extension or is it defined in the
standard ?

The comparison is well-defined (as I learned to my
surprise not long ago) under the usual condition that
the two pointers point to or just after the same array.
qsort() guarantees this (although bsearch() obviously
does not), so the comparison is valid.

However, the fact that the comparison is valid doesn't
imply that it's usable by qsort()! The compare() function
must define a consistent ordering, even while qsort() is
busy rearranging the array. If the compare() function's
result changes as the items are shuffled about, the ordering
is inconsistent and qsort()'s behavior is undefined.

Various people have run afoul of this by trying to
compare the pointers to equal elements in an attempt to
achieve a stable sort, e.g.

int compare(const void *p, const void *q) {
int a = *(const int*)p;
int b = *(const int*)q;

if (a < b) return -1;
if (a > b) return +1;

/* Equal elements; try for stability */
if (p < q) return -1;
if (p > q) return +1;
return 0; /* stupid qsort()! */
}

is wrong, R-O-N-G, wrong. The outcome of comparing two
equal integers would depend on their relative locations
in the array, and these locations can change as qsort()
does its work.
 
A

Andrey Tarasevich

Robert said:
...
should be:
const int aa = *(const int *)a;
return aa - bb;

would be the usual idiom.

The usual idiom would be

return (aa > bb) - (aa < bb);

A mere 'aa - bb' can produce signed overflow, which makes it entirely
useless.
 
L

Lawrence Kirby

Robert said:
int compare(const void* a, const void* b)
{
const int aa = *(int*)a;
should be:
const int aa = *(const int *)a;

It makes no difference.
You wind up with const int aa, either way.

True the int* cast is correct, but not casting away const
is better style. Reasonable compiler often will
issue a warning about that or can be made to, and it is
a good warning to turn on.

Lawrence
 
L

Lawrence Kirby

On Thu, 18 Nov 2004 10:58:06 -0500, Eric Sosman wrote:

....
The comparison is well-defined (as I learned to my
surprise not long ago) under the usual condition that
the two pointers point to or just after the same array.
qsort() guarantees this

I don't see anything in the description of qsort() that guarantees this.
It would be quite reasonable for an implementation for qsort() to copy an
element of the array into a local temporary and compare against that. This
is a natural thing to do in some sorting algorithms.

Lawrence
 
E

Eric Sosman

Lawrence said:
On Thu, 18 Nov 2004 10:58:06 -0500, Eric Sosman wrote:

...




I don't see anything in the description of qsort() that guarantees this.

The C89 wording isn't clear, but C99 makes it explicit:

7.20.5 Searching and sorting utilities
/2/ The implementation shall ensure that [...] both
arguments (when called from qsort), are pointers to
elements of the array. [...]
 
L

Lawrence Kirby

Lawrence Kirby wrote:
....
I don't see anything in the description of qsort() that guarantees this.

The C89 wording isn't clear, but C99 makes it explicit:

7.20.5 Searching and sorting utilities
/2/ The implementation shall ensure that [...] both
arguments (when called from qsort), are pointers to
elements of the array. [...]

OK, it is required in C99. Very strange though, it potentially reduces the
efficiency of the implementation for no obvious benefit.

Lawrence
 
P

pete

Lawrence said:
Lawrence Kirby wrote:
...
I don't see anything in the description of qsort()
that guarantees this.

The C89 wording isn't clear, but C99 makes it explicit:

7.20.5 Searching and sorting utilities
/2/ The implementation shall ensure that [...] both
arguments (when called from qsort), are pointers to
elements of the array. [...]

OK, it is required in C99.
Very strange though, it potentially reduces the
efficiency of the implementation for no obvious benefit.

I must be misunderstanding what you're saying.
I don't see how you can write a compar function without knowing
that the arguments are pointers to array elements.

Is this the same thing as what you're talking about?
My C89 last draft has:

4.10.5.2 The qsort function
The contents of the array are sorted in ascending order according
to a comparison function pointed to by compar , which is called with
two arguments that point to the objects being compared.
 
M

Michael Mair

pete said:
Lawrence said:
Lawrence Kirby wrote:
...


I don't see anything in the description of qsort()
that guarantees this.

The C89 wording isn't clear, but C99 makes it explicit:

7.20.5 Searching and sorting utilities
/2/ The implementation shall ensure that [...] both
arguments (when called from qsort), are pointers to
elements of the array. [...]

OK, it is required in C99.
Very strange though, it potentially reduces the
efficiency of the implementation for no obvious benefit.

Umh, I cannot think of a situation where you cannot make do
with pointers to array elements -- the information is hidden
behind void *, so I fail to see the restriction.

One possible benefit could be that for keys giving the same
value, you want to keep the order in which the respective
objects (and thus the pointers) were stored.
This could of course be done by extending the key but maybe
is not possible in a straightforward way. If we are looking
at the same array, the pointers can be compared whereas this
is not possible if we memcpy() one of the objects.
I must be misunderstanding what you're saying.
I don't see how you can write a compar function without knowing
that the arguments are pointers to array elements.

Is this the same thing as what you're talking about?
My C89 last draft has:

4.10.5.2 The qsort function
The contents of the array are sorted in ascending order according
to a comparison function pointed to by compar , which is called with
two arguments that point to the objects being compared.

The thing is that in C89, I could memcpy() one array element and
pass a pointer to it to the function pointed to by compar, whereas
in the C99 version this is forbidden.


Cheers
Michael
 
P

pete

Michael said:
Lawrence said:
On Thu, 18 Nov 2004 13:29:38 -0500, Eric Sosman wrote:


Lawrence Kirby wrote:

...


I don't see anything in the description of qsort()
that guarantees this.

The C89 wording isn't clear, but C99 makes it explicit:

7.20.5 Searching and sorting utilities
/2/ The implementation shall ensure that [...] both
arguments (when called from qsort), are pointers to
elements of the array. [...]

OK, it is required in C99.
Very strange though, it potentially reduces the
efficiency of the implementation for no obvious benefit.

Umh, I cannot think of a situation where you cannot make do
with pointers to array elements -- the information is hidden
behind void *, so I fail to see the restriction.

One possible benefit could be that for keys giving the same
value, you want to keep the order in which the respective
objects (and thus the pointers) were stored.
This could of course be done by extending the key but maybe
is not possible in a straightforward way. If we are looking
at the same array, the pointers can be compared whereas this
is not possible if we memcpy() one of the objects.
I must be misunderstanding what you're saying.
I don't see how you can write a compar function without knowing
that the arguments are pointers to array elements.

Is this the same thing as what you're talking about?
My C89 last draft has:

4.10.5.2 The qsort function
The contents of the array are sorted in ascending order according
to a comparison function pointed to by compar , which is called with
two arguments that point to the objects being compared.

The thing is that in C89, I could memcpy() one array element and
pass a pointer to it to the function pointed to by compar, whereas
in the C99 version this is forbidden.

Thank you.
 
L

Lawrence Kirby

Lawrence said:
On Thu, 18 Nov 2004 13:29:38 -0500, Eric Sosman wrote:


Lawrence Kirby wrote:

...


I don't see anything in the description of qsort()
that guarantees this.

The C89 wording isn't clear, but C99 makes it explicit:

7.20.5 Searching and sorting utilities
/2/ The implementation shall ensure that [...] both
arguments (when called from qsort), are pointers to
elements of the array. [...]

OK, it is required in C99.
Very strange though, it potentially reduces the
efficiency of the implementation for no obvious benefit.

Umh, I cannot think of a situation where you cannot make do
with pointers to array elements -- the information is hidden
behind void *, so I fail to see the restriction.

A simple example is an insertion sort. A basic implementation
compares adjacent elements. If they are out of order it swaps them and
moves on. Essentially one element moves along the array until it is in its
correct place. An optimisation of this is to copy that element to a
temporary variable and compare that against array elements, move them when
out of order and finally write your temporary back to the free slot in the
array when you find the right position. Esssentially you can optimise a
lot of swap operations into moves. With the requirements of C99 the
element must stay in the array for the purposes of comparison so this
optimisation is no longer possible. You can "make do" but what is the
benefit of potentially cripping the efficiency of the qsort()
implementation?
One possible benefit could be that for keys giving the same
value, you want to keep the order in which the respective
objects (and thus the pointers) were stored.
This could of course be done by extending the key but maybe
is not possible in a straightforward way. If we are looking
at the same array, the pointers can be compared whereas this
is not possible if we memcpy() one of the objects.

Comparing addresses does *not* make a sort stable, because during the sort
process the position of elements varies and not necessarily in a
consistent way (think for example of the partitioning process that
Quicksort uses).

Indeed ANY test of relative addresses that can affect the result of the
comparison function will guarantee that the function no longer generates a
consistent ordering relation. So there is no value in supporting relative
pointer comparisons.

When comparing 2 elements all you need is access to the values of those
elements, whether they are part of the same array or not is not useful or
relevant information.
The thing is that in C89, I could memcpy() one array element and
pass a pointer to it to the function pointed to by compar, whereas
in the C99 version this is forbidden.

Yes, that's the problem, C99 appears to have invented a pointless and
counterproductive restriction.

Lawrence
 
M

Michael Mair

Lawrence said:
Lawrence Kirby wrote:

On Thu, 18 Nov 2004 13:29:38 -0500, Eric Sosman wrote:

Lawrence Kirby wrote:

I don't see anything in the description of qsort()
that guarantees this.

The C89 wording isn't clear, but C99 makes it explicit:

7.20.5 Searching and sorting utilities
/2/ The implementation shall ensure that [...] both
arguments (when called from qsort), are pointers to
elements of the array. [...]

OK, it is required in C99.
Very strange though, it potentially reduces the
efficiency of the implementation for no obvious benefit.

Umh, I cannot think of a situation where you cannot make do
with pointers to array elements -- the information is hidden
behind void *, so I fail to see the restriction.

A simple example is an insertion sort. A basic implementation
compares adjacent elements. If they are out of order it swaps them and
moves on. Essentially one element moves along the array until it is in its
correct place. An optimisation of this is to copy that element to a
temporary variable and compare that against array elements, move them when
out of order and finally write your temporary back to the free slot in the
array when you find the right position. Esssentially you can optimise a
lot of swap operations into moves. With the requirements of C99 the
element must stay in the array for the purposes of comparison so this
optimisation is no longer possible. You can "make do" but what is the
benefit of potentially cripping the efficiency of the qsort()
implementation?

Okay, so we essentially have to switch from a few memcpy()s to
one memmove(). For small partitions left by a quicksort or a
"nearly" sorted array, we probably will lose something.
I guess Shell sort would be even more pathological as you cannot
use one memmove but would have to first find out where everything
goes and then loop again to do the memcpy()s.

Thank you for explaining.

Comparing addresses does *not* make a sort stable, because during the sort
process the position of elements varies and not necessarily in a
consistent way (think for example of the partitioning process that
Quicksort uses).

Indeed ANY test of relative addresses that can affect the result of the
comparison function will guarantee that the function no longer generates a
consistent ordering relation. So there is no value in supporting relative
pointer comparisons.

Er, perhaps I am slow on the uptake but IMO a quicksort _can_ keep
the previous relative order when partitioning. If I've time today, I
will try it out.
However, the requirements from the standard are probably not strong
enough for that to portably work.

Apart from that, I do _not_ want to say that it is a good
idea to bring in relative position as sorting criteria. But this
is the only "benefit" I could think of.

Did the standard people give any rationale as to why they changed
the wording? Otherwise this might be a good question for c.s.c.


Cheers
Michael
 
P

pete

Michael said:
Lawrence said:
pete wrote:

Lawrence Kirby wrote:

On Thu, 18 Nov 2004 13:29:38 -0500, Eric Sosman wrote:

Lawrence Kirby wrote:

I don't see anything in the description of qsort()
that guarantees this.

The C89 wording isn't clear, but C99 makes it explicit:

7.20.5 Searching and sorting utilities
/2/ The implementation shall ensure that [...] both
arguments (when called from qsort), are pointers to
elements of the array. [...]

OK, it is required in C99.
Very strange though, it potentially reduces the
efficiency of the implementation for no obvious benefit.

Umh, I cannot think of a situation where you cannot make do
with pointers to array elements -- the information is hidden
behind void *, so I fail to see the restriction.

A simple example is an insertion sort. A basic implementation
compares adjacent elements. If they are out of order it swaps
them and moves on. Essentially one element moves along
the array until it is in its correct place.
An optimisation of this is to copy that element to a
temporary variable and compare that against array elements,
move them when out of order and finally write your temporary
back to the free slot in the
array when you find the right position.
Esssentially you can optimise a lot of swap operations into moves.
With the requirements of C99 the
element must stay in the array for the purposes of comparison
so this optimisation is no longer possible.
You can "make do" but what is the
benefit of potentially cripping the efficiency of the qsort()
implementation?

Okay, so we essentially have to switch from a few memcpy()s to
one memmove(). For small partitions left by a quicksort or a
"nearly" sorted array, we probably will lose something.
I guess Shell sort would be even more pathological as you cannot
use one memmove but would have to first find out where everything
goes and then loop again to do the memcpy()s.

Shell sort with a temp variable
doesn't need any relooping that I'm aware of.

e_type is a user defined nonarray type.
GT is a user defined "greater than" macro.
If e_type were to be an array, then all of the e_type
assignments would have to be replaced with memcpy calls.

void s3sort(e_type *array, size_t nmemb)
{
e_type temp, *i, *j, *k, *after;

after = array + nmemb;
if (nmemb > (size_t)-1 / 3) {
nmemb = (size_t)-1 / 3;
}
do {
for (i = array + nmemb; i != after; ++i) {
j = i - nmemb;
if (GT(j, i)) {
k = i;
temp = *k;
do {
*k = *j;
k = j;
if (nmemb + array > j) {
break;
}
j -= nmemb;
} while (GT(j, &temp));
*k = temp;
}
}
nmemb = nmemb != 2 ? 3 * nmemb / 7 : 1;
} while (nmemb != 0);
}

The references to memcpy and memmove
suggest that you're talking about writing qsort in C.
qsort can't depend on malloc (and friends) to provide the temp object,
because malloc may fail with valid arguments, while qsort may not.
That means that if qsort is going to use a temp variable,
it must have an alternative way if malloc fails.
Automatic variables are unsuitable for temp types larger than char,
because their alignment requirements may be less than their size,
while array elements of the same size may need to be aligned
at their full size.
Er, perhaps I am slow on the uptake but IMO a quicksort _can_ keep
the previous relative order when partitioning. If I've time today, I
will try it out.

Any array sorting algorithm that compares and swaps
nonadjacent elements, like quicksort does,
is going to have a very hard time being made stable,
if stable sorting is what you're talking about.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,048
Latest member
verona

Latest Threads

Top