简体   繁体   中英

How to use bsearch correctly in c?

I want to search 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 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");
}
}

key_date is a pointer but you pass its address to bsearch so when cmpDates is called, it will try to cast(reinterpret) a void* which is a date** to a date* and then dereference it to a date . Obviously, the values retrieved from that date struct will be all wrong. I am surprised you didn't have a crash with the month string.

q_result = (date*) bsearch(&key_date, date_list, n, sizeof(date), cmpDates);

should be

q_result = (date*) bsearch(key_date, date_list, n, sizeof(date), cmpDates);

Obviously, you still need to sort out(no pun intended) the calloc issue

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

should be(if you really need to keep month as a string)

for (i=0; i<size; i++)
{
    array[i].month = calloc(MAX_MONTH_STR,sizeof(char));
    array[i].day = 0;
    array[i].year = 0;
}

and then

for (i=0; i<size; i++)
{
    free(array[i].month);
    free(&array[i].day);
    free(&array[i].year);
}

should be

for (i=0; i<size; i++)
{
    free(array[i].month);
}

Regarding your month comparison, you need a function like this:

months GetMonth(char* m)
{
    if (strcmp(m, "January") == 0)
        return months.January;
    else if (strcmp(m, "February") == 0)
        return months.February;
    ....
}

I have left out case sensitiveness.

Then you store this enum in your date struct and the month comparison will at least make sense.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM