Pasar parámetro extra al comparador para qsort

Me pregunto si hay una manera de pasar un parámetro adicional a mi comparador que luego se usará en mi función qsort.

Por ejemplo tengo estos 2 comparadores (uno en orden ascendente y otro en descendente)

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); } 

¿Hay alguna manera de hacer algo como esto?

 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); } 

La razón por la que pregunto es que mi progtwig de clasificación tiene que tomar interruptores y si tengo 2 interruptores diferentes (+ a, -a) para ascender y descender respectivamente, entonces tengo que hacer 2 funciones de comparación diferentes. Si agrego más, se vuelve más complicado. ¿Hay alguna manera de mejorar el diseño de este progtwig?

EDITAR: No se permiten variables globales y externas.

Vieja pregunta, pero en caso de que alguien se tropiece con eso …

Existen versiones no estándar de qsort () que le permiten pasar un parámetro adicional a la función de callback. glib ofrece qsort_r () mientras que VC le da qsort_s ().

En su caso de ejemplo, es mejor tener dos comparadores diferentes. Si solo tuviera uno, cada comparación tendría que determinar innecesariamente el orden de clasificación, que de todos modos no podría cambiar de orden intermedio para obtener resultados significativos. Entonces, en lugar de poner el if (ascending_sort) { } else { } dentro del comparador, ponlo en tu llamada qsort :

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

Edición: Algunos consejos si añades más comparadores:

– recuerde que no necesita volver a escribir cada comparador; puede hacer que se llamen entre sí si está clasificando en varios campos (y siempre puede invertir el resultado de un comparador con - , por ejemplo, compare_asc(a, b) puede devolver -compare_desc(a, b) ).

– es fácil invertir el orden de toda la matriz al final, por lo que no necesita duplicar el número de comparadores para admitir una opción para invertir todo el orden de clasificación

– puede reemplazar el operador trinario ( ? : 🙂 En mi ejemplo con una función que devuelve el comparador apropiado como se sugiere en los comentarios a continuación

Lo que debe hacer es cambiar los argumentos a qsort para que pase el puntero a la función según corresponda.

Dado su escenario, podría ser algo como

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

O alternativamente puedes hacer algo como:

 // 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); 

> ¿Hay alguna manera de mejorar el diseño de este progtwig?

No hagas esto, esto no es una mejora de diseño , es solo un experimento.

 #include  #include  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; } 

Comstack “limpiamente” con 3 comstackdores.

 $ gcc -std = c89 -pedantic -Wall 4210689.c
 4210689.c: En la función 'comparefx':
 4210689.c: 7: advertencia: convertir desde puntero a entero de diferente tamaño

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

Y funciona como se esperaba

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

En casos simples, puede utilizar una variable global.

Sin usar una variable global, AFAIK en general no puede, debe proporcionar dos funciones diferentes para los dos métodos de clasificación. En realidad, esta es una de las razones por las que los funtores de C ++ (objetos que proporcionan un operador de llamada de función sobrecargado) se usan a menudo.

La falta de clases y cierres significa que estás atascado en escribir un comparador separado para cada tipo diferente de comparación que desees.

Una cosa que podría hacer es que cada elemento de la matriz sea una estructura, que contenga los campos value y sort_order . Todos los campos sort_order serían iguales … pero eso es peor que tener solo 2 comparadores.

Piénsalo de esta manera: de todos modos terminas escribiendo el mismo código de comparación. Pero en lugar de tener un complejo nested si / else con 8 casos, tiene 8 funciones. La diferencia es algunas declaraciones de funciones extra.

EDITAR: Para responder a los comentarios de R … ese es un buen punto. Tenía esto antes pero lo borré:

Puede crear un marco similar a la función list.sort() Python. Bastante mucho

  • Crea una estructura con value y campos de value sortvalue .
  • Poner valores iniciales en value .
  • Escriba cualquier código para transformar los elementos en el campo sortvalue .
  • Use un comparador estándar en eso, junto con qsort .
  • Cuando hayas terminado, simplemente saca los elementos de los campos de value . Se ordenarán según el valor de sortvalue pero los valores serán correctos.

Esto se usa en Python, por ejemplo, donde si quiere ordenar por decir el cuarto elemento en una tupla, no escribe un comparador completo (como lambda v1,v2: v1[3]-v2[3] ), pero en su lugar, simplemente transforme las entradas con una función key ( lambda k: k[3] ) y utilice un método de clasificación estándar. Funcionaría en el caso de los “miles de millones de clases”, ya que su código puede hacer cualquier operación complicada con muchas entradas para transformar los valores.

Simplemente use una función lambda para el cierre. Algo así como este código C ++:

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

qsort_r() y qsort_s()

Existen funciones llamadas qsort_r() o qsort_s() disponibles en algunas implementaciones que hacen lo que usted desea: lleve un puntero a los datos adicionales que se pasan a las funciones del comparador.

Las implementaciones de la variante BSD (incluyendo macOS o Mac OS X) proporcionan una versión de qsort_r() , y también la biblioteca de GNU C. Lamentablemente, las dos variantes tienen diferentes firmas. Eso no impide que sean útiles, pero sí significa que no se puede usar el mismo código fuente en las dos plataformas, y además debe asegurarse de que entiende qué variante de qsort_r() está disponible en cualquier máquina donde intente para usarlo.

De manera similar, Microsoft proporciona una versión de qsort_s() y el estándar C11 define una versión de qsort_s() (como una función opcional en el Anexo K, basada en TR-24731), pero las dos difieren en la firma nuevamente. Tal vez sea una suerte que las funciones del Anexo K no estén ampliamente implementadas.

BSD qsort_r()

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

qsort_r() biblioteca de GNU C qsort_r()

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

Tenga en cuenta que en BSD, el ‘thunk’ es equivalente a ‘arg’ en GNU, pero estos argumentos aparecen en diferentes lugares en la secuencia de llamada a la función qsort_r() (antes y después del puntero de la función comparadora). Además, tenga en cuenta que el ‘thunk’ se pasa como argumento 1 a las funciones de comparación de BSD, pero el ‘argumento’ se pasa como argumento 3 a las funciones de comparador de GNU.

qsort_r para qsort_r : los datos de contexto se especifican en relación con el comparador en la secuencia de llamada en la misma relación que el contexto se pasa a los comparadores en relación con los dos valores que se comparan. Contexto antes de puntero a comparador significa contexto antes de valores en llamada a comparador; contexto después de puntero a comparador significa contexto después de valores en llamada a comparador.

Anexo 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); 

El anexo K qsort_s() es único en la devolución de un valor; Todas las otras variantes no devuelven ningún valor. De lo contrario, para los propósitos más prácticos, coincide con la función qsort_r() GNU.

Microsoft qsort_s()

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

La distinción rsize_t y size_t no es muy importante al comparar el anexo K y las variantes de qsort_s() Microsoft, pero en el anexo K qsort_s() , el contexto se pasa como argumento 3 al comparador, pero en el qsort_s() Microsoft, El contexto se pasa como argumento 1 al comparador.

Resumen

Las funciones llamadas qsort_r() o qsort_s() proporcionan la funcionalidad requerida. Sin embargo, debe verificar la especificación de la plataforma para la cual la función está presente y la secuencia de llamada correcta para los argumentos de la función de clasificación, y la secuencia de llamada correcta para los argumentos de los comparadores.

Nominalmente, también debe verificar el tipo de retorno de la función, pero pocos progtwigs considerarían revisarlo, principalmente porque la mayoría de las variantes de qsort() no devuelven ningún valor.