Использование qsort и структур для сортировки списка

Я новичок в C и использую структуры. Я пытаюсь создать программу, которая сортирует список дат. Пользователь сначала вводит количество дат, а затем сами даты, то есть месяц, день и год. Затем, используя qsort, я хочу отсортировать его в хронологическом порядке (сначала по годам, затем по месяцам, затем по дням). Я попытался сначала отсортировать год, но я получаю только результат «0».

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char* month;
    int day;
    int year;
} date;

int sort(const void* a, const void* b)
{

    date* date1 = (date*)a;
    date* date2 = (date*)b;

    if (date2->year != date1->year) {
        int year2 = date2->year;
        int year1 = date2->year;
        if (year1 < 14) {
            year1 = year1 + 100;
        }
        if (year2 < 14) {
            year2 = year2 + 100;
        }
        int yearcompare = year2 - year1;
        return -yearcompare;
    }
}

output(date* ar, int i, int n)
{

    for (i = 0; i < n; i++) {
        //printf("Enter the date (month day year) i n the following format: text number number");
        // printf("%s ", ar[i].month);
        //printf("%d ", ar[i].day);
        printf("%d\n", ar[i].year);
    }
}

int main()
{
    int n;
    int i;
    int MIN_SIZE = 0;
    int MAX_SIZE = 1000;

    while (1) {
        printf("Enter number of dates you want to enter (between 1 and 10000):\n");
        scanf("%d", &n);

        if (n < MIN_SIZE) {

            printf("You have entered a number lower than 0\n");
        }

        if (n > MAX_SIZE) {

            printf("You have entered a number higher than 1000\n");
        }

        else {

            break;
        }
    }

    date* ar = malloc(sizeof(int) * n);
    //ALLOCATE MEMORY

    printf("Enter the date (month day year) in the following format: text, number(between 1 and 31), number(between 00 and 12): \n");
    for (i = 0; i < n; i++) {
        scanf("%s", ar[i].month);
        scanf("%d", &ar[i].day);
        scanf("%d", &ar[i].year);
    }

    qsort(ar, n, sizeof(date), sort);

    output(ar, i, n);
}

