Search code examples
c#arraysstringbinary-search

Binary searching array to display row containing string field


I have a button which should take a 'surname' string input, search a directory array for the 'record' structure correlating to that surname, and output that record to a listview. The directory consists of many rows of 3 string record structures: surname, forename, extcode.

After days of scouring similar SO questions and other sources, I've pieced together the methods which seem to fit my problem best, but my primary search method is not, for some reason, referencing my secondary method. Can you tell me why you think this is, whether you think it will work if I manage to get the reference working, and if not, any alternative suggestions?

FYI my my if statement is a little unruly as my project requires that the surname be case insensitive and accept partial string matches.

private void SearchSurname()
    {
        Array.Sort(directory, (x, y) => String.Compare(x.surname, y.surname));

        ClearForm();
        int surnameIndex = Array.BinarySearch(directory, txtSurname.Text);

        if (directory[surnameIndex].surname.ToUpper().Substring(0, txtSurname.Text.Length).Contains(txtSurname.Text.ToUpper()))
        {
            ListViewItem record = new ListViewItem();

            // Send each field in current record to single listview item
            record.Text = (Convert.ToString(txtSurname.Text));
            record.SubItems.Add(Convert.ToString(txtForename.Text));
            record.SubItems.Add(Convert.ToString(txtExtCode.Text));

            // Display new record listview item in listview
            lvDirectory.Items.Add(record);
        }
    }

    public int BinarySearch(string[] directory, string searchTerm)
    {
        int first = 0;
        int last = directory.Length - 1;
        int position = -1;
        bool found = false;
        int compCount = 0;
        searchTerm = txtSurname.Text;

        while (found != true && first <= last)
        {
            int middle = (first + last) / 2;

            if (string.Compare(directory[middle], searchTerm, true) == 0)
            {
                found = true;
                position = middle;
                compCount++;
            }
            else if (string.Compare(directory[middle], searchTerm, true) > 0)
            {
                last = middle;
                compCount++;
            }
            else
            {
                first = middle;
                compCount++;
            }
        }
        return position;
    }

Edit: Updated code per Olivier Jacot-Descombes' answer:

private void SearchSurname()
    {
        // Sort directory alphabetically by surname
        Array.Sort(directory, (x, y) => String.Compare(x.surname, y.surname));

        ClearForm();

        // In directory, find line index of search term
        int surnameIndex = BinarySearch(directory, txtSurname.Text);

        ListViewItem record = new ListViewItem();

        // Send each field in current record to single listview item
        record.Text = (Convert.ToString(directory[surnameIndex].surname));
        record.SubItems.Add(Convert.ToString(directory[surnameIndex].forename));
        record.SubItems.Add(Convert.ToString(directory[surnameIndex].extCode));

        // Display new record listview item in listview
        lvDirectory.Items.Add(record);
    }

    private int BinarySearch(record[] directory, string searchTerm)
    {
        int first = 0;
        int last = directory.Length - 1;
        int surnameIndex = -1;
        bool indexFound = false;

        // While index not found and there are still points in the array to check
        while (indexFound != true && first < last)
        {
            int middle = (first + last) / 2;

            // If surname field in middle record of directory array matches the search term
            if (string.Compare(directory[middle].surname, searchTerm, true) == 0)
            {
                // Index found!
                indexFound = true;
                surnameIndex = middle;
                MessageBox.Show("If 1");
            }
            // If surname field in middle record of directory array is higher, alphabetically, than the search term
            else if(string.Compare(directory[middle].surname, searchTerm, true) > 0)
            {
                // The next search will be between the first and the current middle records of the array
                last = middle;
                MessageBox.Show("If 2");
            }
            // If surname field in middle record of directory array is lower, alphabetically, than the search term
            else
            {
                // The next search will be between the current middle and the highest records of the array
                first = middle;
                MessageBox.Show("If 3");
            }
        }
        return surnameIndex;
    }

Solution

  • Your SearchSurname method calls Array.BinarySearch, which is a static method of the Array Class. If you wanted to call your own method, you would have to write:

    int surnameIndex = BinarySearch(directory, txtSurname.Text); // Without "Array."
    

    You are using case-insensitive comparison through the 3rd parameter of String.Compare being true.

    You could use

    if (string.StartsWith(directory[middle], searchTerm,
                          StringComparison.CurrentCultureIgnoreCase))
    

    To search only for beginning of names. But there is a problem if several names match. E.g. You are searching for "smit" and you have "Smith" and "Smithy" in the array. Therefore, you would have to test the entries before and after the found match, and return all matching ones as possible results.