1

I made a qsort function for a larger program. It is to sort by time. I have a class schedule I am working with and found a need to compare time with AM and PM. ie if A is chosen then all the classes in the morning, if P is chosen all the classes in the afternoon. My question is this, is there a way to use this sort function with a greater than or less than comparator? If so can someone show me how if it is not to much trouble?

int sortFunction(const void *p, const void *q) {
    return ((sched_record *) p)->start.hour -
                   ((sched_record *) q)->start.hour;
}
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
c_sharp
  • 281
  • 1
  • 5
  • 13
  • 2
    I'm confused, could you possibly expand on what you're asking? – pg1989 May 01 '12 at 23:36
  • @pg1989 Yes, I have a class schedule that has several constraints. One is whether a class is in the morning(A) or afternoon(P). I have tried this statement `if(data[i].start.hour >= '12')` and I get a warning error of `multi-character character constant`. When I try `if(data[i].start.hour >= "12")` I get the ole `comparison between pointer and integer`. – c_sharp May 02 '12 at 00:29
  • 1
    you should represent your time hours as number not string. You can even use floats like 0.5 to represent "12:30". That way it will be easier to compare them. – Will Ness May 02 '12 at 00:37
  • It looks like [SO 10378143](http://stackoverflow.com/questions/10378143/compile-error-of-incompatible-types-when-assigning-to-type-char2-from-type-in) has the type information that is missing from this question. – Jonathan Leffler May 02 '12 at 02:26

3 Answers3

2

Writing a comparator

In C, the comparator function from qsort() returns a number less than, equal to, or greater than zero according to whether the data structure represented by the first argument should be sorted before, equal to, or after the second parameter.

Sorting am and pm times is painful; even converting from am/pm to 24-hour is not entirely trivial. It is far better to store time values in 24 hour notations (or even as seconds since The Epoch). The presentation layer should deal with presenting the time in am/pm notation; the model and controller layers should usually avoid messing with am/pm. Don't forget:

12:01 am happens 1 hour   before  1:01 am
11:59 am happens 1 minute before 12:00 pm
12:00 pm happens 1 hour   before  1:00 pm

Assuming you are not constrained to events starting on the hour and that you have resolved to use 24 hour times internally, then you can write code such as:

int SchedRecordTimeComparator(void const *v1, void const *v2)
{
    sched_record const *r1 = v1;  /* I don't mind a cast; there are those who do */
    sched_record const *r2 = v2;
    if (r1->start.hour < r2->start.hour)
        return -1;
    else if (r1->start.hour > r2->start.hour)
        return +1;
    else if (r1->start.minute < r2->start.minute)
        return -1;
    else if (r1->start.minute > r2->start.minute)
        return +1;
    else
        return  0;
}

It is fairly obvious how to extend that to manage seconds or other match criteria. Note that this code does not run the risk of integer overflow, whereas subtracting two numbers could in general run into overflow problems.

If you decide to continue with 12 hour clocks, then you will have to have a way to distinguish 6 am from 6 pm. Basically, you'll convert your 12 hour notation into 24 hour notation in the function, then do the comparison on that basis (I'm assuming AM and PM are enumeration constants):

int SchedRecordTimeComparator(void const *v1, void const *v2)
{
    sched_record const *r1 = v1;  /* I don't mind a cast; there are those who do */
    sched_record const *r2 = v2;
    int hhmm1 = ((r1->start.hour % 12) + (r1->start.am_pm == AM ? 0 : 12)) * 60 +
                  r1->start.minute;
    int hhmm2 = ((r2->start.hour % 12) + (r2->start.am_pm == PM ? 0 : 12)) * 60 +
                  r2->start.minute;
    if (hhmm1 < hhmm2)
        return -1;
    else if (hhmm1 > hhmm2)
        return +1;
    else
        return  0;
}

How might this be used?

I am not really sure on how to use it?

Usage is fairly simple. Somewhere in your program, you have an array of sched_records:

sched_record *array = malloc(num_records * sizeof(*array));
...
...fill up array with data...
...
qsort(array, num_records, sizeof(*array), SchedRecordTimeComparator);
...

That's all there is to it. It could be a fixed size array instead:

sched_record array[NUM_RECORDS];

Then, assuming you still have a variable size_t num_records which indicates how many of the records are in use, you use the same qsort() call. Using qsort() is very straight-forward. Using bsearch() is slightly more complex, because you typically have to fake up a record to find:

sched_record *SchedRecordSearch(int hour, int minute, sched_record *array, size_t num_records)
{
    sched_record key = { .start.hour = hour, .start.minute = minute };
    return bsearch(&key, array, num_records, sizeof(*array), SchedRecordTimeComparator);
}

Using C99 and designated initializers makes it easier. You have to ensure that your key record has an appropriate value in each of the fields that will be used by the comparator. Of course, you've already sorted the array with qsort() before you use bsearch() on it — or made sure that the data is in the same sorted order 'as if' you had done a qsort() on it with the same comparator.

It's also worth writing a function to check the sort order of an array — it is straight-forward, and left as an 'exercise for the reader'. You can then use that in assertions, for example.


Don't write your own qsort()

I note that all of us answering the question are assuming that you are using the Standard C Library sort function, yet your question suggests you've written your own. Generally speaking, you'll have to be very good to do better than the system-provided qsort(); I wouldn't bother to write my own unless I could demonstrate that the system function was too slow.

  • Use the system-provided qsort() until you don't need to ask how to write one for yourself.

If you must still write the code, then you need to decide on the interface. You can either mimic the standard interface (but use a different name), or you can write a custom interface tied to one specific type (which must be re-parameterized if you need to sort a different type). The latter is roughly what C++ does with templates.

One of the problems for writing your own generic comparator is swapping the elements when you don't know how big the elements will be in advance. If you can use C99 and VLAs, it isn't too bad, though if the size of an element blows your stack, then you're completely hosed.

Inside the function, you have to be careful to use char * instead of void * because you can't legitimately do pointer arithmetic on void *, notwithstanding that GCC does allow you to do so as a non-standard extension.

You need to make sure you have a clear picture of how the data is laid out, and what your sorting code will be doing to it. You'll take a comparator like the ones described in the various answers, and when you need to do a comparison, you'll do something like:

 int cmp = (*Comparator)(char_base + (i * element_size), char_base + (j * element_size));

You can then do:

 if (cmp < 0)
     act on "element i smaller than element j"
 else if (cmp > 0)
     act on "element i greater than element j"
 else
     act on "elements i and j are equal"

Showing Different Ranges

I'm not sure it would do what I want. I have to look more closely. My sort function I originally posted did sort by time from earliest to latest. I may not have been clear on my question. In the menu portion of my program, I have an option line of sorting by AM, PM or Don't care. A is for classes starting before 12:00 PM, P is for classes starting at noon or later, and D for don't care. If the user chooses A, than list the classes up to 12:00, etc. If yours does that, than how do I make that distinction?

You're confusing two things: sorting the data and presenting the right subset of the data. You sort the data as discussed/shown. This gives you a sorted array. Then you scan through the array to present the entries in the time range you are interested in. That will be a separate function; you might well still use the comparator function, but you'd create a pair of dummy keys for the start and end of the time range (each key would be a bit like the key in the bsearch() example in my answer) and then look for all the records in the sorted array after the start time and before the end time.

I'm about to make some simplifying assumptions. Your start.hour records the time unambiguously as a number of minutes since midnight, and it is an integer.

  1. Sort the array:

    qsort(array, num_records, sizeof(*array), SchedRecordTimeComparator);
    
  2. Generate the correct keys — lo and hi:

    typedef struct sched_range
    {
        sched_record *lo;
        sched_record *hi;
    } sched_range;
    
    sched_record lo, hi;
    if (choice == 'A')
    {
        lo.start.hour = 0;        /* Midnight (am) */
        hi.start.hour = 12 * 60;  /* Midday */
    }
    else if (choice == 'D')
    {
        lo.start.hour = 12 * 60;  /* Midday */
        hi.start.hour = 24 * 60;  /* Midnight (pm) */
    }
    else
    {
        lo.start.hour = 0;        /* Midnight (am) */
        hi.start.hour = 24 * 60;  /* Midnight (pm) */
    }
    
  3. Write the SchedRangeSearch() function:

    sched_range SchedRangeSearch(sched_record const *array, size_t num_records,
                    sched_record *lo, sched_record *hi,
                    int (*comparator)(void const *v1, void const *v2))
    {
         sched_range r = { 0, 0 };
         sched_record const *ptr = array;
         sched_record const *end = array + num_records;
    
         /* Skip records before start time */
         while (ptr < end && (*comparator)(lo, ptr) < 0)
             ptr++;
         if (ptr >= end)
             return r;  /* No matching records */
    
         r.lo = ptr;  /* First record in range */
    
         /* Find first record after finish time - if any */
         while (ptr < end && (*comparator)(ptr, hi) < 0)
             ptr++;
    
         r.hi = ptr;
         return r;
    }
    
  4. Use the search function to find the range required:

    sched_range r = SchedRangeSearch(array, num_records, &lo, &hi, SchedRecordTimeComparator);
    
  5. Show the relevant records:

    if (r.lo != 0)
    {
        assert(r.hi != 0);
        sched_record *ptr;
    
        for (ptr = r.lo; ptr < r.hi; ptr++)
            show_sched_record(ptr);
    }
    else
        show_empty_schedule();
    

Untested code: beware crashes, out of bounds access, etc.