person LookingWest    schedule 17.03.2017    source источник
comment
Вы не указали, что происходит в sort(), когда год совпадает. Компилятор должен был выдать предупреждение: не все пути управления возвращают значение. Пока вы этого не сделаете, return 0.   -  person Weather Vane    schedule 18.03.2017
comment
Использование return 0 не решает мою проблему? Вывод по-прежнему 0   -  person LookingWest    schedule 18.03.2017
comment
date* ar = malloc(sizeof(int) * n); не хватает памяти.   -  person aschepler    schedule 18.03.2017
comment
Как выделить больше памяти? Использовать большее целое число, чем n?   -  person LookingWest    schedule 18.03.2017
comment
Предположительно, вы бы выделили sizeof(*ar) * n (или, что то же самое, sizeof(date) * n.   -  person ShadowRanger    schedule 18.03.2017


Ответы (1)


Похоже, вам нужна помощь, чтобы собрать все кусочки головоломки вместе. Во-первых, в typedef из date вы включаете char *month. Это указатель, который будет неинициализирован при выделении ar, что означает, что вам потребуется отдельное выделение для ar[i].month. Вы можете сделать это (в этом случае вы можете эффективно использовать strdup), но почему? Если вы вводите строку месяца, то максимальная длина составляет 10 символов (September + nul-byte). Просто используйте статически объявленные month или 10 или более символов и избегайте динамического выделения month.

Например, вы можете объявить полезные константы для использования в вашем коде либо с отдельными директивами #define, либо вы можете использовать глобальную директиву enum для достижения той же цели, например.

/* constants for max chars, max day, max year, max size */
enum { MAXC = 12, MAX_DAY = 31, MAX_YEAR = 2017, MAX_SIZE = 1000 };

typedef struct {
    char month[MAXC];   /* either make static or allocate separately */
    unsigned day;
    unsigned year;
} date;

Следующая проблема, с которой вы столкнетесь, — это смешивание символьного и числового ввода в scanf, которое не очищает буфер ввода (например, stdin) при каждом вызове. Это означает, что если пользователь вводит что-то отличное от действительного десятичного числа для 'n' (например, если он случайно нажимает 'q' вместо '1'), "q\n" остается во входном буфере, который будет принят в качестве вашего ввода для ar[0].month ниже. Чтобы этого не произошло, вам нужно вручную очистить входной буфер (или вместо этого использовать fgets, а затем sscanf для разбора пользовательского ввода — при использовании scanf для пользовательского ввода есть много ловушек).

Несмотря на это, вы можете довольно легко опустошить stdin. Вы можете сделать это встроенным с помощью int c; while ((c = getchar()) != '\n' && c != EOF) {} или создать короткую функцию, если вы будете вызывать ее неоднократно, чтобы сократить набор текста, например:

/* empty character remaining in stdin */
void empty_stdin ()
{
    int c;
    while ((c = getchar ()) != '\n' && c != EOF) {}
}

Когда вы вводите данные (будь то с помощью семейства функций scanf или с помощью fgets (или любым другим способом), всегда проверяйте пользовательский ввод. Насколько вам известно, кошка может наступать на клавиатуру. Кроме того, всегда проверяйте наличие EOF, которое указывает, что пользователь отменил ввод с помощью Ctrl+d или Ctrl+z (в windows). Например:

     while (1) {     /* obtain valid 'n', compare with using fgets below */

        int rtn; /* varaible to save return of scanf -- always validate */

        printf ("Enter number of dates to be entered (between 1 & 1000): ");
        if ((rtn = scanf ("%d", &n)) != 1) {   /* if conversion failed */
            if (rtn == EOF) {   /* test for user cancelation of input */
                fprintf (stderr, "note: user canceled input, exiting.\n");
                return 0;
            }                   /* otherwise simply an invalid input */
            fprintf (stderr, "error: invalid input.\n");
            goto tryagain;
        }

        if (n < 0) {            /* invalid input < 0 */
            fprintf (stderr, "error: invalid input (n < 0).\n");
            goto tryagain;
        }

        if (n > MAX_SIZE) {     /* invalid input > MAX_SIZE */
            fprintf (stderr, "error: invalid input (n > %d).\n", MAX_SIZE);
            goto tryagain;
        }

        break;      /* if we are here - we have a good value, break */

      tryagain:;    /* label for goto to jump over break */

        empty_stdin ();   /* empty characters that remain in input buffer */
    }

Сравните с использованием fgets и sscanf для чтения/анализа ввода month, day, year. Вы можете сделать что-то простое, например:

    for (i = 0; i < n;) {   /* loop until all elements filled */

        char buf[MAX_DAY + 1] = "", ans[MAXC] = "";

        /* if fgets return is NULL, EOF encountered */
        if (fgets (buf, MAX_DAY + 1, stdin) == NULL) {
            fprintf (stderr, "note: user canceled input, exiting.\n");
            return 0;
        }

        /* parse with sscanf, validate 3 conversion took place */
        if (sscanf (buf, "%11s %u %u", ar[i].month, &ar[i].day, &ar[i].year) != 3)
        {
            fprintf (stderr, "error: invalid input.\n");
            continue;
        }

        i++;    /* only increment if valid sscanf conversion took place */
    }

Нет необходимости передавать int i в качестве параметра функции output, просто объявите его локально, например:

/* output n elements of array of struct date */
void output (date *ar, int n)
{
    int i;

    printf ("\nOutput sorted by year:\n\n");

    for (i = 0; i < n; i++)
        printf ("  %s %d %d\n", ar[i].month, ar[i].day, ar[i].year);
}

Далее, хотя ваша функция sort может работать, вы можете сжать сортировку по годам, избегая потенциального переполнения, используя вместо этого неравенства:

/* sort struct date on year */
int sort (const void *a, const void *b)
{
    date *date1 = (date *) a;
    date *date2 = (date *) b;

    if (date2->year != date1->year)
        return (date1->year > date2->year) - (date1->year < date2->year);

    return 0;
}

Наконец, если вы выделяете память, вы несете ответственность за сохранение указателя на начало блока, а затем на free память, когда она больше не нужна. Пока она будет освобождена exit, заведите привычку отслеживать и освобождать всю выделенную память. Хорошие привычки сослужат вам хорошую службу, когда вы будете работать над более сложными проектами.

Собрав все вместе и добавив запрос на выход, если пользователь просто нажимает Enter вместо ввода даты, вы можете сделать что-то вроде следующего:

#include <stdio.h>
#include <stdlib.h>

/* constants for max chars, max day, max year, max size */
enum { MAXC = 12, MAX_DAY = 31, MAX_YEAR = 2017, MAX_SIZE = 1000 };

typedef struct {
    char month[MAXC];   /* either make static or allocate separately */
    unsigned day;
    unsigned year;
} date;

/* empty character remaining in stdin */
void empty_stdin ()
{
    int c;
    while ((c = getchar ()) != '\n' && c != EOF) {}
}

/* sort struct date on year */
int sort (const void *a, const void *b)
{
    date *date1 = (date *) a;
    date *date2 = (date *) b;

    if (date2->year != date1->year)
        return (date1->year > date2->year) - (date1->year < date2->year);

    return 0;
}

/* output n elements of array of struct date */
void output (date *ar, int n)
{
    int i;

    printf ("\nOutput sorted by year:\n\n");

    for (i = 0; i < n; i++)
        printf ("  %s %d %d\n", ar[i].month, ar[i].day, ar[i].year);
}

int main (void) {

    int i, n;
    date *ar = NULL;

    while (1) {     /* obtain valid 'n', compare with using fgets below */

        int rtn; /* varaible to save return of scanf -- always validate */

        printf ("Enter number of dates to be entered (between 1 & 1000): ");
        if ((rtn = scanf ("%d", &n)) != 1) {   /* if conversion failed */
            if (rtn == EOF) {   /* test for user cancelation of input */
                fprintf (stderr, "note: user canceled input, exiting.\n");
                return 0;
            }                   /* otherwise simply an invalid input */
            fprintf (stderr, "error: invalid input.\n");
            goto tryagain;
        }

        if (n < 0) {            /* invalid input < 0 */
            fprintf (stderr, "error: invalid input (n < 0).\n");
            goto tryagain;
        }

        if (n > MAX_SIZE) {     /* invalid input > MAX_SIZE */
            fprintf (stderr, "error: invalid input (n > %d).\n", MAX_SIZE);
            goto tryagain;
        }

        break;      /* if we are here - we have a good value, break */

      tryagain:;    /* label for goto to jump over break */

        empty_stdin ();   /* empty characters that remain in input buffer */
    }

    empty_stdin ();     /* empty characters that remain in input buffer */

    /* allocate array of struct ar, n elements */
    if ((ar = malloc (sizeof *ar * n)) == NULL) {
        fprintf (stderr, "error: virtual memory exhausted.\n");
        return 1;
    }

    /* provide format instructions */
    printf ("Enter the date (month day year)\n"
            "  format, e.g.:  Jan 18 2017\n\n");

    for (i = 0; i < n;) {   /* loop until all elements filled */

        char buf[MAX_DAY + 1] = "", ans[MAXC] = "";

        printf (" date[%2d] : ", i + 1);    /* prompt for input */

        /* if fgets return is NULL, EOF encountered */
        if (fgets (buf, MAX_DAY + 1, stdin) == NULL) {
            fprintf (stderr, "note: user canceled input, exiting.\n");
            return 0;
        }

        if (*buf == '\n') { /* if first char is '\n', user just hit enter */
            printf ("no input provided, quit (y/n)? ");
            if (fgets (ans, MAXC, stdin) && (*ans == 'y' || *ans == 'Y'))
                return 0;
            else if (!*ans) {   /* if ans NULL, EOF encountered */
                fprintf (stderr, "note: user canceled input, exiting.\n");
                return 0;
            }
        }

        /* parse with sscanf, validate 3 conversion took place */
        if (sscanf (buf, "%11s %u %u", ar[i].month, &ar[i].day, &ar[i].year) != 3)
        {
            fprintf (stderr, "error: invalid input.\n");
            continue;
        }

        i++;    /* only increment if valid sscanf conversion took place */
    }

    qsort (ar, n, sizeof (date), sort);     /* sort by year */

    output (ar, n);     /* output results */

    free (ar);      /* free ar - you allocate it, you free it */

    return 0;
}

Примечание: существует множество способов приблизиться практически к каждой части кода. Если вы посмотрите, где находится большая часть строк, они используются для проверки ввода. Это всего лишь минимум проверки. В идеале вы должны сравнить значения для каждого day и year с максимальными/минимальными значениями, и вы должны сравнить каждый month с таблицей поиска (или хэш-таблицей), чтобы подтвердить, что каждый месяц является допустимым месяцем (вы также можете использовать функции даты/времени, но осталось для другого вопроса)

Пример использования/вывода

$ ./bin/qsortstruct
Enter number of dates to be entered (between 1 & 1000): 4
Enter the date (month day year)
  format, e.g.:  Jan 18 2017

 date[ 1] : September 11 2001
 date[ 2] : April 22 2010
 date[ 3] : June 2 1968
 date[ 4] : February 13 1979

Output sorted by year:

  June 2 1968
  February 13 1979
  September 11 2001
  April 22 2010

Просмотрите все, убедитесь, что вы понимаете каждую часть того, что происходит, и спросите, есть ли у вас дополнительные вопросы.

person David C. Rankin    schedule 18.03.2017