Binary Search: Speed up your searches!
Tags: ruby, tutorial, computerscience
Published: 2020-09-02 20:07:45

Say you had to determine whether 15 was in the array below, *without* using a built-in method. How would you do it?

```ruby
array = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
```

One way would be to go through each value in the array, check if it was equal to 15, and return true if so. Something like this:

```ruby
for i in 0...array.length do
   if array[i] == search
        return true
   end
end
```

Easy enough. But linear search has a worst-case time complexity of O(N), meaning in a worst-case scenario (say we were looking for 21, or a value that doesn't exist like 10) we would have to compare every single element.

Not too bad for an array of 11 items, but not great if it was 100M values long. That's 100M potential comparisons!

Enter Binary Search. So powerful, it will find any value in a 100M length array in a maximum of 27 comparisons. How come?

The key feature of our array is that it is *already sorted*. If we pick the middle value, we know that everything to the left is less or equal, and everything to the right is greater or equal.

```ruby
array = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
# middle value is 11
```

If we're searching for 15, we can make one comparison with the midpoint 11. 15 is greater than 11, so we can instantly cut our search in half as there is no need to look at the left half of the array. We can then start our new search at 13 and find a new midpoint.

Binary search uses the fact the array is sorted to cut our search list in half every time we make a comparison. That's huge!

## Pseudocode

- Set a search start and search finish variable
- While our search area is valid (i.e. start is less than or equal to our finish)
- Set the midpoint index halfway between start and finish
- Compare the value at that midpoint index with our search value
- If they're equal, great! We can return the midpoint
- If the midpoint value is higher than search, then we set our finish index to be the index before our midpoint
- If the midpoint value is lower than search, then we set our start index to be the value after our midpoint
- If the value isn't there, our search area will eventually become invalid, and we'll return false


## Iterative Ruby Code
Solving this iteratively, the code then looks something like this:

``` ruby
def binarySearch(array, search)
  start = 0
  finish = (array.length) - 1

  while start <= finish
    midpoint = (start + finish) / 2
    if array[midpoint] == search
      return midpoint
    elsif array[midpoint] > search
      finish = midpoint - 1
    elsif array[midpoint] < search
      start = midpoint + 1
    end
  end
  return false
end
``` 

## Recursive Ruby Code

We can also write this recursively, using the same logic. I think it's a bit cleaner, but it does use more memory and we'll need to pass in our start/finish variables as arguments:

``` ruby
def binarySearchRecursive(array, search, start, finish)
  if finish < start
    return false
  end
  midpoint = (start + finish) / 2
  if array[midpoint] == search
    return midpoint
  elsif array[midpoint] > search
    binarySearchRecursive(array, search, start, midpoint - 1)
  elsif array[midpoint] < search
    binarySearchRecursive(array, search, midpoint + 1, finish)
  end
end
```

And that's binary search! Hope you found this useful and/or interesting, and it saves you some time next time you need to find a needle in a 100M strong haystack...