0

I am having trouble with this code I wrote for a generic binary search. when trying to execute the search on an array of strings I noticed that the array of strings, passed to binSearch function does not contain the strings.

can someone suggest a hint?

Much appreciation

#define SIZE 100
typedef unsigned char BYTE

please consider this main:

void main()
{


char ** stringArr, stringToFind[SIZE];
int stringSize;
int res;

    stringArr = getStringArr(&stringSize);

    // string to find
    gets(stringToFind);


    res = stringBinSearch(stringArr, stringSize, stringToFind);

    if (res == 1)
        printf("The string %s was found\n", stringToFind);
    else
        printf("The string %s was not found\n", stringToFind);
}

char** getStringArr(int* stringSize)
{
    int i, size, len;
    char** arr;
    char temp[SIZE];


    scanf("%d", &size);
    getchar();

    arr = (char**)malloc(size * sizeof(char*));
    checkAllocation(arr);


    for (i = 0; i < size; i++)
    {
        gets(temp);
        len = strlen(temp);
        temp[len] = '\0';
        arr[i] = (char*)malloc((len+1) * sizeof(char));
        checkAllocation(arr[i]);
        strcpy(arr[i], temp);
    }

    *stringSize = size;
    return arr;
}

int stringBinSearch(char** stringArr, int stringSize, char* stringToFind)
{
    return binSearch(stringArr, stringSize, sizeof(char*), stringToFind,compare2Strings);
}

int binSearch(void* Arr, int size, int ElemSize, void* Item, int(*compare)(void*, void*))
{
    int left = 0, right = size - 1, place;
    BOOL found = FALSE;

    while (found == FALSE && left <= right)
    {
        place = (left + right) / 2;

        if (compare(Item, (BYTE*)Arr + place*ElemSize) == 0)
            found = TRUE;

        else if (compare(Item, (BYTE*)Arr + place*ElemSize) < 0)
            right = place - 1;

        else
            left = place + 1;
    }
    return found;
}

int compare2Strings(void* str1, void* str2)
{
    char* elemA, *elemB;

    elemA = (char*)str1;
    elemB = (char*)str2;

    return strcmp(elemA, elemB);
}
Tom.A
  • 436
  • 4
  • 9
  • `len+1 * sizeof(char)` doesn't do what you probably want. It's better to just use `len + 1` anyway (since `sizeof (char)` is `1`). – unwind Jun 11 '17 at 13:29
  • it stands for allocating each cell of the array of strings, as long as the string input following '\0'. – Tom.A Jun 11 '17 at 13:32
  • 1
    No it does not, since `*` binds tighter than `+`. Also, as I said, it's pointless since `sizeof (char)` is always 1. – unwind Jun 11 '17 at 13:33
  • "the array of strings, passed to binSearch function does not contain the strings" - Does this mean that the problem occurs before you even call binSearch? How do you see the problem exactly? – interjay Jun 11 '17 at 13:33
  • As posted, the code would not compile, since the first variable used in `main()` is not declared. That means anyone who attempts to help will be forced to guess what your actual code is. Try posting an [mcve]. – Peter Jun 11 '17 at 13:48
  • I think it is time to introduce your self to Mr Debugger – Ed Heal Jun 11 '17 at 13:52
  • 2
    `elemA = (char*)str1;` --> `elemA = *(char**)str1;` – BLUEPIXY Jun 11 '17 at 14:14
  • elemA already stores char* and is not modifiable however, for elemB what you wrote indeed solved the problem!!, can you please explain? – Tom.A Jun 11 '17 at 14:32
  • `return binSearch(stringArr, stringSize, sizeof(char*), stringToFind,compare2Strings);` --> `return binSearch(stringArr, stringSize, sizeof(char*), &stringToFind,compare2Strings);` – BLUEPIXY Jun 11 '17 at 14:44
  • 1
    Because `stringArr` is an Array of `char*`. – BLUEPIXY Jun 11 '17 at 14:45

3 Answers3

0

Hello your problem is the way you send the array of strings to the binary search function. Because you need to pass an array of strings to it your Arr parameter must be void** not void*

int binSearch(void** Arr, int size, int ElemSize, void* Item, int(*compare)(void*, void*))

And in your function whenever you want to acces a string from your array it will be enough to acces it like: (char*) *(Arr+place*ElemSize)

  • thanks for your reply, but what if binSearch should operate for other types as well, that being said, I wrote compare function that works well with integers. if I change binSearch declaration it will no longer work for any data type, or? – Tom.A Jun 11 '17 at 14:26
  • 2
    No; the problem is not needing `void **`. – Jonathan Leffler Jun 11 '17 at 14:52
0

When you sort an array of int, the values passed are pointer to int, spelled int *. When you sort an array of strings (spelled char *), the values passed are pointer to string, spelled char **. You comparator is no use for comparing strings. As the inimitable BLUEPIXY said in their incredibly terse style — you need to modify the code to treat the passed void * arguments as char ** and not as char *.

With generic sorting, that's usually the end of the issue. With binary search, there's another issue that you run foul of. That is that the type of the item being searched for needs to be the same as the one of the entries in the array, so you need to pass a pointer to the item, not just the item.

So, adding material to allow the code to compile with minimal changes, changing from gets() to a cover for fgets() (because gets() is too dangerous to be used — ever! and programs that use it produce a warning when its used on macOS Sierra 10.12.5 — warning: this program uses gets(), which is unsafe.), and printing out the input data so you can see what's what, I end up with:

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

#define BOOL int
#define TRUE 1
#define FALSE 0

static inline char *sgets(size_t buflen, char *buffer)
{
    char *result = fgets(buffer, buflen, stdin);
    if (result)
        buffer[strcspn(buffer, "\n")] = '\0';
    return result;
}

#define checkAllocation(x) assert((x) != 0)

#define SIZE 100
typedef unsigned char BYTE;

char **getStringArr(int *stringSize);
int stringBinSearch(char **stringArr, int stringSize, char *stringToFind);
int binSearch(void *Arr, int size, int ElemSize, void *Item, int (*compare)(void *, void *));
int compare2Strings(void *str1, void *str2);

int main(void)
{
    char **stringArr, stringToFind[SIZE];
    int stringSize;
    int res;

    stringArr = getStringArr(&stringSize);

    sgets(sizeof(stringToFind), stringToFind);

    printf("Strings: %d\n", stringSize);
    for (int i = 0; i < stringSize; i++)
        printf("[%d] = [%s]\n", i, stringArr[i]);
    printf("Search: [%s]\n", stringToFind);

    res = stringBinSearch(stringArr, stringSize, stringToFind);

    if (res == 1)
        printf("The string %s was found\n", stringToFind);
    else
        printf("The string %s was not found\n", stringToFind);
    return 0;
}

char **getStringArr(int *stringSize)
{
    int i, size, len;
    char **arr;
    char temp[SIZE];

    scanf("%d", &size);
    getchar();

    arr = (char **)malloc(size * sizeof(char *));
    checkAllocation(arr);

    for (i = 0; i < size; i++)
    {
        sgets(sizeof(temp), temp);
        len = strlen(temp);
        temp[len] = '\0';
        arr[i] = (char *)malloc((len + 1) * sizeof(char));
        checkAllocation(arr[i]);
        strcpy(arr[i], temp);
    }

    *stringSize = size;
    return arr;
}

int stringBinSearch(char **stringArr, int stringSize, char *stringToFind)
{
    return binSearch(stringArr, stringSize, sizeof(char *), &stringToFind, compare2Strings);
}

int binSearch(void *Arr, int size, int ElemSize, void *Item, int (*compare)(void *, void *))
{
    int left = 0, right = size - 1, place;
    BOOL found = FALSE;

    while (found == FALSE && left <= right)
    {
        place = (left + right) / 2;

        if (compare(Item, (BYTE *)Arr + place * ElemSize) == 0)
            found = TRUE;

        else if (compare(Item, (BYTE *)Arr + place * ElemSize) < 0)
            right = place - 1;

        else
            left = place + 1;
    }
    return found;
}

int compare2Strings(void *str1, void *str2)
{
    char *elemA = *(char **)str1;
    char *elemB = *(char **)str2;

    return strcmp(elemA, elemB);
}

The key changes are:

  • compare2Strings() — compare the data in char ** values.
  • stringBinSearch() — pass the address of stringToFind.

AFAICR, any other change is cosmetic or 'infrastructure'.

Note that the return type of main() should be int — you can get away with void only on Windows where it is allowed.


Example run 1:

Data:

5
Antikythera
albatross
armadillo
pusillanimous
pygmalion
pygmalion

Output:

Strings: 5
[0] = [Antikythera]
[1] = [albatross]
[2] = [armadillo]
[3] = [pusillanimous]
[4] = [pygmalion]
Search: [pygmalion]
The string pygmalion was found

Example run 2:

Data file:

5
armadillo
pygmalion
Antikythera
pusillanimous
albatross
pygmalion

Output:

Strings: 5
[0] = [armadillo]
[1] = [pygmalion]
[2] = [Antikythera]
[3] = [pusillanimous]
[4] = [albatross]
Search: [pygmalion]
The string pygmalion was not found

The difference between the two sets of data is that in the first case, the strings are in correct sorted order — a prerequisite condition for successful (reliable) binary search — and in the second, the data is not in correct sorted order. (That said, I had one non-sorted order that still found 'pygmalion' — I used a different shuffle for the shown results. But the 'reliable' comment applies.)

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
-1

Your approach which is to write a generic binary search is right. However attempting to return early slows down a binary search. It also means you can't use the C++ convention that "less than" is the comparison operator defined. Wait until left and right equal each other, and return that.

Malcolm McLean
  • 6,258
  • 1
  • 17
  • 18