关于c:qsort函数-尝试使用比较器


qsort function - trying to use a comparator

我为一个更大的程序做了一个qsort函数。它是按时间排序的。我有一个和我一起工作的课程表,我发现有必要将时间与上午和下午进行比较。如果选择A,那么上午的所有课程,如果选择P,那么下午的所有课程。我的问题是,有没有一种方法可以用一个大于或小于比较器来使用这个排序函数?如果是的话,有人能告诉我如果不是太麻烦怎么办?

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


编写比较器

在C语言中,qsort()中的comparator函数根据第一个参数表示的数据结构是在第二个参数之前、之前还是之后排序,返回一个小于、等于或大于零的数字。好的。

对AM和PM时间进行排序是很痛苦的;即使从AM/PM转换为24小时也不完全是微不足道的。最好用24小时的符号来存储时间值(甚至是从新纪元开始的秒)。表示层应该处理以AM/PM表示法表示的时间;模型层和控制器层通常应该避免与AM/PM混淆。别忘了:好的。

1
2
3
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小时时间,那么您可以编写如下代码:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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是枚举常量):好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

这个怎么用?

I am not really sure on how to use it?

Ok.

使用相当简单。在程序的某个地方,您有一个EDOCX1[1]数组:好的。

1
2
3
4
5
6
sched_record *array = malloc(num_records * sizeof(*array));
...
...fill up array with data...
...
qsort(array, num_records, sizeof(*array), SchedRecordTimeComparator);
...

就这些了。它可以是一个固定大小的数组:好的。

1
sched_record array[NUM_RECORDS];

然后,假设您仍然有一个变量size_t num_records,它指示使用了多少条记录,则使用相同的qsort()调用。使用qsort()非常直接。使用bsearch()稍微复杂一点,因为您通常需要伪造记录来查找:好的。

1
2
3
4
5
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和指定的初始值设定项更容易。您必须确保您的键记录在比较器将要使用的每个字段中都有适当的值。当然,在对数组使用bsearch()之前,您已经使用qsort()对数组进行了排序,或者确保数据的排序顺序与使用相同比较器对数组执行qsort()时的排序顺序相同。好的。

还值得编写一个函数来检查数组的排序顺序,它是直接向前的,并作为"读者练习"保留。例如,您可以在断言中使用它。好的。不要写自己的qsort()

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

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

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

编写自己的通用比较器的一个问题是,当您不知道元素提前会有多大时,交换元素。如果你可以使用c99和vlas,那也不算太糟糕,但是如果一个元素的大小破坏了你的堆栈,那么你就完全被主机保护了。好的。

在函数内部,您必须小心使用char *而不是void *,因为您不能合法地在void *上执行指针算术,尽管gcc允许您以非标准扩展方式执行。好的。

您需要确保清楚地了解数据的布局,以及排序代码将如何处理这些数据。您将使用类似于各种答案中描述的比较器,当您需要进行比较时,您将执行如下操作:好的。

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

然后你可以做:好的。

1
2
3
4
5
6
 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"

显示不同的范围

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?

Ok.

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

我要做一些简单的假设。您的start.hour明确地将时间记录为从午夜开始的分钟数,它是一个整数。好的。

  • 排序数组:好的。

    1
    qsort(array, num_records, sizeof(*array), SchedRecordTimeComparator);
  • 生成正确的密钥&mdash;lohi:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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) */
    }
  • 编写SchedRangeSearch()函数:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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;
    }
  • 使用搜索功能查找所需的范围:好的。

    1
    sched_range r = SchedRangeSearch(array, num_records, &lo, &hi, SchedRecordTimeComparator);
  • 显示相关记录:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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();
  • 未测试的代码:当心崩溃、越界访问等。好的。

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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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点,或者类似的情况。

    编辑:结果是操作在其结构中没有AM-PM指示器,因此必须使用24小时时间表示法,显然使用整数表示小时和分钟:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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;
    }


    假设你有一个函数greaterThan,你可以实现sortFunction如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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;
        }
    }