简体   繁体   中英

sorting function for multiple types in C using void pointer and type argument

In this problem, A function that takes a void pointer to an array, number of elements and an integer indicating type of elements should sort the array. Are there any tricks to avoid writing same code 4 times as in the following solution?

//type 1:short, 2:int, 3:float, 4:double
void Sort(void *values, int nValues, int type) {
    int i, j, temp;

    switch(type) {
    case 1: //short
    {
        short *ptr = (short *)values;
        for (i = 0; i < nValues - 1; i++)
            for (j = i; j < nValues; j++)
                if (ptr[i] > ptr[j]) {
                    temp = ptr[i];
                    ptr[i] = ptr[j];
                    ptr[j] = temp;
                }
        break;
    }
    case 2: // int
    {
        int *ptr = (int *)values;
        for (i = 0; i < nValues - 1; i++)
            for (j = i; j < nValues; j++)
                if (ptr[i] > ptr[j]) {
                    temp = ptr[i];
                    ptr[i] = ptr[j];
                    ptr[j] = temp;
                }
        break;
    }
    case 3: // float
    {
        float *ptr = (float *)values;
        for (i = 0; i < nValues - 1; i++)
            for (j = i; j < nValues; j++)
                if (ptr[i] > ptr[j]) {
                    temp = ptr[i];
                    ptr[i] = ptr[j];
                    ptr[j] = temp;
                }
        break;
    }
    case 4: // double
    {
        double *ptr = (double *)values;
        for (i = 0; i < nValues - 1; i++)
            for (j = i; j < nValues; j++)
                if (ptr[i] > ptr[j]) {
                    temp = ptr[i];
                    ptr[i] = ptr[j];
                    ptr[j] = temp;
                }
    }
    }
}

Yes, there is an alternative. In the C standard library there already is a function called qsort (which probably is an abbreviation of quick sort, but it could be any other sorting algorithm as well). To use that function, you pass it the array, the number of elements in the array, the size of each element and a comparison function.

You just need to write the comparison function for each data type, as shown in the example on that page.

If you want to merge the 4 comparison functions into a single function, you have to pass some context information to the comparison function. In that case you cannot use qsort anymore but have to use qsort_s . Then your comparison function could look like:

#define compare(a, b) (((a) > (b)) - ((b) > (a)))
#define compare_ptr(type) (compare(*(type *)(p1), *(type *)(p2)))

static int compare_by_type(const void *p1, const void *p2, void *ctx) {
    int type = *(int *) ctx;
    switch (type) {
    case 1: return compare_ptr(short);
    case 2: return compare_ptr(int);
    case 3: return compare_ptr(float);
    case 4: return compare_ptr(double);
    default: return 0;
    }
}

#undef compare
#undef compare_ptr

int main(void) {
    int iarray[] = {1, 6, 4, 9, 55, 999, -33333};
    int sort_type = 1;

    qsort_s(iarray, 7, sizeof(int), compare_by_type, &type);
}

That's some fairly advanced stuff:

  • passing function pointers around
  • doing pointer stuff with pointers to arbitrary types
  • using macros that accept type names as macro parameter
  • mixing boolean arithmetic with integer arithmetic

But in the end, it's trivial to add more types to the list, as long as they support the < operator.

Note that float and double don't even belong to this category since their operator < returns false as soon as one of the numbers is NaN , which means Not A Number, and results from expressions such as 0.0 / 0.0 . As soon as you have such a value in the array, the behavior becomes undefined. The sorting function might even get stuck in an endless loop. To fix this, change the definition of the compare macro:

#define compare(a, b) (((a) > (b)) - !((b) <= (a)))

It looks even more complicated now, but works for NaN . For example:

compare(NaN, 5)
= (NaN > 5) - !(5 <= NaN)
= false - !(5 <= NaN)
= false - !(false)
= false - true
= 0 - 1
= -1

This means that NaN will be sorted to the front of the array.

