qsort函数 - 尝试使用比较器

时间:2012-05-01 23:32:48

标签: c

我为一个更大的程序做了一个qsort函数。它是按时间排序的。我有一个与我合作的课程表,发现需要将时间与AM和PM进行比较。即如果选择A然后是早上的所有课程,如果P选择了下午的所有课程。我的问题是,有没有办法使用这个排序函数大于或小于比较器?如果是这样,有人可以告诉我如果不是很麻烦吗?

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

3 个答案:

答案 0 :(得分:2)

编写比较器

在C中,来自qsort()的比较器函数返回一个小于,等于或大于零的数字,这取决于第一个参数表示的数据结构是否应该排在之前,等于或之后第二个参数。

上午和下午排序很痛苦;即使从am/pm to 24-hour转换也不是完全无足轻重的。以24小时表示法存储时间值(甚至是自大纪元以来的秒数)要好得多。表示层应该以上午/下午的表示法来处理时间;模型和控制器层通常应该避免混乱am / pm。不要忘记:

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

假设您不受限于从小时开始的事件并且您已决定在内部使用24小时,那么您可以编写如下代码:

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

如何扩展它以管理秒或其他匹配条件是相当明显的。请注意,此代码不会冒整数溢出的风险,而减去两个数字通常会遇到溢出问题。

如果您决定继续使用12小时制,那么您必须有办法区分早上6点和下午6点。基本上,您将12小时符号转换为函数中的24小时符号,然后在此基础上进行比较(我假设AM和PM是枚举常量):

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

如何使用它?

  

我不确定如何使用它?

用法很简单。在程序的某个地方,你有一组sched_record s:

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

这就是它的全部内容。它可以是固定大小的数组:

sched_record array[NUM_RECORDS];

然后,假设您仍然有一个变量size_t num_records,表示正在使用的记录数,您使用相同的qsort()调用。使用qsort()非常简单。使用bsearch()稍微复杂一些,因为您通常需要伪造一条记录来查找:

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

使用C99和指定的初始化程序可以更轻松。您必须确保您的密钥记录在比较器将使用的每个字段中都具有适当的值。当然,在您使用qsort()之前,您已经使用bsearch()对数组进行了排序 - 或确保数据采用相同的排序顺序&#39;好像&#39;你用相同的比较器对它进行了qsort()

还值得编写一个函数来检查数组的排序顺序 - 它是直截了当的,并留给读者作为练习。&#39;然后,您可以在断言中使用它,例如。


不要自己编写qsort()

我注意到我们所有人回答这个问题都假设您使用的是标准C库排序功能,但您的问题表明您已经编写了自己的排序功能。一般来说,你必须做得比系统提供的qsort()更好;除非我能证明系统功能太慢,否则我不会费心去编写自己的。

  • 使用系统提供的qsort(),直到您不需要询问如何为自己写一个。{/ li>

如果您仍然必须编写代码,那么您需要决定接口。您可以模仿标准接口(但使用不同的名称),也可以编写绑定到一种特定类型的自定义接口(如果需要对其他类型进行排序,则必须重新参数化)。后者大致是C ++对模板的作用。

编写自己的通用比较器的一个问题是,当您不知道元素预先有多大时,交换元素。如果你可以使用C99和VGA,那就太糟糕了,不过如果一个元素的大小会炸掉你的堆栈,那么你就完全没有了。

在函数内部,您必须小心使用char *而不是void *,因为您无法在void *上合法地执行指针运算,尽管GCC允许您这样做是非标准的扩展。

您需要确保清楚地了解数据的布局方式,以及排序代码将对其进行的操作。您将采用类似于各种答案中描述的比较器,当您需要进行比较时,您将执行以下操作:

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

然后你可以这样做:

 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"

显示不同的范围

  

我不确定它能做我想做的事。我必须仔细看看。我最初发布的排序功能确实按照从最早到最晚的时间排序。我可能不清楚我的问题。在我的程序的菜单部分,我有一个按AM,PM或Don关心排序的选项。 A适用于中午12:00之前开课,P适用于中午或以后开课,D适合不关心。如果用户选择A,则列出最多12:00的班级,等等。如果您这样做,那么我该如何区分呢?

您会混淆两件事:对数据进行排序并显示正确的数据子集。您按照讨论/显示对数据进行排序。这为您提供了一个排序数组。然后扫描数组以显示您感兴趣的时间范围内的条目。这将是一个单独的函数;您可能仍然使用比较器功能,但是您要为时间范围的开始和结束创建一对虚拟键(每个键有点像我bsearch()示例中的键。 ())然后在开始时间之后和结束时间之前查找已排序数组中的所有记录。

我即将做出一些简化的假设。您的start.hour将时间明确记录为午夜以来的分钟数,并且它是一个整数。

  1. 对数组进行排序:

    qsort(array, num_records, sizeof(*array), SchedRecordTimeComparator);
    
  2. 生成正确的密钥 - lohi

    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. 编写SchedRangeSearch()函数:

    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. 使用搜索功能查找所需范围:

    sched_range r = SchedRangeSearch(array, num_records, &lo, &hi, SchedRecordTimeComparator);
    
  5. 显示相关记录:

    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();
    
  6. 未经测试的代码:当心崩溃,超出界限等等。

    目的是搜索函数提供两个指针,指向开始(范围中的第一个有效项)和范围的结尾,其中结束指针指向超出最后一个有效项。因此,显示有效数据的for循环从开始到严格小于结束。 (这与使用STL迭代器的C ++中使用的约定相同。重用好的想法是值得的。)

答案 1 :(得分:1)

假设您有一个函数greaterThan,您可以按如下方式实现sortFunction

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

答案 2 :(得分:1)

只需为AM与PM添加单独的检查,并使任何AM时间小于PM时间:

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

大概在你的sched_record am_pm字段中,上午为1,下午为2,或类似内容。

编辑 结果OP在其结构中没有am_pm指示符,因此必须使用24小时时间表示,显然使用整数表示小时和分钟:

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