Подтвердить что ты не робот

Передайте дополнительный параметр в компаратор для qsort

Мне просто интересно, есть ли способ передать дополнительный параметр моему компаратору, который затем будет использоваться в моей функции qsort?

Например, у меня есть эти 2 компаратора (один в порядке возрастания и один по убыванию)

qsort(entries, 3, sizeof(struct entry), compare_desc);

int compare_asc(const void *elem1, const void *elem2)
{
     return strcmp(elem1.name.last, elem2.name.last);
}


int compare_desc(const void *elem1, const void *elem2)
{
     return strcmp(elem2.name.last, elem1.name.last);
}

Есть ли способ, чтобы я мог сделать что-то вроде этого:

int compare(const void *elem1, const void *elem2, const char *order)
{
     if (strcmp(order, "asc") == 0)
         return strcmp(elem1.name.last, elem2.name.last);
     else if (strcmp(order, "desc") == 0)
         return strcmp(elem2.name.last, elem1.name.last);
}

Причина, о которой я прошу, заключается в том, что моя программа сортировки должна принимать переключатели, и если у меня есть 2 разных переключателя (+ a, -a) для восходящего и нисходящего соответственно, тогда я должен сделать 2 разных функции компаратора. Если я добавлю больше, это усложнится. Есть ли способ улучшить дизайн этой программы?

EDIT: глобальных и внешних переменных не разрешено.

4b9b3361

Ответ 1

Старый вопрос, но в случае, если кто-то наткнется на него...

Существуют нестандартные версии qsort(), которые позволяют передавать дополнительный параметр функции обратного вызова. glib предлагает qsort_r(), в то время как VC дает вам qsort_s().

Ответ 2

В примере вашего примера лучше иметь два разных компаратора. Если бы у вас было только одно, каждое сравнение было бы необязательно определять порядок сортировки, который вы не могли бы изменить в середине сортировки в любом случае для каких-либо значимых результатов. Поэтому вместо того, чтобы помещать if (ascending_sort) { } else { } внутри компаратора, поставьте его при вызове qsort:

qsort(e, n, sizeof(*e), (strcmp(order, "asc") ? compare_desc : compare_asc));

Изменить: Некоторые советы, если вы добавите больше компараторов:

- помните, что вам не нужно переписывать каждый компаратор; вы можете позвонить им друг другу, если вы сортируете по нескольким полям (и вы всегда можете инвертировать результат компаратора с -, например, compare_asc(a, b) может возвращать -compare_desc(a, b)).

- легко отменить порядок всего массива в конце, поэтому вам не нужно удваивать количество компараторов для поддержки опции для изменения всего порядка сортировки

- вы можете заменить оператор trinary (? :) в моем примере функцией, которая возвращает соответствующий компаратор, как предложено в комментариях ниже

Ответ 3

Что вам нужно сделать, это переключить аргументы на qsort, чтобы вы соответствующим образом передали указатель на функцию.

Учитывая ваш сценарий, это может быть что-то вроде

// selectively invoke qsort:
if(strcmp(var, "+a")){
    qsort(entries, 3, sizeof(struct entry), compare_asc);
}else{
    qsort(entries, 3, sizeof(struct entry), compare_desc);
}

Или вы можете сделать что-то вроде:

// declare a function pointer
int (*func)(const void*, const void*);

// sometime later decide which function to assign
// to the function pointer
if(strcmp(var, "+a")){
    func = compare_asc;
}else{
    func = compare_Desc;
}

// sometime later invoke qsort
qsort(entries, 3, sizeof(struct entry), compare_desc);

Ответ 4

> Есть ли способ улучшить дизайн этой программы?

Не делайте этого - это не улучшение дизайна, это просто эксперимент.

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

int comparefx(const void *a, const void *b) {
    static int extra = 0;
    if (a == NULL) {
        extra = (int)b;
        return 0;
    }
    switch (extra) {
        case 24: puts("24"); return *(const int*)a + *(const int*)b; break;
        case 42: puts("42"); return *(const int*)b - *(const int*)a; break;
        default: puts("--"); return *(const int*)a - *(const int*)b; break;
    }
}

int main(void) {
    int entries[] = {4, 2, 8};

    qsort(entries, 3, sizeof *entries, comparefx);
    printf("%d %d %d\n", entries[0], entries[1], entries[2]);

    comparefx(NULL, (void*)42); /* set 'extra' parameter */
    qsort(entries, 3, sizeof *entries, comparefx);
    printf("%d %d %d\n", entries[0], entries[1], entries[2]);

    return 0;
}

Он компилирует "чисто" с помощью 3 компиляторов

$ gcc -std=c89 -pedantic -Wall 4210689.c
4210689.c: In function 'comparefx':
4210689.c:7: warning: cast from pointer to integer of different size

$ clang -std=c89 -pedantic -Wall 4210689.c
$ tcc -Wall 4210689.c
$ 

И он работает как ожидалось

$ ./a.out
--
--
--
2 4 8
42
42
42
8 4 2

Ответ 5

В простых случаях вы можете использовать глобальную переменную.

Ответ 6

Без использования глобальной переменной AFAIK вообще вы не можете, вам нужно предоставить две разные функции для двух методов сортировки. На самом деле это одна из причин причин, почему часто используются функторы С++ (объекты, которые предоставляют перегруженный оператор функциональных вызовов).