compare(NaN, NaN)
= (NaN > NaN) - !(NaN <= NaN)
= false - true
= -1

Dammit. Comparing two NaNs should have resulted in 0, meaning they are equal. So in that special case there needs to be a correction:

#define compare(a, b) (((a) > (b)) - ((b) > (a)) - ((a) != (a) || (b) != (b)))

Since NaN is the only value that compares unequal to itself, this additional code does not affect the integer arithmetic and should be optimized away by the compiler.

Here are two different approached to avoiding code duplication for your problem:

If you cannot use library functions or want to keep your algorithm, here is a solution with a preprocessor macro:

#define SORT_TYPE(values, nValues, type) do {\
        type *ptr = (type *)(values);        \
        int i, j, n = (nValues);             \
        for (i = 0; i < n - 1; i++) {        \
            for (j = i + 1; j < n; j++) {    \
                if (ptr[i] > ptr[j]) {       \
                    type temp = ptr[i];      \
                    ptr[i] = ptr[j];         \
                    ptr[j] = temp;           \
                }                            \
            }                                \
        }                                    \
    } while (0)

//type 1:short, 2:int, 3:float, 4:double
void Sort(void *values, int nValues, int type) {
    switch (type) {
      case 1: //short
        SORT_TYPE(values, nValues, short);
        break;
      case 2: // int
        SORT_TYPE(values, nValues, int);
        break;
      case 3: // float
        SORT_TYPE(values, nValues, float);
        break;
      case 4: // double
        SORT_TYPE(values, nValues, double);
        break;
    }
}

Notes:

  • The macro SORT_TYPE can be invoked with arguments that have side effects as they are evaluated once only, but it is still fragile: the code generated would break if the arguments were expressed in terms of variables names ptr , i , j or n . It is possible to try and make these collisions less likely by naming the block variables ptr__ , or some other contorted way, but it does not completely solve the problem. Macros must be written with extreme attention and used with care.

  • NaN values in float and double arrays may not be handled properly as the comparisons will return false if one or both arguments as NaN s. With the naive bubble sort algorthm, they will stay in place, which may or may not be appropriate. With other sorting algorithms or just a slight variation of this one, the behavior may be different, possibly undefined.

  • Your bubble sort implementation should use i = j + 1 for marginally better performance.

  • The bubble sort algorithm is very inefficient for large arrays with a time complexity of O(N 2 ) .

Here is a more efficient approach where the sorting algorithm is left to the C library function qsort and a specific comparison function is written for each type:

int shortCmp(const void *aa, const void *bb) {
    short a = *(const short *)aa;
    short b = *(const short *)bb;
    return (b < a) - (a < b);
}

int intCmp(const void *aa, const void *bb) {
    int a = *(const int *)aa;
    int b = *(const int *)bb;
    return (b < a) - (a < b);
}

int floatCmp(const void *aa, const void *bb) {
    float a = *(const float *)aa;
    float b = *(const float *)bb;
    if (a != a || b != b) {
        /* sort NaN values to the end of the array */
        return (a != a) - (b != b);
    }
    return (b < a) - (a < b);
}

int doubleCmp(const void *aa, const void *bb) {
    double a = *(const double *)aa;
    double b = *(const double *)bb;
    if (a != a || b != b) {
        /* sort NaN values to the end of the array */
        return (a != a) - (b != b);
    }
    return (b < a) - (a < b);
}

//type 1:short, 2:int, 3:float, 4:double
void Sort(void *values, int nValues, int type) {
    switch (type) {
      case 1: //short
        qsort(values, nValues, sizeof(short), shortCmp);
        break;
      case 2: // int
        qsort(values, nValues, sizeof(int), intCmp);
        break;
      case 3: // float
        qsort(values, nValues, sizeof(float), floatCmp);
        break;
      case 4: // double
        qsort(values, nValues, sizeof(double), doubleCmp);
        break;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM