carraysstructbsearch

How to use bsearch correctly in c?


I want to search for a date (which is a struct) in an array of dates to see if it is in it. This is the first time I am using bsearch and it always returns the same result, 0, whereas it should either return null or a pointer to the date found. I am using the same comparing function I used to sort the array and the sorting works fine. I'm guessing if the function returns `0 it means it has found the date in the array. What have I done wrong? If the mistake is not obvious I can post the full code.

#define MIN_SIZE 0
#define MAX_SIZE 10000
#define MAX_MONTH_STR 9
#define SWAP 1
#define NO_SWAP -1
#define EQUAL 0

//Define a struct data type
typedef struct
{
    //Declaration of struct members
    char* month;
    int day;
    int year;
}date;

//Method to allocate memory for a list of date structures
date* allocateStruct(int size)
{
    //Declaration of variables
    date *array;
    int i;

    //Allocate memory for array to store 'size' many 'date' struct data types
    array = malloc(size*sizeof(date));

    //For-loop to allocate memory for each struct's members and initialize them to zero
    for (i=0; i<size; i++)
    {
        array[i].month = calloc(MAX_MONTH_STR,sizeof(char));
        array[i].day = (int) calloc(1,sizeof(int));
        array[i].year = (int) calloc(1,sizeof(int));
    }

    return array;
}

//Method to free memory allocated
void freeStruct(date* array, int size)
{
    //Declaration of variable
    int i;

    //For-loop to free up struct members
    for (i=0; i<size; i++)
    {
        free(array[i].month);
        free(&array[i].day);
        free(&array[i].year);
    }

    //Free up structs
    free(array);
}

//Method to compare two dates
int cmpDates (const void *a, const void *b)
{
    //Declaration and dereference of variables
    date first = *(date*)a;
    date second = *(date*)b;
    int y_result, m_result, d_result;

    //Calculate results
    y_result = second.year-first.year;
    m_result = second.month-first.month;
    d_result = second.day-first.day;

    //If-statements to determine whether to swap dates based on year
    //If-statement to determine whether both years are in 90s group
    if (first.year>=90 && first.year<=99 && second.year>=90 && second.year<=99)
    {
        //If-statement to determine whether years are equal
        if (y_result!=0)
        {
            return (y_result);
        }
    }
    //Else-if-statement to determine whether both years are in 00-12 group
    else if (first.year>=0 && first.year<=12 && second.year>=0 && second.year<=12)
    {
        //If-statement to determine whether years are equal
        if (y_result!=0)
        {
            return (y_result);
        }
    }
    //Otherwise the two years belong to different year groups
    else
    {
        //If-statement to determine whether first year belongs to 00-12 group
        if (first.year>=0 && first.year<=12)
        {
            return NO_SWAP;
        }
        else
        {
            return SWAP;
        }
    }

    //If-statement to determine whether to swap dates based on months
    if (m_result!=0)
    {
        return m_result;
    }

    //If-statement to determine whether to swap dates based on days
    if (d_result!=0)
    {
    return d_result;
    }

    //If dates are exactly the same
    return EQUAL;
}

