简体   繁体   中英

How to reuse qsort compare function so I can switch between different comparer

I want to switch between comparer in qsort instead creating 5 different comparer, but for some reason the program doesn't work. I don't have any idea why this is happen. Can anyone explain please? Thanks!

struct propertySales{
    unsigned int tanggal[3];
    char pelanggan[30];
    char jenisProperty[30];
    char namaProperty[30];
    int jumlahProperty;
    double hargaProperty;
    double totalPembayaran;
}Arr[100], compare[100], temp;
typedef int (*compfn)(const void*, const void*);
int compare_harga(struct propertySales *, struct propertySales *);
int compare_pembayaran(struct propertySales *, struct propertySales *);
int compare_harga(struct propertySales *elem1, struct propertySales *elem2){
    if( elem1->hargaProperty < elem2->hargaProperty){
        return -1;
    }
    else if(elem1->hargaProperty > elem2->hargaProperty){
        return 1;
    }
    else{
        return 0;
    }   
}

int compare_pembayaran(struct propertySales *elem1, struct propertySales *elem2){
    if( elem1->totalPembayaran < elem2->totalPembayaran){
        return -1;
    }
    else if(elem1->totalPembayaran > elem2->totalPembayaran){
        return 1;
    }
    else{
        return 0;
    }   
}

The compiler throw warning saying --- [Warning] cast to pointer from integer of different size [-Wint-to-pointer-cast]

Short code

Let's start with our structures. I'm going to mess with them, just for the purposes of having a greater variety of object types inside.

// property-sales.h

struct Tanggal { // date
    short tahun; // year
    char  bulan; // month
    char  hari;  // day
};
typedef struct Tanggal Tanggal;

struct Penjualan_Properti {
    Tanggal      tanggal_penjualan;   // date of sale      // two different ways
    time_t       tanggal_pembelian;   // date of purchase  // to manage a date
    char         jalan[32];           // street name
    int          nomor;               // number
    const char * kelurahan;           // subdistrict       // These are strings kept 
    const char * kecamatan;           // district          // elsewhere, selected from
    const char * kota_atau_kabupaten; // city or regency   // a list.
    const char * propinsi;            // province          //
    char         kode_pos[6];         // zip code
    double       luasnya_km;          // area (km)
    double       harga;               // price
    double       total_pembayarn;     // total payment to date
    Pelanggan  * pelanggan;           // customer
    char         keadaan;             // status   'U'=tertunda, 'J'=terjual, 'A'=arsip
};
typedef struct Penjualan_Properti Penjualan_Properti;

// Precision for floating point comparisons can be controlled
extern double epsilon;

Now for comparator functions.

// property-sales.c

#include "property-sales.h"

// Precision for floating point comparisons
double epsilon = 0.00001;

// Now we just list a comparator function for every kind of sort order we want

int membandingkan_nomor_Penjualan_Properti( const void * _a, const void * _b ) {
    // Sort non-decreasing by building number
    const Penjualan_Properti * a = _a;
    const Penjualan_Properti * b = _b;
    return (*a < *b) ? -1 : (*b < *a);
}

int membandingkan_kode_pos_Penjualan_Properti( const void * _a, const void * _b ) {
    // Sort non-decreasing by building number
    const Penjualan_Properti * a = _a;
    const Penjualan_Properti * b = _b;
    return strcmp( a->kode_pos, b->kode_pos );
}

// ...and so on... (for as many ways as you wish to sort two Property_Sales objects)

That's it! Now to sort your data you just need to use the proper comparator function!

qsort( properti, ARRAY_SIZE(properti), sizeof(Penjualan_Properti),
    &membandingkan_kode_pos_Penjualan_Properti );

In English:

qsort( properties, ARRAY_SIZE(properties), sizeof(Property_Sales),
    &compare_Property_Sales_zip_code );

Shorter code with X-macros

You can reduce the repetative typing quite a bit with some macros, but the real benefit here is ODR , which is a useful concept in C as well as C++.

The idea is to create a single table of information that we will afterward use to construct our objects.

// property-sales.h

#include <time.h>

#define IS_INDEXABLE(arg) (sizeof(arg[0]))
#define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg)))
#define ARRAY_SIZE(arr) (IS_ARRAY(arr) ? (sizeof(arr) / sizeof(arr[0])) : 0)

struct Tanggal {
    short tahun;   // year
    char  bulan;   // month
    char  hari;    // day
};
typedef struct Tanggal Tanggal;

typedef struct Pelanggan { int foo; } Pelanggan;  // Client struct stub defintion

//     type,         name,         dimensions, comparator function code
#define PENJUALAN_PROPERTI(X) \
    X( Tanggal,      tanggal_penjualan,      , (a->tahun < b->tahun) ? -1 : (a->tahun < b->tahun) ? 1 :   \
                                               (a->bulan < b->bulan) ? -1 : (a->bulan < b->bulan) ? 1 :   \
                                               (a->hari  < b->hari)  ? -1 : (a->hari  < b->hari);       ) \
    X( time_t,       tanggal_pembelian,      , (*a < *b) ? -1 : (*b < *a) ) \
    X( char,         jalan,              [32], strcmp( a, b ) ) \
    X( int,          nomor,                  , (*a < *b) ? -1 : (*b < *a) ) \
    X( const char *, kelurahan,              , strcmp( *a, *b ) ) \
    X( const char *, kecamatan,              , 0 ) /* see note below */ \
    X( const char *, kota_atau_kabupaten,    , strcmp( *a, *b ) ) \
    X( const char *, propinsi,               , strcmp( *a, *b ) ) \
    X( char,         kode_pos,            [6], strcmp(  a,  b ) ) \
    X( double,       luasnya_km,             , (fabs(*a-*b) < epsilon) ? 0 : (*a < *b) ? -1 : 1 ) \
    X( double,       harga,                  , (fabs(*a-*b) < epsilon) ? 0 : (*a < *b) ? -1 : 1 ) \
    X( double,       total_pembayarn,        , (fabs(*a-*b) < epsilon) ? 0 : (*a < *b) ? -1 : 1 ) \
    X( Pelanggan *,  pelanggan,              , membandingkan_Pelanggan( *a, *b ) ) \
    X( char,         keadaan,                , status_cmp( *a, *b ) ) \