Ответ 7

Отсутствие классов и закрытий в значительной степени означает, что вы застряли в написании отдельного компаратора для каждого различного типа сравнения, которое вы хотите.

Одна вещь, которую вы можете сделать, - это каждый элемент массива быть структурой, содержащей поля value и sort_order. Все поля sort_order будут одинаковыми... но это хуже, чем просто наличие двух компараторов.

Подумайте об этом так: вы все равно напишите все-таки код компаратора. Но вместо того, чтобы иметь сложный вложенный if/else с 8 случаями, у вас есть 8 функций. Разница заключается в некоторых дополнительных объявлениях функций.

РЕДАКТИРОВАТЬ: ответить на комментарии R.. это хороший момент. У меня было это раньше, но я удалил его:

Вы можете создать фреймворк, подобный функции Python list.sort(). В значительной степени:

  • Создайте структуру с полями value и sortvalue.
  • Поместите начальные значения в value.
  • Напишите любой код, чтобы преобразовать элементы в поле sortvalue.
  • Используйте стандартный компаратор, а также qsort.
  • Когда вы закончите, просто возьмите элементы из полей value. Они будут отсортированы в соответствии с sortvalue, но значения будут правильными.

Это используется, например, в Python, где, если вы хотите отсортировать, скажем, 4-й элемент в кортеже, вы не пишете целый компаратор (например, lambda v1,v2: v1[3]-v2[3]), а вместо этого просто преобразуете входы с помощью key (lambda k: k[3]) и использовать стандартный метод сортировки. Он будет работать в случае "миллиардных сортов", поскольку ваш код может выполнять любую сложную операцию с каким-либо количеством входов для преобразования значений.

Ответ 8

Просто используйте лямбда-функцию для закрытия. Что-то вроде этого кода на С++:

string sortOrder="asc";   
qsort(entries, 3, sizeof(struct entry),    
[=](const void *elem1, const void *elem2) -> int{
        myCompare(elem1,elem2,sortOrde)             

});

Ответ 9

qsort_r() и qsort_s()

Существуют функции, называемые qsort_r() или qsort_s(), доступные в некоторых реализациях, которые делают то, что вы хотите - используйте указатель на дополнительные данные, переданные в функции компаратора.

Варианты реализации BSD (включая macOS или Mac OS X) предоставляют версию qsort_r(), а также библиотеку GNU C. К сожалению, два варианта имеют разные подписи. Это не мешает им быть полезным, но это означает, что один и тот же исходный код не может использоваться на двух платформах, и, кроме того, вы должны убедиться, что вы понимаете, какой вариант qsort_r() доступен на любом компьютере, на котором вы пытаетесь использовать его.

Аналогично, Microsoft предоставляет версию qsort_s(), а стандарт C11 определяет версию qsort_s() (в качестве дополнительной функции в Приложении K на основе TR-24731), но эти два отличаются в подписи снова. Может быть, повезло, что функции Приложения К широко не реализованы.

BSD qsort_r()

void qsort_r(void *base, size_t nel, size_t width, void *thunk,
             int (*compar)(void *, const void *, const void *));

Библиотека GNU C qsort_r()

void qsort_r(void *base, size_t nmemb, size_t size,
             int (*compar)(const void *, const void *, void *),
             void *arg);

Обратите внимание, что в BSD "thunk" эквивалентен "arg" в GNU, но эти аргументы отображаются в разных местах в вызывающей последовательности функции qsort_r() (до и после указателя функции компаратора). Кроме того, обратите внимание, что "thunk" передается как аргумент 1 в функции BSD-компаратора, но "arg" передается как аргумент 3 в функции компаратора GNU.

Мнемоника для qsort_r: данные контекста указываются относительно компаратора в вызывающей последовательности в том же отношении, что и контекст передается компараторам по отношению к двум сравниваемым значениям. Контекст перед указателем на компаратор означает контекст перед значениями в вызове компаратора; контекст после указателя на компаратор означает контекст после значений в вызове компаратора.

Приложение K qsort_s()

errno_t qsort_s(void *base, rsize_t nmemb, rsize_t size,
               int (*compar)(const void *x, const void *y, void *context),
               void *context);

Приложение K qsort_s() является уникальным при возврате значения; все остальные варианты не возвращают никакой ценности. В противном случае, для большинства практических целей, он соответствует функции GNU qsort_r().

Microsoft qsort_s()

void qsort_s(void *base, size_t num, size_t width,
             int (__cdecl *compare )(void *, const void *, const void *),
             void * context);

Различие rsize_t и size_t не очень важно при сравнении вариантов приложения K и Microsoft qsort_s(), но в Приложении K qsort_s() контекст передается в качестве аргумента 3 компаратору, но в Microsoft qsort_s() контекст передается в качестве аргумента 1 в компаратор.

Резюме

Функции, называемые qsort_r() или qsort_s(), обеспечивают требуемую функциональность. Тем не менее, вы должны проверить спецификацию платформы, для которой присутствует функция, и правильную последовательность вызовов для аргументов функции сортировки и правильную последовательность вызовов для аргументов для компараторов.

Номинально, вы также должны проверить тип возврата функции, но мало программ рассмотрят ее проверку, главным образом потому, что большинство вариантов qsort() не возвращают значения.