enum months { 
    January=1, 
    February, 
    March, 
    April, 
    May, 
    June, 
    July, 
    August, 
    September, 
    October, 
    November, 
    December 
};
int main()
{
    //Declaration of variables
    int n; //number of dates in array
    date* date_list; //array of dates
    date *key_date; //date to search for
    date *q_result; //result of search

    //Read input
    do
    {
        //printf("Enter number of dates you want to enter (between 1 and 10000):\n");
        scanf("%d", &n);
    }while(n<MIN_SIZE || n>MAX_SIZE);

    //Allocate memory for an array of n dates
    date_list = allocateStruct(n);

    //For-loop to store values in 'date_list'
    for (i=0; i<n; i++)
    {
        //printf("Enter the date (month day year) in the following format <text number number>:");
        scanf("%s", date_list[i].month);
        scanf("%d", &date_list[i].day);
        scanf("%d", &date_list[i].year);
    }

    //Allocate memory for one date
    key_date = allocateStruct(1);

    //Read date for query
    //printf("Enter date you want to query:");
    scanf("%s", key_date->month);
    scanf("%d", &key_date->day);
    scanf("%d", &key_date->year);

    //Sort the array with built-in function qsort
    qsort(date_list, n, sizeof(date), cmpDates);

    //Print list of sorted dates
    for (i=0; i<n; i++)
    {
        //printf("Enter the date (month day year) in the following format: text number number");
        printf("%s ", date_list[i].month);
        printf("%d ", date_list[i].day);
        printf("%02d\n", date_list[i].year); //need & ?
    }

    //Query with bsearch --> I TRIED BOTH OF THESE LINES BUT THE RESULT WAS THE SAME
    q_result = (date*) bsearch(&key_date, date_list, n, sizeof(date), cmpDates);
    // q_result = bsearch(&key_date, date_list, n, sizeof(date), cmpDates);

    //Printing answer to query
    if(q_result!=NULL)
    {
        printf("Yes in list");
    }
    else
    {
        printf("No not in list");
    }
}