// Create the struct!
#define F(type, name, extents, code) type name extents;
struct Penjualan_Properti { PENJUALAN_PROPERTI(F) };
typedef struct Penjualan_Properti Penjualan_Properti;
#undef F

// Create comparator function prototypes
#define F(type, name, extents, code) \
    int membandingkan_ ## name ## _Penjualan_Properti( const void * _a, const void * _b );
PENJUALAN_PROPERTI(F)
#undef F

// Precision for floating point comparisons can be controlled
extern double epsilon;

// Order of sorting by status code can be controlled as well:
extern char status_order[4];  // 'U'=tertunda, 'J'=terjual, 'A'=arsip

Woah: Yeah! Now to generate the comparator functions:

// property-sales.c

#include <stddef.h>
#include <string.h>
#include "property-sales.h"

// Precision for floating point comparisons
double epsilon = 0.00001;

// Helpers to sort status code as we desire. Defaults to:
char status_order[4] = "UJA";

int status_cmp( char _a, char _b )
{
    int a = (int)(strchr( status_order, _a ) - status_order);
    int b = (int)(strchr( status_order, _b ) - status_order);
    return (a < b) ? -1 : (b < a);
}

// Also, that compare_client() function needs to exist
int membandingkan_Pelanggan( const Pelanggan * a, const Pelanggan * b ) {
    return 0;  // Change this to something that properly sorts by client.
               // Since a Pelanggan is a struct, feel free to use any
               // dirty trick you like to compare them by a specific element.
               // For example, a `client_order` global may exist that
               // is itself just a function pointer to a `compare_Client_name`
               // function or a `compare_Client_district` function, etc, which
               // we could use here as:
               //   return client_order( a, b );
}

// Generate the comparator functions!
#define F(type, name, extents, code) \
    int membandingkan_ ## name ## _Penjualan_Properti( const void * _a, const void * _b ) { \
        type const * a = (const void *)((const char *)_a + offsetof(Penjualan_Properti,name)); \
        type const * b = (const void *)((const char *)_b + offsetof(Penjualan_Properti,name)); \
        return code; \
    }
PENJUALAN_PROPERTI(F)
#undef F

That was... surprisingly short, right? Even with all the additional code doign stuff the original code could not, and complete code for all comparator functions (because we left them all out in the first example).

The obvious tradeoff is that now we are in tricky-code territory.
The original code is very clear and easy to read. This stuff takes extra brainpower.

Whether it is worth it or not is a debate to be had elsewhere. For now, we can easily sort an array of Property_Sales against any member.

strcpy( status_order, "AUJ" ); // sort by status: Archived, Pending, Sold
qsort( properti, ARRAY_SIZE(properti), sizeof(Penjualan_Properti),
    &membandingkan_keadaan_Penjualan_Properti );

English:

strcpy( status_order, "APS" ); // sort by status: Archived, Pending, Sold
qsort( properties, ARRAY_SIZE(properties), sizeof(Property_Sales),
    &compare_Property_Sales_status );

If that Client structure existed, and had things set up to sort Clients by a specific member comparator function, then you could sort by client as well:

pelanggan_order = &membandingkan_nama_keluarga_Pelanggan;
qsort( properti, ARRAY_SIZE(properti), sizeof(Penjualan_Properti),
    &membandingkan_pelanggan_Penjualan_Properti

English:

client_order = &compare_Client_surname;
qsort( properties, ARRAY_SIZE(properties), sizeof(Property_Sales),
    &compare_Property_Sales_client );

That's it!

All the code is still repetitive, but we put all that repetition into some macros to generate code for us from a table. This trick is known as the X-macro trick .

Noted from above: we made the district effectively unsortable by simply returning a 0 value. We could have should have given it sort ability just like all the other elements around it. The point is just to provide an example!

I should point out that I also used the (in)famous offsetof() macro to make things nicer for us. In the sort function a and b are now direct pointers to the member elements to compare (and not to the structure itself as before). Notice how this had a direct effect on the comparison code in the PENJUALAN_PROPERTI table: be careful to remember at what level of indirection you are at. Notice, for example, the difference when using strcmp() on a member string array versus a member string pointer .

Remember also that qsort() is neither re-entrant nor stable. Playing with global state like we are means we need to be careful about threaded and recursive uses.

Oh, and find that ARRAY_SIZE macro here (with a slightly different name).

Remember, with great power comes great responsibility!

OP requested example of longer block of code to illustrate usage.

(No warranty that this is suitable as is.)

int compare_pembayaran( const void *p1, const void *p2 ) {
    struct propertySales *elem1 = (struct propertySales *)p1;
    struct propertySales *elem2 = (struct propertySales *)p2;

    if( elem1->totalPembayaran < elem2->totalPembayaran)
        return -1;
    
    return elem1->totalPembayaran > elem2->totalPembayaran;
}

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