The intention is that the search function provides two pointers, to the start (first valid item in the range) and end of the range, where the end pointer points beyond the last valid item. Hence the for loop to display the valid data goes from start to strictly less than end. (This is the same convention used in C++ with STL iterators. It pays to reuse good ideas.)

Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • it's very unlikely to run into overflow by subtracting two integers representing minutes of the hour i.e. in range 0..59. :) – Will Ness May 02 '12 at 00:52
  • @JonathanLeffler I truely appreciate this! Though I am not really sure on how to use it. – c_sharp May 02 '12 at 01:23
  • @WillNess: Agreed, but the overflow should be considered, even if it is also rejected as a problem. And the comparison technique rather than subtraction extends gracefully to string comparisons too. – Jonathan Leffler May 02 '12 at 01:23
  • @JonathanLeffler I appreciate this. I'm not sure it would do what I want. I have to look more closely. My sort function I originally posted did sort by time from earliest to latest. I may not have been clear on my question. In the mmenu portin of my program, I have an option line of sorting by AM, PM or Don't care. A is < classes to 12:00. P is >= classes at noon and beyond. D for don't care is exactly that. If the user chooses A, than list the classes up to 12:00, etc If yours does that, than how do I make that distinction? – c_sharp May 02 '12 at 01:49
  • @JonathanLeffler Was not confused, just did not express my question correctly. I am aware of the qsort() function and using it right now. I really appreciate your time. – c_sharp May 02 '12 at 02:00
  • @JonathanLeffler Thank You, I am using the qsort() function in C, I am not writing my own. Much appreciated! The sortFunction I have above is called by qsort() in my program via `qsort(data, MAX_RECORD, sizeof(sched_record), sortFunction);` – c_sharp May 02 '12 at 03:14
  • I think you have a typo, "larger" instead of "smaller", at the end of "Don't write your own qsort()" subsection. :) I'd also use quotes to separate out the propositions like *act on "i smaller than j"*, to ease the parsing (English is notoriously ambiguous, esp. when a reader doesn't know what is explained yet). – Will Ness May 03 '12 at 08:27
1

Assuming you have a function greaterThan, you can implement sortFunction as follows:

int sortFunction(const void *p, const void *q) {
    if (greaterThan(p, q)) { // p > q
        return +1;
    } else if (greaterThan(q, p)) { // p < q
        return -1;
    } else { // p == q
        return  0;
    }
}
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
phihag
  • 278,196
  • 72
  • 453
  • 469
1

Just add separate check for AM vs PM and make any AM time be less than PM time:

int sortFunction(const void *p, const void *q) {
  return
    (sched_record *) p)->am_pm < (sched_record *) q)->am_pm ?
      -1 :
    (sched_record *) p)->am_pm > (sched_record *) q)->am_pm ?
       1 :
       ((sched_record *) p)->start.hour -
               ((sched_record *) q)->start.hour;
}

presumably in your sched_record the am_pm field will hold 1 for am and 2 for pm, or something like that.

edit: turns out the OP doesn't have am_pm indicator in their structure and so must probably use 24 hour time representation, apparently with integers for hours and minutes:

int sortFunction(const void *p, const void *q) {
  return
    (sched_record *) p)->start.hour < (sched_record *) q)->start.hour ?
      -1 :
    (sched_record *) p)->start.hour > (sched_record *) q)->start.hour ?
       1 :
       ((sched_record *) p)->start.minutes -
               ((sched_record *) q)->start.minutes;
}
Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • That's important but insufficient because 12:01 am comes an hour before 1:01 am. The 24 hour clock rules; record the times in 24 hour notation and present in am/pm if necessary. – Jonathan Leffler May 02 '12 at 00:13
  • Yes, so in the internal representation it is important to use 0:00 times, not 12:00 times, to avoid this problem. – Will Ness May 02 '12 at 00:16
  • @WillNess there is no am pm in the typedef struct. My menu asks the user either am or pm. All I have is time in the struct. – c_sharp May 02 '12 at 00:37
  • @user1318371 IOW ***you must use 24 hours representation*** probably, with separate integer numbers for hour and minutes. That is not what you asked. :) But still, you just compare - first hours, than minutes. Morning times will be less than evenings, automatically. – Will Ness May 02 '12 at 00:42
  • 1
    @user1318371: Your structure information should be in the question, not in a comment. If you don't have an am/pm indicator, then either your times are ambiguous (is that an 8 am or an 8 pm class) or you have a heuristic such as 'classes run from 8 am to 7 pm' and never overlap. – Jonathan Leffler May 02 '12 at 00:42
  • @user1318371 so have you noticed the update to my answer? You can now use this code directly as a comparator in a call to `qsort()`. Just check out the spelling for `start.hour` and `start.minutes` (you've deleted the struct description here in the comments...). – Will Ness May 02 '12 at 01:31
  • @user1318371 you've got 18 rep now, which give you certain new privileges on SO, like up-voting, did you know that? :) – Will Ness May 03 '12 at 08:07