Solution

  • Here is code I assembled based on yours that seems to work. I've not stress tested it.

    #include <stdlib.h>
    #include <string.h>
    #include <stdio.h>
    
    #define MIN_SIZE 1
    #define MAX_SIZE 10000
    #define MAX_MONTH_STR 10
    
    #define EQUAL 0
    #define LESS_THAN -1
    #define MORE_THAN +1
    
    typedef struct
    {
        char *month;
        int day;
        int year;
    } date;
    
    static
    date *allocateStruct(int size)
    {
        date *array = malloc(size * sizeof(date));
        if (array == 0)
            exit(1);
    
        for (int i = 0; i < size; i++)
        {
            array[i].month = calloc(MAX_MONTH_STR, sizeof(char));
            if (array[i].month == 0)
                exit(1);
        }
    
        return array;
    }
    
    static
    void freeStruct(date *array, int size)
    {
        for (int i = 0; i < size; i++)
        {
            free(array[i].month);
        }
        free(array);
    }
    
    static inline int normalize_year(int year)
    {
        return (year < 50) ? year + 2000 : year + 1900;
    }
    
    static inline int month_number(const char *name)
    {
        static const char *months[] =
        {
            "",
            "January", "February", "March", "April", "May", "June",
            "July", "August", "September", "October", "November", "December",
        };
        for (int i = 1; i <= 12; i++)
            if (strcmp(name, months[i]) == 0)
                return i;
        fprintf(stderr, "Invalid (unrecognized) month name <<%s>>\n", name);
        exit(1);
        /*NOTREACHED*/
    }
    
    static
    int cmpDates(const void *a, const void *b)
    {
        date first = *(date *)a;
        date second = *(date *)b;
    
        int y1 = normalize_year(first.year);
        int y2 = normalize_year(second.year);
        if (y1 < y2)
            return LESS_THAN;
        else if (y1 > y2)
            return MORE_THAN;
    
        int m1 = month_number(first.month);
        int m2 = month_number(second.month);
        if (m1 < m2)
            return LESS_THAN;
        else if (m1 > m2)
            return MORE_THAN;
    
        if (first.day < second.day)
            return LESS_THAN;
        else if (first.day > second.day)
            return MORE_THAN;
        else
            return EQUAL;
    }
    
    int main(void)
    {
        int n;
        date *date_list;
        date *key_date;
        date *q_result;
    
        do
        {
            if (scanf("%d", &n) != 1)
                exit(1);
        } while (n < MIN_SIZE || n > MAX_SIZE);
    
        date_list = allocateStruct(n);
    
        printf("Input data:\n");
        for (int i = 0; i < n; i++)
        {
            scanf("%s", date_list[i].month);
            scanf("%d", &date_list[i].day);
            scanf("%d", &date_list[i].year);
            printf("%.2d: %.2d %s %.2d\n", i, date_list[i].day, date_list[i].month, date_list[i].year);
        }
    
        qsort(date_list, n, sizeof(date), cmpDates);
    
        printf("Sorted:\n");
        for (int i = 0; i < n; i++)
            printf("%.2d: %.2d %s %.2d\n", i, date_list[i].day, date_list[i].month, date_list[i].year);
    
        key_date = allocateStruct(1);
    
        scanf("%s", key_date->month);
        scanf("%d", &key_date->day);
        scanf("%d", &key_date->year);
        printf("Search: %.2d %s %.2d\n", key_date->day, key_date->month, key_date->year);
    
        q_result = (date *) bsearch(key_date, date_list, n, sizeof(date), cmpDates);
    
        if (q_result != NULL)
        {
            printf("Yes (%.2d %s %.2d) in list (%.2d %s %.2d)\n",
                    key_date->day, key_date->month, key_date->year,
                    q_result->day, q_result->month, q_result->year);
        }
        else
        {
            printf("No (%.2d %s %.2d) in list\n",
                    key_date->day, key_date->month, key_date->year);
        }
        freeStruct(date_list, n);
        freeStruct(key_date, 1);
        return 0;
    }
    

    Sample data:

    16
    November 26 00
    February 18 12
    January 19 08
    March 22 11
    October 08 05
    December 22 10
    November 08 01
    May 21 10
    July 10 92
    October 06 91
    November 30 93
    April 25 90
    March 21 90
    September 18 97
    June 23 97
    July 19 98
    November 29 93
    

    The last line of the file is the date to search for. The November 29 93 is not in the list, but change 29 to 30 and the date is in the list. In both cases, the code reports this correctly.

    Sample output:

    Input data:
    00: 26 November 00
    01: 18 February 12
    02: 19 January 08
    03: 22 March 11
    04: 08 October 05
    05: 22 December 10
    06: 08 November 01
    07: 21 May 10
    08: 10 July 92
    09: 06 October 91
    10: 30 November 93
    11: 25 April 90
    12: 21 March 90
    13: 18 September 97
    14: 23 June 97
    15: 19 July 98
    Sorted:
    00: 21 March 90
    01: 25 April 90
    02: 06 October 91
    03: 10 July 92
    04: 30 November 93
    05: 23 June 97
    06: 18 September 97
    07: 19 July 98
    08: 26 November 00
    09: 08 November 01
    10: 08 October 05
    11: 19 January 08
    12: 21 May 10
    13: 22 December 10
    14: 22 March 11
    15: 18 February 12
    Search: 29 November 93
    No (29 November 93) in list
    

    I think it is a lousy idea to store the month name in the date structure. If you are going to store a pointer, you could store a pointer to one of a fixed array of strings representing the month names. You'd read the input name into an array, then find the canonical pointer from a list of the month names. You'd store the names so that the address of January is before the address for February, etc, so that you could compare pointers and come up with the correct answer. However, I've not implemented that. In fact, I think storing month names in the structure is a bad idea; I'd store a month number. I also think storing just two digits of the year is a lousy decision. Maybe you weren't aware of the Y2K bug fixing work that was necessary because people tried to cut corners and only stored two digits for years instead of four digits, but it was messy. It is far, far simpler just to store the year number. You aren't even saving any space since you're using 4 bytes (an int is usually 4 bytes these days) to store the 2-digit year. If you have year as a 4-digit number, and month and day as 2-digit numbers, then yyyy * 10000 + mm * 100 + dd gives you an 8-digit integer that can be sorted trivially. If you need to compute the number of days between two dates, then the broken-down format isn't as convenient as a count of the days since a reference date (such as 1900-01-01 was day 1). You can take the difference between two such date values to get the number of days between them.