I had some time to kill today, and I found the Karate Chop Kata.
Specification:
Write a binary chop method that takes an integer search target and a sorted array of integers. It should return the integer index of the target in the array, or -1 if the target is not in the array.
I've never implemented a binary search before. So, even though all of my tests pass, I'm not sure that I've covered all of the corner cases. It also doesn't look very elegant. How can I improve on this?
I'm also still not sure that I'm unit testing in a "proper" way. How can I improve them? (Please keep in mind that Rubberduck automatically inserts boilerplate for new test methods.)
Chop
Option Explicit
' Returns index of the target number in a given array.
' If not found returns -1
Public Function Chop(target As Long, Arr() As Long, Optional midpoint As Long = -1) As Long
Dim result As Long
Dim currentTest As Long
If Not IsArrayAllocated(Arr) Then
Chop = -1
Exit Function
End If
If midpoint < 0 Then
midpoint = UBound(Arr) \ 2 'integer division
End If
currentTest = Arr(midpoint)
If target = currentTest Then
result = midpoint
ElseIf midpoint = 0 Or (Arr(UBound(Arr)) < target) Then
result = -1 'not found
Else
If target > currentTest Then
midpoint = midpoint + (midpoint \ 2) ' go up by half
If midpoint > UBound(Arr) Then midpoint = UBound(Arr)
Else
midpoint = midpoint \ 2
End If
result = Chop(target, Arr, midpoint)
End If
Chop = result
End Function
'Borrorwed from Chip Pearson
' http://www.cpearson.com/excel/isarrayallocated.aspx
Function IsArrayAllocated(Arr As Variant) As Boolean
On Error Resume Next
IsArrayAllocated = IsArray(Arr) And _
Not IsError(LBound(Arr, 1)) And _
LBound(Arr, 1) <= UBound(Arr, 1)
End Function
Rubberduck Unit Tests
Option Explicit
Option Private Module
'@TestModule
Private Assert As New Rubberduck.AssertClass
'@TestMethod
Public Sub EmptyArrayReturnsNegativeOne()
On Error GoTo TestFail
Arrange:
Const expected As Long = -1
Dim integers() As Long
Act:
Assert:
Assert.AreEqual expected, Chop(2, integers)
TestExit:
Exit Sub
TestFail:
If Err.Number <> 0 Then
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
Else
Resume TestExit
End If
End Sub
'@TestMethod
Public Sub TargetNotInArrayReturnsNegativeOne()
On Error GoTo TestFail
Arrange:
Const expected As Long = -1
Const target As Long = 2
Dim integers(0) As Long
integers(0) = 3
Act:
Assert:
Assert.AreEqual expected, Chop(target, integers)
TestExit:
Exit Sub
TestFail:
If Err.Number <> 0 Then
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
Else
Resume TestExit
End If
End Sub
'@TestMethod
Public Sub TargetNotInArrayReturnsNegativeOne_LargerArray()
On Error GoTo TestFail
Arrange:
Const expected As Long = -1
Act:
Assert:
Assert.AreEqual expected, Chop(10, EvenSizeArray)
TestExit:
Exit Sub
TestFail:
If Err.Number <> 0 Then
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
Else
Resume TestExit
End If
End Sub
'@TestMethod
Public Sub OddSizeArray_FirstReturnsZero()
On Error GoTo TestFail
Arrange:
Const expected As Long = 0
Act:
Assert:
Assert.AreEqual expected, Chop(1, OddSizeArray)
TestExit:
Exit Sub
TestFail:
If Err.Number <> 0 Then
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
Else
Resume TestExit
End If
End Sub
'@TestMethod
Public Sub OddSizeArray_MiddleReturnsTwo()
On Error GoTo TestFail
Arrange:
Const expected As Long = 2
Act:
Assert:
Assert.AreEqual expected, Chop(3, OddSizeArray)
TestExit:
Exit Sub
TestFail:
If Err.Number <> 0 Then
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
Else
Resume TestExit
End If
End Sub
'@TestMethod
Public Sub OddSizeArray_LastReturnsFour()
On Error GoTo TestFail
Arrange:
Const expected As Long = 4
Act:
Assert:
Assert.AreEqual expected, Chop(5, OddSizeArray)
TestExit:
Exit Sub
TestFail:
If Err.Number <> 0 Then
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
Else
Resume TestExit
End If
End Sub
'@TestMethod
Public Sub EvenSizeArray_FirstReturnsZero()
On Error GoTo TestFail
Arrange:
Const expected As Long = 0
Act:
Assert:
Assert.AreEqual expected, Chop(1, EvenSizeArray)
TestExit:
Exit Sub
TestFail:
If Err.Number <> 0 Then
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
Else
Resume TestExit
End If
End Sub
'@TestMethod
Public Sub EvenSizeArray_LastReturnsFive()
On Error GoTo TestFail
Arrange:
Const expected As Long = 5
Act:
Assert:
Assert.AreEqual expected, Chop(6, EvenSizeArray)
TestExit:
Exit Sub
TestFail:
If Err.Number <> 0 Then
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
Else
Resume TestExit
End If
End Sub
'@TestMethod
Public Sub EvenSizeArray_NextToLastReturnsFour()
On Error GoTo TestFail
Arrange:
Const expected As Long = 4
Act:
Assert:
Assert.AreEqual expected, Chop(5, EvenSizeArray)
TestExit:
Exit Sub
TestFail:
If Err.Number <> 0 Then
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
Else
Resume TestExit
End If
End Sub
Private Function OddSizeArray() As Long()
Dim result(0 To 4) As Long
Dim i As Long
For i = LBound(result) To UBound(result)
result(i) = i + 1
Next
OddSizeArray = result
End Function
Private Function EvenSizeArray() As Long()
Dim result(0 To 5) As Long
Dim i As Long
For i = LBound(result) To UBound(result)
result(i) = i + 1
Next
EvenSizeArray = result
End Function