I'm trying to write a fully recursive version of merge sort
I get segmentation fault on merge function when I try to order more than 100k records, I read online that it would be a stack overflow issue, related to VLAs, but I can't figure it out.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct _MBArray MBArray;
struct _MBArray {
void ** array;
int length;
};
void mergeSort(MBArray *mbarray, int start, int end, int (*compare)(void *, void *)) {
if (start == end) {
return;
} else {
int middle = (end + start) / 2;
mergeSort(mbarray, start, middle, compare);
mergeSort(mbarray, middle + 1, end, compare);
merge(mbarray, start, middle + 1, end, compare, (mbarray->array)[middle + 1]);
return;
}
}
void merge(MBArray *mbarray, int startA, int startB, int length, int (*compare)(void *, void *),void *elem) {
if (startA > startB - 1) {
return;
} else
if (startB > length) {
return;
} else
if ((*(compare))((mbarray->array)[startA], (mbarray->array)[startB])) {
merge(mbarray, startA + 1, startB, length, compare, (mbarray->array)[startB]);
return;
} else {
memcpy(mbarray->array + (startA + 1), mbarray->array + startA, (startB - startA) * sizeof(void *));
(mbarray->array)[startA] = elem;
merge(mbarray, startA + 1, startB + 1, length, compare, (mbarray->array)[startB + 1]);
return;
}
}
Your merge and mergesort functions are incorrect:
the mergesort function is given index values into the array such that start is included and end is excluded. Here is how to fix it:
void mergeSort(MBArray *mbarray, int start, int end,
int (*compare)(void *, void *))
{
if (end - start > 1) {
int middle = start + (end - start) / 2;
mergeSort(mbarray, start, middle, compare);
mergeSort(mbarray, middle, end, compare);
merge(mbarray, start, middle, end, compare);
}
}
The merge function should be simplified. If you really intend for this function to recurse instead of using a temporary array, you might recurse as many as length/2 times hence are likely to cause a stack overflow even faster than by allocating a VLA in the merge function.
Here is a VLA based merge function:
void merge(MBArray *mbarray, int low, int middle, int end,
int (*compare)(void *, void *))
{
int templen = middle - low;
void *temp[templen];
memcpy(temp, mbarray->array + low, sizeof(temp));
int i = 0, j = middle, k = low;
while (i < templen) {
if (j >= end || compare(temp[i], mbarray->array[j])) {
mbarray->array[k++] = temp[i++];
else
mbarray->array[k++] = mbarray->array[j++];
}
}
A fully recursive version should save the element and recurse before setting it into its final position. This is much more costly in stack space than allocating a VLA so stack overflow is more likely. Allocating a temporary array once with malloc() and passing it to mergesort is much safer and also more efficient.
Here is a modified fully recursive merge function:
void mergeRecur(MBArray *mbarray, int dest, int a0, int a1, int b0, int b1,
int (*compare)(void *, void *))
{
if (a0 < a1) {
if (b0 >= b1 || compare(mbarray->array[a0], mbarray->array[b0])) {
void *temp = mbarray->array[a0];
mergeRecur(mbarray, dest + 1, a0 + 1, a1, b0, b1, compare);
mbarray->array[dest] = temp;
} else {
void *temp = mbarray->array[b0];
mergeRecur(mbarray, dest + 1, a0, a1, b0 + 1, b1, compare);
mbarray->array[dest] = temp;
}
}
}
void merge(MBArray *mbarray, int low, int middle, int end,
int (*compare)(void *, void *))
{
mergeRecur(mbarray, low, low, middle, middle, end, compare);
}
For large arrays, I would recommend allocating a temporary array once and calling a recursive version of mergesort that uses the temporary array instead of allocating a VLA:
#include <stdlib.h>
#include <string.h>
struct _MBArray {
void ** array;
int length;
};
void mergeTemp(MBArray *mbarray, int low, int middle, int end,
int (*compare)(void *, void *), void **temp)
{
int templen = middle - low;
int i = 0, j = middle, k = low;
memcpy(temp, mbarray->array + low, sizeof(*temp) * templen);
while (i < templen) {
if (j >= end || compare(temp[i], mbarray->array[j])) {
mbarray->array[k++] = temp[i++];
else
mbarray->array[k++] = mbarray->array[j++];
}
}
void mergeSortTemp(MBArray *mbarray, int start, int end,
int (*compare)(void *, void *), void **temp)
{
if (end - start > 1) {
int middle = start + (end - start) / 2;
mergeSortTemp(mbarray, start, middle, compare, temp);
mergeSortTemp(mbarray, middle, end, compare, temp);
mergeTemp(mbarray, start, middle, end, compare, temp);
}
}
void mergeSort(MBArray *mbarray, int (*compare)(void *, void *)) {
if (mbarray->length > 1) {
void **temp = malloc(sizeof(*temp) * (mbarray->length / 2));
mergeSortTemp(mbarray, 0, mbarray->length, compare, temp);
free(temp);
}
}
The problem is most likely due to merge() calling itself for each element merged. It would be better to have an entry function for mergeSort() that does a one time allocation of a second array (of pointers), then pass the second array as a parameter to a recursive MergeSortR(), and merge back and forth between the two arrays.
Related
I am trying to implement this merge sort function to sort an array of structs in c. When I call the function my program exits early, I think this is because my array i am sorting is of type row_t* and needs to be row_t**, I am unsure on how to correctly malloc my data in order to achieve this.
//I have copied relevant bits of my code below
//this is the struct i am trying to sort by the value S
typedef struct
{
double rho, u, v, x, y, flux_u, flux_v, S;
} row_t;
//This is where i allocate the array i want to sort
row_t* linear_row_arr = (row_t*)malloc(sizeof(row_t)*100);
//this is where i try to call the function,
//linear_row_arr is an array of row_t, with 100 elements
merge_sort((void**)linear_row_arr, 99, row_array_s_comp);
//This is the function i am trying to call.
void merge(void** array, int n, int mid, int cmp(const void*, const void*))
{
// (0) need extra space for merging
void** tmp = malloc(n * sizeof(void*));
void** left = array;
void** right = array + mid;
int i = 0;
int j = 0;
int left_size = mid;
int right_size = n - mid;
// (1) perform the merge
for (int k = 0; k < n; k++) {
if (j == right_size)
tmp[k] = left[i++];
else if (i == left_size)
tmp[k] = right[j++];
else if (cmp(left[i], right[j]) < 1)
tmp[k] = left[i++];
else
tmp[k] = right[j++];
}
// (2) copy the merged array
for (int i = 0; i < n; i++) {
array[i] = tmp[i];
}
// (3) clean up
free(tmp);
}
void merge_sort(void** array, int n, int cmp(const void*, const void*))
{
if (n > 1) {
int mid = n / 2;
merge_sort(array, mid, cmp);
merge_sort(array + mid, n - mid, cmp);
merge(array, n, mid, cmp);
}
}
int row_array_s_comp(const void* a, const void* b)
{
row_t* ra = (row_t*)a;
row_t* rb = (row_t*)b;
// with int data we can just subtract to get the right behaviour
return ra->S - rb->S;
}
When I run this the code exits early with no error message.
EDIT:
I tried using #Ian Abbott's solution and it produced a seg fault at my comparison function. Could it be that I used malloc instead of calloc to allocate the memory for my data?
// This is my function call
//100 elements of row_t*
merge_sort(linear_row_arr, 100, sizeof(row_t*), row_array_s_comp);
EDIT 2:
Thank you Ian, I have fixed my errors and now have a handy merge sort function at my disposal. I up voted your answer but it says it won be displayed publicly as i have less than 15 rep. If anyone needs it here is the final comparison function i used was
int row_array_s_comp(const void* a, const void* b)
{
row_t* ra = (row_t*)a;
row_t* rb = (row_t*)b;
// with double data we can just subtract to get the right behaviour
return (ra->S > rb->S) - (ra->S < ra->S);
}
and i called the function with
merge_sort(linear_row_arr, 100, sizeof(row_t), row_array_s_comp);
If anyone finds this useful feel free to upvote #Ians Abotts answer as it is correct but I can't.
Thanks again for your time!
Here is a simple implementation of a top-down merge sort of an array, using parameters similar to qsort. Time complexity is O(n log n). It uses temporary storage of similar size to the input array.
/* Subroutine to merge two input arrays into an output array. */
static void merge(void *out, const void *pa, size_t na,
const void *pb, size_t nb, size_t elemsize,
int (*cmp)(const void *, const void *))
{
while (na != 0 || nb != 0) {
if (na == 0 || nb != 0 && cmp(pa, pb) > 0) {
memcpy(out, pb, elemsize);
pb = (const char *)pb + elemsize;
nb--;
} else {
memcpy(out, pa, elemsize);
pa = (const char *)pa + elemsize;
na--;
}
out = (char *)out + elemsize;
}
}
/* Merge sort an array. */
void merge_sort(void *base, size_t nmemb, size_t elemsize,
int (*cmp)(const void *, const void *))
{
size_t nbottom;
size_t ntop;
void *midp;
void *bottom;
void *top;
if (nmemb <= 1) {
/* Too small to sort. */
return;
}
/* Sort the bottom half and the top half. */
nbottom = nmemb / 2;
ntop = nmemb - nbottom;
midp = (char *)base + (nbottom * elemsize);
merge_sort(base, nbottom, elemsize, cmp);
merge_sort(midp, ntop, elemsize, cmp);
/* Make temporary copies of the sorted bottom half and top half. */
bottom = malloc(nbottom * elemsize);
top = malloc(ntop * elemsize);
memcpy(bottom, base, nbottom * elemsize);
memcpy(top, midp, ntop * elemsize);
/* Do a sorted merge of the copies into the original. */
merge(base, bottom, nbottom, top, ntop, elemsize, cmp);
/* Free temporary copies. */
free(bottom);
free(top);
}
I try to implement some of the algorithms pure generic using C. I stick with the 3-way quicksort but somehow the implementation does not give correct output. The output nearly sorted but some keys aren't where it should be. The code is below. Thanks in advance.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
static void swap(void *x, void *y, size_t size) {
void *tmp = malloc(size);
memcpy(tmp, x, size);
memcpy(x, y, size);
memcpy(y, tmp, size);
free(tmp);
}
static int cmpDouble(const void *i, const void *j) {
if (*(double *)i < *(double *)j)
return 1;
else if (*(double *)i == *(double *)j)
return 0;
else
return -1;
}
void qsort3way(void *base, int lo, int hi, size_t size,
int (*cmp)(const void *, const void *)) {
if (hi <= lo)
return;
else {
char *ptr = (char*)base;
char *v = ptr + lo * size;
int lt = lo, gt = hi;
int i = lo;
while (i <= gt) {
int c = cmp(v, ptr + i * size);
if (c < 0)
swap(ptr + (lt++) * size, ptr + (i++) * size, size);
else if (c > 0)
swap(ptr + i * size, ptr + (gt--) * size, size);
else
i++;
}
qsort3way(base, lo, lt - 1, size, cmp);
qsort3way(base, gt + 1, hi, size, cmp);
}
}
int main(void) {
int i;
double *d = (double*)malloc(sizeof(double) * 100);
for (i = 0; i < 100; i++)
d[i] = (double)rand();
qsort3way(d, 0, 100 -1, sizeof(double), cmpDouble);
for (i = 0; i < 100; i++)
printf("%.10lf\n", d[i]);
free(d);
return 0;
}
sample output:
41.0000000000
153.0000000000
288.0000000000
2082.0000000000
292.0000000000
1869.0000000000
491.0000000000
778.0000000000
1842.0000000000
6334.0000000000
2995.0000000000
8723.0000000000
3035.0000000000
3548.0000000000
4827.0000000000
3902.0000000000
4664.0000000000
5436.0000000000
4966.0000000000
5537.0000000000
5447.0000000000
7376.0000000000
5705.0000000000
6729.0000000000
6868.0000000000
7711.0000000000
9961.0000000000
8942.0000000000
9894.0000000000
9040.0000000000
9741.0000000000
After reading the book link that you provide to #JohnBollinger. I understand how your algorithm work. Your problem is that your pivot move, but you don't change the value of v. Your pivot is at the index lt
char *ptr = base;
int lt = lo, gt = hi; // lt is the pivot
int i = lo + 1; // we don't compare pivot with itself
while (i <= gt) {
int c = cmp(ptr + lt * size, ptr + i * size);
if (c < 0) {
swap(ptr + lt++ * size, ptr + i++ * size, size);
}
else if (c > 0)
swap(ptr + i * size, ptr + gt-- * size, size);
else
i++;
}
qsort3way(base, lo, lt - 1, size, cmp);
qsort3way(base, gt + 1, hi, size, cmp);
I propose you a "proper" solution:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
typedef void qsort3way_swap(void *a, void *b);
typedef int qsort3way_cmp(void const *a, void const *b);
static void qsort3way_aux(char *array_begin, char *array_end, size_t size,
qsort3way_cmp *cmp, qsort3way_swap *swap) {
if (array_begin < array_end) {
char *i = array_begin + size;
char *lower = array_begin;
char *greater = array_end;
while (i < greater) {
int ret = cmp(lower, i);
if (ret < 0) {
swap(i, lower);
i += size;
lower += size;
} else if (ret > 0) {
greater -= size;
swap(i, greater);
} else {
i += size;
}
}
qsort3way_aux(array_begin, lower, size, cmp, swap);
qsort3way_aux(greater, array_end, size, cmp, swap);
}
}
static void qsort3way(void *array_begin, void *array_end, size_t size,
qsort3way_cmp *cmp, qsort3way_swap *swap) {
qsort3way_aux(array_begin, array_end, size, cmp, swap);
}
static void swap_int_aux(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
static void swap_int(void *a, void *b) { swap_int_aux(a, b); }
static int cmp_int_aux(int const *a, int const *b) {
if (*a < *b) {
return 1;
} else if (*a > *b) {
return -1;
} else {
return 0;
}
}
static int cmp_int(void const *a, void const *b) { return cmp_int_aux(a, b); }
static void print_int(char const *intro, int const *array, size_t const size) {
printf("%s:", intro);
for (size_t i = 0; i < size; i++) {
printf(" %d", array[i]);
}
printf("\n");
}
#define SIZE 42
int main(void) {
int array[SIZE];
srand((unsigned int)time(NULL));
for (size_t i = 0; i < SIZE; i++) {
array[i] = rand() % SIZE - SIZE / 2;
}
print_int("before", array, SIZE);
qsort3way(array, array + SIZE, sizeof *array, cmp_int, swap_int);
print_int("after", array, SIZE);
}
Note: The optimization int i = lo + 1; and char *i = array_begin + size; are mandatory. Because in the case where the function compare return that pivot != pivot this will lead to a infinite recursion. How this would be possible?
The function cmp is bug.
double has strange power... A double can be not equal to itself! (-nan).
The implementation does not give the correct result because it is wrong. Pretty badly wrong, in fact, given that it's supposed to be a three-way quicksort and not a regular one.
One basic problem is that you've omitted the bit where you move the pivot(s) into their proper position after the main partitioning loop. For standard quicksort, that requires one extra swap or assignment after the loop, depending on implementation details. For a three-way quicksort that involves one or two extra loops to move the potentially-many values equal to the pivot into their positions.
A more insidious problem is the one #Stargateur first pointed out: you track the pivot element by pointer, not by value, and you (sometimes) swap the original value out from that position in the course of the partitioning loop.
Furthermore, your main partitioning loop is wrong for a three-way quicksort, too. When you encounter an element equal to the pivot you just leave it in place, but you need instead to move it to one end or the other (or to some kind of auxiliary storage, if you're willing to incur that memory cost) so that you can perform that move to the middle at the end. In a sense, the previous problem is a special case of this one -- you're not reserving space for or tracking the pivot values. Fixing this will solve the previous problem as well.
I'm not sure what reference you used to prepare your implementation, or whether you built it from scratch, but Geeks for Geeks has a C++ (but pretty much also C) implementation for int arrays that you might want to check out.
Your implementation is incorrect because the pivot may move during the partitioning phase and you use a pointer for the comparison which no longer points to it. Implementations in other languages use the value of the pivot instead of its address.
Note also these shortcomings:
recursing both ways may cause stack overflow on pathological distributions. In you case, an array that is already sorted is a pathological distribution.
the comparison function should return the opposite values: -1 if a < b, +1 is a > b and 0 if a == b.
the API is non-standard and confusing: you should pass the number of elements instead of a range with included bounds.
Here is a corrected and commented version:
#include <stdio.h>
#include <stdlib.h>
static void swap(unsigned char *x, unsigned char *y, size_t size) {
/* sub-optimal, but better than malloc */
while (size-- > 0) {
unsigned char c = *x;
*x++ = *y;
*y++ = c;
}
}
void qsort3way(void *base, int n, size_t size,
int (*cmp)(const void *, const void *))
{
unsigned char *ptr = (unsigned char *)base;
while (n > 1) {
/* use first element as pivot, pointed to by lt */
int i = 1, lt = 0, gt = n;
while (i < gt) {
int c = cmp(ptr + lt * size, ptr + i * size);
if (c > 0) {
/* move smaller element before the pivot range */
swap(ptr + lt * size, ptr + i * size, size);
lt++;
i++;
} else if (c < 0) {
/* move larger element to the end */
gt--;
swap(ptr + i * size, ptr + gt * size, size);
/* test with that element again */
} else {
/* leave identical element alone */
i++;
}
}
/* array has 3 parts:
* from 0 to lt excluded: elements smaller than pivot
* from lt to gt excluded: elements identical to pivot
* from gt to n excluded: elements greater than pivot
*/
/* recurse on smaller part, loop on larger to minimize
stack use for pathological distributions */
if (lt < n - gt) {
qsort3way(ptr, lt, size, cmp);
ptr += gt * size;
n -= gt;
} else {
qsort3way(ptr + gt * size, n - gt, size, cmp);
n = lt;
}
}
}
static int cmp_double(const void *i, const void *j) {
/* this comparison function does not handle NaNs */
if (*(const double *)i < *(const double *)j)
return -1;
if (*(const double *)i > *(const double *)j)
return +1;
else
return 0;
}
int main(void) {
double d[100];
int i;
for (i = 0; i < 100; i++)
d[i] = rand() / ((double)RAND_MAX + 1);
qsort3way(d, 100, sizeof(*d), cmp_double);
for (i = 0; i < 100; i++)
printf("%.10lf\n", d[i]);
return 0;
}
I have a quicksort program which executes as shown. But the last element is not getting sorted. Can anyone tell me how to modify the program to obtain the correct result.
#include <stdlib.h>
#include <stdio.h>
static void swap(void *x, void *y, size_t l)
{
char *a = x, *b = y, c;
while(l--)
{
c = *a;
*a++ = *b;
*b++ = c;
}
}
static void sort(char *array, size_t size, int (*cmp)(void*,void*), int begin, int end)
{
if (end > begin)
{
void *pivot = array + begin;
int l = begin + size;
int r = end;
while(l < r)
{
if (cmp(array+l,pivot) <= 0)
{
l += size;
}
else if ( cmp(array+r, pivot) > 0 )
{
r -= size;
}
else if ( l < r )
{
swap(array+l, array+r, size);
}
}
l -= size;
swap(array+begin, array+l, size);
sort(array, size, cmp, begin, l);
sort(array, size, cmp, r, end);
}
}
void qsort(void *array, size_t nitems, size_t size, int (*cmp)(void*,void*))
{
sort(array, size, cmp, 0, (nitems-1)*size);
}
typedef int type;
int type_cmp(void *a, void *b){ return (*(type*)a)-(*(type*)b); }
int main(void)
{ /* simple test case for type=int */
int num_list[]={5,4,3,2,1};
int len=sizeof(num_list)/sizeof(type);
char *sep="";
int i;
qsort(num_list,len,sizeof(type),type_cmp);
printf("sorted_num_list={");
for(i=0; i<len; i++){
printf("%s%d",sep,num_list[i]);
sep=", ";
}
printf("};\n");
return 0;
}
Result:
sorted_num_list={2, 3, 4, 5, 1};
Why?
I tried doing (nitems)*size. But when I try the same for strings, it gives me error
The problem comes from this line:
sort(array, size, cmp, 0, (nitems-1)*size);
nitems is your number of items, here 5, you're substracting one so your quick sort will only sort the 4 first elements. Remove the -1 and it will work.
sort(array, size, cmp, 0, nitems*size);
2 mistakes here...
First change qsort() function names to qsort_user() or any other name then the qsort() because qsort() is the standard function defined in stdlib.h
Then change this line
sort(array, size, cmp, 0, (nitems-1)*size);
to
sort(array, size, cmp, 0, (nitems)*size);
I am working on a generic merge sort algorithm. Now the problem is I keep getting garbage values when I print the content of the supposedly "sorted" array.
The merge sort algorithm:
void merge(void *a, int n, int size, int (*fcmp)(const void *, const void *)){
int i, j, k, mid=n/2;
void * temp = (void *)malloc(n*size);
for(i=0, j=mid, k=0; k<n; k++){
if((i<mid)&&(j>= n)){
memcpy(temp+(k*size), a+i*size, size);
i++;
}
else if((i<mid)&&(fcmp(a + i*size, a+j*size) <= 0)){
memcpy(temp+(k*size), a+j*size, size);
j++;
}
}
for(i=0, j=0; j<n; i++, j++)
memcpy(a+(j*size),temp+(i*size),size);
free(temp);
}
void genmsort(void *a, int n, int size, int (*fcmp)(const void *, const void *)){
if(n>1){
genmsort(a, n/2, size, (int(*)(const void *, const void *)) compnode);
genmsort(a+(n/2)*size, n-n/2, size, (int(*)(const void *, const void *)) compnode);
merge(a, n, size, (int(*)(const void *, const void *)) compnode);
}
}
The compnode function:
int compnode(node *a, node *b){
return (strcmp(a->name, b->name));
}
The initialization function:
void init_node(node a[], int n){
int i;
for(i=0; i<n; i++){
a[i].stdno=i+1;
sprintf(a[i].name, "%li", a[i].stdno);
}
srand(8);
for(i=0; i<n; i++)
genswap(a+i, a+(rand()%n), sizeof(node));
}
And the main function:
int main(){
int n=10;
clock_t t1, t2;
node *b;
b=(node *)malloc(n*sizeof(node));
init_node(b, n);
t1=clock();
genmsort(b, n, sizeof(node), (int(*)(const void *, const void *)) compnode);
t2=clock();
free(b);
}
What could be wrong here? I'm sorry for the lengthy code but I hope you can understand it. I would really appreciate your help because I am stuck with this code for some time now.
The latitude of things in this code are copious. Some are show-stoppers, but ultimately it is your merge function. A typical merge algorithm moves one item into the target buffer with each iteration until such time as one list or the other is exhausted. Once that happens the remaining items in the remaining list are bulk-copied into place and the algorithm terminates.
You have a fundamental flaw, and we'll cover that now. Your main loop runs k all the way through to n, and at least that is right. But, look at your first expressions in your if-else-if conditions:
if((i<mid)&&(j>= n))
{
memcpy(temp+(k*size), a+i*size, size);
i++;
}
else if((i<mid)&&(fcmp(a + i*size, a+j*size) <= 0))
{
memcpy(temp+(k*size), a+j*size, size);
j++;
}
They both have i<mid, so this could be simplified to be:
if (i<mid)
{
if (j>=n)
{
memcpy(temp+(k*size), a+i*size, size);
i++;
}
else if (fcmp(a + i*size, a+j*size) <= 0))
{
memcpy(temp+(k*size), a+j*size, size);
j++;
}
}
which means if your i-side is ever exhausted before your j-side, you simply do nothing from that point on, just incrementing k until it reaches n. The rest of the j-side of the split-list is completely ignored. Then, at the end of the function you copy uninitialized data right over the top of your original array.
Some things to consider. First, typedef your comparator function requirements and stick to it. It is the responsibility of the comparator to adhere to the requirements of the callback-requestor; not the other way around.
typedef int (*fn_cmp)(const void*, const void*);
and use this correctly by implementing your callback to that standard.
// compare two nodes.
int compare_node(const void* lhs, const void* rhs)
{
const node* lhn = lhs;
const node* rhn = rhs;
return (strcmp(lhn->name, rhn->name));
}
This also makes your generic mergesort much cleaner:
// generic mergesort algorithm
void genmsort(void *src, unsigned int len, unsigned int size, fn_cmp fcmp)
{
if (len < 2)
return;
unsigned int mid = len/2;
genmsort(src, mid, size, fcmp);
genmsort((unsigned char*)src+(mid*size), len - mid, size, fcmp);
merge(src, mid, len-mid, size, fcmp);
}
Readability aside, the biggest difference between the following merge and yours is the addition of the second length parameter (the fact that this one works is considered a bonus). You're code inferred this value from the single length originally passed in; something you did in an entirely separate place in your code when calculating your recursive partition sizes Those same sizes need to be passed here as well, for multiple reasons that include consistency and usability).
Consider the following please. If it is possible to annotate this algorithm better, or make it clearer, I'm at a loss to see how:
// merges two lists back to back in a single sequence.
void merge(void *src,
unsigned int alen, // note parition size.
unsigned int blen, // and again here.
unsigned int size,
fn_cmp fcmp)
{
void *bsrc = (unsigned char*)src + alen * size;
void *dst = malloc((alen + blen)*size);
unsigned int a = 0, b = 0, k = 0;
for (k=0; k<(alen+blen); ++k)
{
// still got a's ?
if (a < alen)
{
// still got b's ?
if (b < blen)
{
// get "lesser" of the two.
if (fcmp((const unsigned char*)src + a*size,
(const unsigned char*)bsrc + b*size) <= 0)
{
// a is less. move it in.
memcpy((unsigned char *)dst + k*size,
(const unsigned char*)src + a++*size, size);
}
else
{ // b is less. move it in.
memcpy((unsigned char *)dst + k*size,
(const unsigned char*)bsrc + b++*size, size);
}
}
else
{ // no more b's. move the rest of the a's
// into the target and leave.
memcpy((unsigned char *)dst + k*size,
(const unsigned char*)src + a*size, (alen - a)*size);
k += (alen-a);
}
}
else
{ // else no a's. move the rest of the b's into
// the target and leave.
memcpy((unsigned char *)dst + k*size,
(const unsigned char*)bsrc + b*size, (blen - b)*size);
k += (blen-b);
}
}
// copy final output.
memcpy(src, dst, (alen+blen)*size);
free(dst);
}
Finally, this codes does not require any compiler extensions such as the standard-violating incremental void* you so-heavily exploited in your code. I strongly advise you stay clear of such extensions.
The following is the full test program used to verify the algorithm above and its interface. Read it carefully.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <time.h>
// simple node definition.
typedef struct node
{
char name[32];
int id;
} node;
// compare two nodes.
int compare_node_names(const void* lhs, const void* rhs)
{
const node* lhn = lhs;
const node* rhn = rhs;
return (strcmp(lhn->name, rhn->name));
}
// compare two nodes.
int compare_node_ids(const void* lhs, const void* rhs)
{
const node* lhn = lhs;
const node* rhn = rhs;
return (lhn->id - rhn->id);
}
// comparator requirements.
typedef int (*fn_cmp)(const void*, const void*);
// merges two lists back to back in a single sequence.
void merge(void *src,
unsigned int alen, // note parition size.
unsigned int blen, // and again here.
unsigned int size,
fn_cmp fcmp)
{
void *bsrc = (unsigned char*)src + alen * size;
void *dst = malloc((alen + blen)*size);
unsigned int a = 0, b = 0, k = 0;
for (k=0; k<(alen+blen); ++k)
{
// still got a's ?
if (a < alen)
{
// still got b's ?
if (b < blen)
{
// get "lesser" of the two.
if (fcmp((const unsigned char*)src + a*size,
(const unsigned char*)bsrc + b*size) <= 0)
{
// a is less. move it in.
memcpy((unsigned char *)dst + k*size,
(const unsigned char*)src + a++*size, size);
}
else
{ // b is less. move it in.
memcpy((unsigned char *)dst + k*size,
(const unsigned char*)bsrc + b++*size, size);
}
}
else
{ // no more b's. move the rest of the a's
// into the target and leave.
memcpy((unsigned char *)dst + k*size,
(const unsigned char*)src + a*size, (alen - a)*size);
k += (alen-a);
}
}
else
{ // else no a's. move the rest of the b's into
// the target and leave.
memcpy((unsigned char *)dst + k*size,
(const unsigned char*)bsrc + b*size, (blen - b)*size);
k += (blen-b);
}
}
// copy final output.
memcpy(src, dst, (alen+blen)*size);
free(dst);
}
// generic mergesort algorithm
void genmsort(void *src, unsigned int len, unsigned int size, fn_cmp fcmp)
{
if (len < 2)
return;
unsigned int mid = len/2;
genmsort(src, mid, size, fcmp);
genmsort((unsigned char*)src+(mid*size), len - mid, size, fcmp);
merge(src, mid, len-mid, size, fcmp);
}
int main()
{
static const unsigned int N = 50;
node *data = malloc(N * sizeof(*data));
int i=0;
srand((unsigned)time(NULL));
for (i=0;i<N;++i)
{
data[i].id = i+1;
sprintf(data[i].name, "String%.3d", 1 + rand() % 999);
}
// sort on names.
genmsort(data, N, sizeof(data[0]), compare_node_names);
for (i=0;i<N;++i)
printf("%s : %u\n", data[i].name, data[i].id);
printf("\n");
// use a different comparator, this time by id.
genmsort(data, N, sizeof(data[0]), compare_node_ids);
for (i=0;i<N;++i)
printf("%s : %u\n", data[i].name, data[i].id);
printf("\n");
free(data);
return 0;
}
Output
String053 : 49
String097 : 38
String104 : 46
String122 : 41
String129 : 8
String139 : 3
String168 : 30
String184 : 22
String222 : 16
String230 : 28
String249 : 4
String265 : 34
String285 : 44
String295 : 20
String298 : 47
String300 : 19
String321 : 2
String375 : 37
String396 : 50
String408 : 13
String430 : 31
String466 : 35
String483 : 24
String484 : 27
String491 : 25
String494 : 39
String507 : 10
String513 : 7
String514 : 11
String539 : 5
String556 : 29
String570 : 43
String583 : 33
String584 : 42
String620 : 15
String632 : 12
String671 : 21
String705 : 23
String710 : 14
String714 : 45
String724 : 18
String733 : 9
String755 : 48
String805 : 36
String814 : 6
String847 : 32
String876 : 40
String893 : 26
String906 : 17
String972 : 1
String972 : 1
String321 : 2
String139 : 3
String249 : 4
String539 : 5
String814 : 6
String513 : 7
String129 : 8
String733 : 9
String507 : 10
String514 : 11
String632 : 12
String408 : 13
String710 : 14
String620 : 15
String222 : 16
String906 : 17
String724 : 18
String300 : 19
String295 : 20
String671 : 21
String184 : 22
String705 : 23
String483 : 24
String491 : 25
String893 : 26
String484 : 27
String230 : 28
String556 : 29
String168 : 30
String430 : 31
String847 : 32
String583 : 33
String265 : 34
String466 : 35
String805 : 36
String375 : 37
String097 : 38
String494 : 39
String876 : 40
String122 : 41
String584 : 42
String570 : 43
String285 : 44
String714 : 45
String104 : 46
String298 : 47
String755 : 48
String053 : 49
String396 : 50
Auxilliary problems
Transcribed from the comments to the question.
For pity's sake, write compnode() sanely so that you don't have to go through the ghastly casts! Write it to take two const void * arguments and convert them in the code (it'll be a no-op):
int compnode(const void *v1, const void *v2)
{
const node *a = v1;
const node *b = v2;
return strcmp(a->name, b->name);
}
Also, don't use GCC's extensions. It is a bad habit if you have any pretensions towards writing portable code. Writing a+(n/2)*size where the argument is void *a is undefined behaviour per the C standard. You have to convert to char * (or some other type other than void *) before adding.
In genmnode(), you should be passing fcmp to the recursive functions and the merge() function, instead of passing compnode() directly.
Gannicus asked:
What do you mean pass fcmp instead of compnode?
WhozCraig explained:
[It] means you're passing your custom comparator function to the "generic" sort function as the fcmp parameter. Within that function, you blindly pass compnode to the recursive calls. You should be passing fcmp to those recursive calls instead, or your "generic" ideology just went out the window.
Primary problem
The primary problem is in your merge() function. The interface to that is most unusual. Normally, you pass two arrays to be merged, along with the size of each. You've chosen to pass one array and do some fancy footwork. The code in the main for loop in screws everything up.
void merge(void *a, int n, int size, int (*fcmp)(const void *, const void *)){
int i, j, k, mid=n/2;
void * temp = (void *)malloc(n*size);
for(i=0, j=mid, k=0; k<n; k++){
if((i<mid)&&(j>= n)){
memcpy(temp+(k*size), a+i*size, size);
i++;
}
else if((i<mid)&&(fcmp(a + i*size, a+j*size) <= 0)){
memcpy(temp+(k*size), a+j*size, size);
j++;
}
}
for(i=0, j=0; j<n; i++, j++)
memcpy(a+(j*size),temp+(i*size),size);
free(temp);
}
The trailing loop should be a single memcpy() operation, but what's there will work.
You have a single array, a, with a total of n elements of the given size. It must be treated as two sub-arrays, one of elements [0..mid), the LHS, and the other of elements [mid..n), the RHS. The ranges include the lower bound and exclude the upper bound.
The first condition inside the loop says 'if there is an element left in LHS and nothing left in RHS, copy the LHS element to the output'. The second condition says 'if there is an element left in the LHS (and, by elimination, there is an element in the RHS too), and the LHS compares smaller than the RHS, then copy the RHS element to the output'.
There are different and ultimately equivalent ways to write the merge process, but
normally the easiest to understand is:
while (item left in LHS and item left in RHS)
{
if (item in LHS is smaller than item in RHS)
copy LHS to result
else
copy RHS to result
}
while (item left in LHS)
copy item to result
while (item left in RHS)
copy item to result
The loop implemented does not come close to implementing that logic, or one of its equivalents.
Working code
Here's my diagnostic version of your code. The memset() at the top of merge() should not matter; you should be copying to temp and writing over all the X's. In practice, you are not.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct node node;
struct node
{
long stdno;
char name[20];
};
static
void genswap(void *v1, void *v2, size_t size)
{
char v3[size];
memmove(v3, v1, size);
memmove(v1, v2, size);
memmove(v2, v3, size);
}
static
void print_node(const char *tag, node a[], int n)
{
printf("%s\n", tag);
for (int i = 0; i < n; i++)
printf("%2d: %p %2ld %s\n", i, &a[i], a[i].stdno, a[i].name);
}
static
void merge(void *a, int n, int size, int (*fcmp)(const void *, const void *))
{
int i, j, k, mid = n/2;
void *temp = (void *)malloc(n*size);
memset(temp, 'X', n*size);
printf("-->> %s\n", __func__);
print_node("Before Merge", (node *)a, n);
for (i = 0, j = mid, k = 0; k < n; k++)
{
if ((i < mid) && (j >= n))
{
memcpy(temp+(k*size), a+i*size, size);
i++;
}
else if ((i < mid) && (fcmp(a + i*size, a+j*size) <= 0))
{
memcpy(temp+(k*size), a+j*size, size);
j++;
}
}
print_node("Mid Merge", (node *)temp, n);
for (i = 0, j = 0; j < n; i++, j++)
memcpy(a+(j*size), temp+(i*size), size);
free(temp);
print_node("After Merge", (node *)a, n);
printf("<<-- %s\n", __func__);
}
static
void genmsort(void *a, int n, int size, int (*fcmp)(const void *, const void *))
{
if (n > 1)
{
genmsort(a, n/2, size, fcmp);
genmsort(a+(n/2)*size, n-n/2, size, fcmp);
merge(a, n, size, fcmp);
}
}
static
int compnode(const void *v1, const void *v2)
{
const node *a = v1;
const node *b = v2;
printf("%s: (%ld:%s) vs (%ld:%s)\n", __func__, a->stdno, a->name, b->stdno, b->name);
return(strcmp(a->name, b->name));
}
static
void init_node(node a[], int n)
{
for (int i = 0; i < n; i++)
{
a[i].stdno = i+1;
sprintf(a[i].name, "%li", a[i].stdno);
}
srand(8);
for (int i = 0; i < n; i++)
genswap(a+i, a+(rand()%n), sizeof(node));
}
int main(void)
{
int n = 10;
node *b = (node *)malloc(n*sizeof(node));
init_node(b, n);
print_node("Before:", b, n);
genmsort(b, n, sizeof(node), compnode);
print_node("After:", b, n);
free(b);
return 0;
}
Let's say I have structure like this:
typedef struct MyStruct{
char *string1;
int number1, number2, number3;
char string2[11], string3[9];
char *string4;
char *string5;
}MyStruct;
Programs prompts user to choose by what field it should sort the data. I am having trouble thinking of a way to sort array effectively. Do I really need to write separate sorting functions for each field? There must be some other way, because writing 8 functions, where 2 would suffice, doesn't look rational.
Look up qsort() from <stdlib.h>. It takes a comparator function. You can write separate comparator functions for the different sort orders, but still use the standard library qsort() to do the sorting.
For example:
int ms_cmp_string1(const void *vp1, const void *vp2)
{
const MyStruct *ms1 = vp1;
const MyStruct *ms2 = vp2;
int cmp = strcmp(ms1->string1, ms1->string2);
if (cmp != 0)
return cmp;
else if (ms1->number1 < ms2->number1)
return -1;
else if (ms1->number1 > ms2->number1)
return +1;
//...other comparisons as required...
else
return 0;
}
This is a decent outline for comparators. This one sorts on string1 and then by number1. You can either write variants that sort on different fields, or devise a scheme that applies the various possible tests in an order of your choosing. But the basic outline works pretty well and is suitable for passing to qsort() without any casts necessary.
You don't need to write 8 functions if only 2 are needed. Build your own qsort function and send a last parameter containing the member offset to the compare function, then, in your compare function, cast pointer + offset to the right type.
Something like:
int comp_int(const void *pa, const void *pb, size_t offset)
{
const int *a = (const int *)((const char *)pa + offset);
const int *b = (const int *)((const char *)pb + offset);
return *a - *b;
}
int comp_string(const void *pa, const void *pb, size_t offset)
{
const char *a = (const char *)pa + offset;
const char *b = (const char *)pb + offset;
return strcmp(a, b);
}
void swap(void *v[], int a, int b)
{
void *temp;
temp = v[a];
v[a] = v[b];
v[b] = temp;
}
void sort(void *v[], int left, int right, size_t offset, int (*comp)(const void *, const void *, size_t))
{
int i, last;
if (left >= right) return;
swap(v, left, (left + right) / 2);
last = left;
for (i = left + 1; i <= right; i++) {
if ((*comp)(v[i], v[left], offset) < 0)
swap(v, ++last, i);
}
swap(v, left, last);
sort(v, left, last - 1, offset, comp);
sort(v, last + 1, right, offset, comp);
}
offsetof can help
Here is a sample of using qsort from my another answer:
struct stringcase { char* string; void (*func)(void); };
void funcB1();
void funcAzA();
struct stringcase cases [] =
{ { "B1", funcB1 }
, { "AzA", funcAzA }
};
struct stringcase work_cases* = NULL;
int work_cases_cnt = 0;
// comparator function
int stringcase_cmp( const void *p1, const void *p2 )
{
return strcasecmp( ((struct stringcase*)p1)->string, ((struct stringcase*)p2)->string);
}
// prepare the data for searching
void prepare() {
// allocate the work_cases and copy cases values from it to work_cases
qsort( cases, i, sizeof( struct stringcase ), stringcase_cmp );
}
If you're using the GNU C library, there's an extension called qsort_r() that lets you pass an extra parameter to the comparison function.
Using some macros:
#include <stdio.h>
#include <stdlib.h>
struct data {
int x, y, z;
};
#define comp(member) comp_##member
#define comp_build(member) \
int comp_##member(const void *pa, const void *pb) \
{ \
const struct data *a = pa, *b = pb; \
return (a->member < b->member) ? -1 : (a->member > b->member); \
}
comp_build(x)
comp_build(y)
comp_build(z)
int main(void)
{
#define ROWS 3
struct data v[] = {
{3, 2, 1},
{1, 3, 2},
{2, 1, 3}
};
int i;
puts("Unsorted");
for (i = 0; i < ROWS; i++) printf("%d %d %d\n", v[i].x, v[i].y, v[i].z);
qsort(v, ROWS, sizeof(struct data), comp(x));
puts("Sorted by x");
for (i = 0; i < ROWS; i++) printf("%d %d %d\n", v[i].x, v[i].y, v[i].z);
puts("Sorted by y");
qsort(v, ROWS, sizeof(struct data), comp(y));
for (i = 0; i < ROWS; i++) printf("%d %d %d\n", v[i].x, v[i].y, v[i].z);
puts("Sorted by z");
qsort(v, ROWS, sizeof(struct data), comp(z));
for (i = 0; i < ROWS; i++) printf("%d %d %d\n", v[i].x, v[i].y, v[i].z);
return 0;
}