Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I have a long list of sorted strings in a Workbook, and I have to find a lot of values in it. I was hoping to build a binary search Class Module that will find them for me faster than Excel's built-in search formulas/methods.

Can anyone think of a faster way, or maybe more bug proof?

Values() As String
Value2 as String
Upper As Integer
Lower As Integer
Middle As Integer
Private Function Move_Array() As Integer
    Middle = Round((Upper - Lower) / 2, 0)
    Move_Array = Compare(Values(Middle), Value2)
End Function
Function Search(Cells As Range, Value As String) As Integer
    If Cells.Count < 1 Then
        Values() = Cells.value
    End If
Value2 = Value
Again:
    Select Case Move_Array()
        Case -1
            Lower = Middle
        Case 1
            Upper = Middle
        Case 0
            Search = Middle
            Exit Function
    End Select
    GoTo Again
End Function

This is a Sub that would call this class. However, this is subject to change.

Sub Start_BINSearch()
    Dim Search_operation As BINSearch
    Dim Cells As Range
    Dim Value As String

    Set Search_operation = BINSearch
    Set Cells = Application.InputBox( _
        Prompt:="Enter Range to Search", _
        Type:=8)
    Value = Application.InputBox( _
        Prompt:="Enter value that you waant to look for")
    Position = Search_operation.Search(Cells, Value)
End Sub
share|improve this question

closed as off-topic by RubberDuck, Phrancis, radarbob, EngieOP, Mat's Mug Oct 31 at 1:15

This question appears to be off-topic. The users who voted to close gave this specific reason:

  • "Questions containing broken code or asking for advice about code not yet written are off-topic, as the code is not ready for review. After the question has been edited to contain working code, we will consider reopening it." – RubberDuck, Phrancis, radarbob, EngieOP, Mat's Mug
If this question can be reworded to fit the rules in the help center, please edit the question.

1  
Welcome to Code Review! How much faster is it? –  Mat's Mug Oct 30 at 19:34
1  
It doesn't compile. Values() As String throws a compiler error. "Statement invalid outside Type block." –  RubberDuck Oct 30 at 20:01

1 Answer 1

Your code is quite difficult to manipulate into doing what you are trying to do.

Some specifics:

  • Don't use "goto" whenever possible

The following needs to be fixed:

   Dim Search_operation As BINSearch
   'Set Search_operation = BINSearch
   Set Search_operation = New BINSearch

When working with objects, you need to use New when using Set.

This is currently not handling any errors or anything for your input ranges.

Set Cells = Application.InputBox( _
    Prompt:="Enter Range to Search", _
    Type:=8)
Value = Application.InputBox( _
    Prompt:="Enter value that you waant to look for")
Position = Search_operation.Search(Cells, Value)

When you want to use global variables (which, while there are cases to use them, this is not one of them) you need to dimension them still. Plus, the way you had them before won't compile anyways...

Public Value2 as String
Public Upper As Integer
Public Lower As Integer
Public Middle As Integer

I don't recommend this at all because it is hard to read.


The following does the random search the way you are looking for. I also added a comparison vs .Find to make it easier. I used the animal list here. This is the pastebin.

Public Declare Function GetTickCount Lib "kernel32.dll" () As Long

Private Function getRandomSearchArray(ByRef arr, wsRange As Range, ByVal numbAttempts As Long) As Variant

    ReDim arr(0 To numbAttempts)
    Dim wsArr
    wsArr = wsRange.Value

    Dim i As Long
    Dim rand As Double

    For i = LBound(arr) To UBound(arr)
        rand = Round(rnd() * (UBound(wsArr) - LBound(wsArr)), 0) + 1
        arr(i) = wsArr(rand, 1)
    Next i


End Function

Sub myBinarySearchExample()

    Dim searchRange As Range
    Dim sortedValues As Variant
    Dim searchValues As Variant
    Dim startTickCount As Double
    Dim numbAttempts As Long
    Dim i As Long
    Dim s As String
    Dim arrIndex As Integer


    Set searchRange = Worksheets("BinSearch").Range("A1:A141")
    numbAttempts = 100000

    sortedValues = searchRange.Value
    getRandomSearchArray searchValues, searchRange, numbAttempts

    startTickCount = GetTickCount

    'using Binary Search
    For i = 1 To numbAttempts
        s = searchValues(i)
        'Do this to simulate the "read into memory" each iteration
        sortedValues = searchRange.Value

        arrIndex = BinarySearch(sortedValues, s)
    Next i

    Debug.Print "It took: " & GetTickCount - startTickCount & " ms to run this with the Binary Search."


    'do with .Find
    startTickCount = GetTickCount

    For i = 1 To numbAttempts

        arrIndex = searchRange.Find(What:=searchValues(i), After:=Range("A1"), LookIn:=xlFormulas, _
            LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
            MatchCase:=False, SearchFormat:=False).Row


    Next i

    Debug.Print "It took: " & GetTickCount - startTickCount & " ms to run this with the Excel Find."

End Sub




Function BinarySearch(searchArray As Variant, searchValue As String) As Integer

    'dimension these as long to avoid possible integer
    'overflow errors for large lists
    Dim curIndex As Long
    Dim firstIndex As Long
    Dim lastIndex As Long

    Dim nextMiddle As Long
    Dim strValue As String

    'set lower/upper bounds correctly initially for the search
    firstIndex = LBound(searchArray)
    lastIndex = UBound(searchArray)


    Do

        nextMiddle = Round((lastIndex - firstIndex) / 2 + firstIndex)

        strValue = searchArray(nextMiddle, 1)

        'check if it is before/after the current point
        If StrComp(searchValue, strValue, vbTextCompare) = -1 Then
            lastIndex = nextMiddle - 1
        ElseIf StrComp(searchValue, strValue, vbTextCompare) = 1 Then
            firstIndex = nextMiddle + 1
        Else
            BinarySearch = nextMiddle
            Exit Do
        End If


    Loop




End Function

It took: 3182 ms to run this with the Binary Search.
It took: 11763 ms to run this with the Excel Find.
share|improve this answer
    
So wait, your custom search is actually faster? –  konijn Oct 30 at 20:11
    
@konijn yes, I added the actual time output. –  enderland Oct 30 at 20:26
    
A whole heck of a lot faster @konijn. It took: 328 ms to run this with the Binary Search. It took: 10171 ms to run this with the Excel Find. –  RubberDuck Oct 30 at 20:29
1  
@RubberDuck make sure to see the revised one, I think the initial speed increase was because I only read the worksheet values in once for the binary search, whereas the .Find would be searching the sheet each time. With reading the array each time, it is still considerably faster but slightly less fast. –  enderland Oct 30 at 20:30

Not the answer you're looking for? Browse other questions tagged or ask your own question.