diff --git a/Binary Search/BinarySearch.playground/Contents.swift b/Binary Search/BinarySearch.playground/Contents.swift index fdfd37d8f..cccf9f8ac 100644 --- a/Binary Search/BinarySearch.playground/Contents.swift +++ b/Binary Search/BinarySearch.playground/Contents.swift @@ -4,53 +4,16 @@ let numbers = [11, 59, 3, 2, 53, 17, 31, 7, 19, 67, 47, 13, 37, 61, 29, 43, 5, 41, 23] // Binary search requires that the array is sorted from low to high -let sorted = numbers.sort() +let sorted = numbers.sorted() -/* - The recursive version of binary search. -*/ -func binarySearch(a: [T], key: T, range: Range) -> Int? { - if range.startIndex >= range.endIndex { - return nil - } else { - let midIndex = range.startIndex + (range.endIndex - range.startIndex) / 2 - if a[midIndex] > key { - return binarySearch(a, key: key, range: range.startIndex ..< midIndex) - } else if a[midIndex] < key { - return binarySearch(a, key: key, range: midIndex + 1 ..< range.endIndex) - } else { - return midIndex - } - } -} +// Using recursive solution +binarySearch(a: sorted, key: 2, range: 0 ..< sorted.count) // gives 0 +binarySearch(a: sorted, key: 67, range: 0 ..< sorted.count) // gives 18 +binarySearch(a: sorted, key: 43, range: 0 ..< sorted.count) // gives 13 +binarySearch(a: sorted, key: 42, range: 0 ..< sorted.count) // nil -binarySearch(sorted, key: 2, range: 0 ..< sorted.count) // gives 0 -binarySearch(sorted, key: 67, range: 0 ..< sorted.count) // gives 18 -binarySearch(sorted, key: 43, range: 0 ..< sorted.count) // gives 13 -binarySearch(sorted, key: 42, range: 0 ..< sorted.count) // nil - -/* - The iterative version of binary search. - - Notice how similar these functions are. The difference is that this one - uses a while loop, while the other calls itself recursively. -*/ -func binarySearch(a: [T], key: T) -> Int? { - var range = 0..(a: [T], key: T, range: Range) -> Int? { + if range.lowerBound >= range.upperBound { + return nil + } else { + let midIndex = range.lowerBound + (range.upperBound - range.lowerBound) / 2 + if a[midIndex] > key { + return binarySearch(a: a, key: key, range: range.lowerBound ..< midIndex) + } else if a[midIndex] < key { + return binarySearch(a: a, key: key, range: midIndex + 1 ..< range.upperBound) + } else { + return midIndex + } + } +} + +/** + The iterative version of binary search. + + Notice how similar these functions are. The difference is that this one + uses a while loop, while the other calls itself recursively. + **/ + +public func binarySearch(a: [T], key: T) -> Int? { + var lowerBound = 0 + var upperBound = a.count + while lowerBound < upperBound { + let midIndex = lowerBound + (upperBound - lowerBound) / 2 + if a[midIndex] == key { + return midIndex + } else if a[midIndex] < key { + lowerBound = midIndex + 1 + } else { + upperBound = midIndex + } + } + return nil +} diff --git a/Binary Search/BinarySearch.swift b/Binary Search/BinarySearch.swift index 5bcdf4152..d6623e355 100644 --- a/Binary Search/BinarySearch.swift +++ b/Binary Search/BinarySearch.swift @@ -1,24 +1,52 @@ -/* - Binary Search +/** + Binary Search + + Recursively splits the array in half until the value is found. + + If there is more than one occurrence of the search key in the array, then + there is no guarantee which one it finds. + + Note: The array must be sorted! + **/ - Recursively splits the array in half until the value is found. +import Foundation - If there is more than one occurrence of the search key in the array, then - there is no guarantee which one it finds. +// The recursive version of binary search. - Note: The array must be sorted! -*/ -func binarySearch(a: [T], key: T) -> Int? { - var range = 0..(_ a: [T], key: T, range: Range) -> Int? { + if range.lowerBound >= range.upperBound { + return nil } else { - range.endIndex = midIndex + let midIndex = range.lowerBound + (range.upperBound - range.lowerBound) / 2 + if a[midIndex] > key { + return binarySearch(a, key: key, range: range.lowerBound ..< midIndex) + } else if a[midIndex] < key { + return binarySearch(a, key: key, range: midIndex + 1 ..< range.upperBound) + } else { + return midIndex + } } - } - return nil +} + +/** + The iterative version of binary search. + + Notice how similar these functions are. The difference is that this one + uses a while loop, while the other calls itself recursively. + **/ + +public func binarySearch(_ a: [T], key: T) -> Int? { + var lowerBound = 0 + var upperBound = a.count + while lowerBound < upperBound { + let midIndex = lowerBound + (upperBound - lowerBound) / 2 + if a[midIndex] == key { + return midIndex + } else if a[midIndex] < key { + lowerBound = midIndex + 1 + } else { + upperBound = midIndex + } + } + return nil } diff --git a/Binary Search/README.markdown b/Binary Search/README.markdown index 8c5d8f0f1..0aa039254 100644 --- a/Binary Search/README.markdown +++ b/Binary Search/README.markdown @@ -16,12 +16,12 @@ The built-in `indexOf()` function performs a [linear search](../Linear Search/). ```swift func linearSearch(a: [T], _ key: T) -> Int? { - for i in 0 ..< a.count { - if a[i] == key { - return i + for i in 0 ..< a.count { + if a[i] == key { + return i + } } - } - return nil + return nil } ``` @@ -58,27 +58,27 @@ Here is a recursive implementation of binary search in Swift: ```swift func binarySearch(a: [T], key: T, range: Range) -> Int? { - if range.startIndex >= range.endIndex { - // If we get here, then the search key is not present in the array. - return nil + if range.lowerBound >= range.upperBound { + // If we get here, then the search key is not present in the array. + return nil - } else { - // Calculate where to split the array. - let midIndex = range.startIndex + (range.endIndex - range.startIndex) / 2 - - // Is the search key in the left half? - if a[midIndex] > key { - return binarySearch(a, key: key, range: range.startIndex ..< midIndex) - - // Is the search key in the right half? - } else if a[midIndex] < key { - return binarySearch(a, key: key, range: midIndex + 1 ..< range.endIndex) - - // If we get here, then we've found the search key! } else { - return midIndex + // Calculate where to split the array. + let midIndex = range.lowerBound + (range.upperBound - range.lowerBound) / 2 + + // Is the search key in the left half? + if a[midIndex] > key { + return binarySearch(a, key: key, range: range.lowerBound ..< midIndex) + + // Is the search key in the right half? + } else if a[midIndex] < key { + return binarySearch(a, key: key, range: midIndex + 1 ..< range.upperBound) + + // If we get here, then we've found the search key! + } else { + return midIndex + } } - } } ``` @@ -94,7 +94,7 @@ Note that the `numbers` array is sorted. The binary search algorithm does not wo I said that binary search works by splitting the array in half, but we don't actually create two new arrays. Instead, we keep track of these splits using a Swift `Range` object. Initially, this range covers the entire array, `0 ..< numbers.count`. As we split the array, the range becomes smaller and smaller. -> **Note:** One thing to be aware of is that `range.endIndex` always points one beyond the last element. In the example, the range is `0..<19` because there are 19 numbers in the array, and so `range.startIndex = 0` and `range.endIndex = 19`. But in our array the last element is at index 18, not 19, since we start counting from 0. Just keep this in mind when working with ranges: the `endIndex` is always one more than the index of the last element. +> **Note:** One thing to be aware of is that `range.upperBound` always points one beyond the last element. In the example, the range is `0..<19` because there are 19 numbers in the array, and so `range.lowerBound = 0` and `range.upperBound = 19`. But in our array the last element is at index 18, not 19, since we start counting from 0. Just keep this in mind when working with ranges: the `upperBound` is always one more than the index of the last element. ## Stepping through the example @@ -109,10 +109,10 @@ We're trying to determine if the number `43` is in this array. To split the array in half, we need to know the index of the object in the middle. That's determined by this line: ```swift - let midIndex = range.startIndex + (range.endIndex - range.startIndex) / 2 +let midIndex = range.lowerBound + (range.upperBound - range.lowerBound) / 2 ``` -Initially, the range has `startIndex = 0` and `endIndex = 19`. Filling in these values, we find that `midIndex` is `0 + (19 - 0)/2 = 19/2 = 9`. It's actually `9.5` but because we're using integers, the answer is rounded down. +Initially, the range has `lowerBound = 0` and `upperBound = 19`. Filling in these values, we find that `midIndex` is `0 + (19 - 0)/2 = 19/2 = 9`. It's actually `9.5` but because we're using integers, the answer is rounded down. In the next figure, the `*` shows the middle item. As you can see, the number of items on each side is the same, so we're split right down the middle. @@ -122,18 +122,18 @@ In the next figure, the `*` shows the middle item. As you can see, the number of Now binary search will determine which half to use. The relevant section from the code is: ```swift - if a[midIndex] > key { - // use left half - } else if a[midIndex] < key { - // use right half - } else { - return midIndex - } +if a[midIndex] > key { + // use left half +} else if a[midIndex] < key { + // use right half +} else { + return midIndex +} ``` In this case, `a[midIndex] = 29`. That's less than the search key, so we can safely conclude that the search key will never be in the left half of the array. After all, the left half only contains numbers smaller than `29`. Hence, the search key must be in the right half somewhere (or not in the array at all). -Now we can simply repeat the binary search, but on the array interval from `midIndex + 1` to `range.endIndex`: +Now we can simply repeat the binary search, but on the array interval from `midIndex + 1` to `range.upperBound`: [ x, x, x, x, x, x, x, x, x, x | 31, 37, 41, 43, 47, 53, 59, 61, 67 ] @@ -169,9 +169,9 @@ And now we're done. The search key equals the array element we're looking at, so It may have seemed like a lot of work, but in reality it only took four steps to find the search key in the array, which sounds about right because `log_2(19) = 4.23`. With a linear search, it would have taken 14 steps. -What would happen if we were to search for `42` instead of `43`? In that case, we can't split up the array any further. The `range.endIndex` becomes smaller than `range.startIndex`. That tells the algorithm the search key is not in the array and it returns `nil`. +What would happen if we were to search for `42` instead of `43`? In that case, we can't split up the array any further. The `range.upperBound` becomes smaller than `range.lowerBound`. That tells the algorithm the search key is not in the array and it returns `nil`. -> **Note:** Many implementations of binary search calculate `midIndex = (startIndex + endIndex) / 2`. This contains a subtle bug that only appears with very large arrays, because `startIndex + endIndex` may overflow the maximum number an integer can hold. This situation is unlikely to happen on a 64-bit CPU, but it definitely can on 32-bit machines. +> **Note:** Many implementations of binary search calculate `midIndex = (lowerBound + upperBound) / 2`. This contains a subtle bug that only appears with very large arrays, because `lowerBound + upperBound` may overflow the maximum number an integer can hold. This situation is unlikely to happen on a 64-bit CPU, but it definitely can on 32-bit machines. ## Iterative vs recursive @@ -181,18 +181,19 @@ Here is an iterative implementation of binary search in Swift: ```swift func binarySearch(a: [T], key: T) -> Int? { - var range = 0.. + BuildableName = "BinarySearchTests.xctest" + BlueprintName = "BinarySearchTests" + ReferencedContainer = "container:BinarySearchTests.xcodeproj"> @@ -33,9 +33,9 @@ + BuildableName = "BinarySearchTests.xctest" + BlueprintName = "BinarySearchTests" + ReferencedContainer = "container:BinarySearchTests.xcodeproj"> @@ -56,9 +56,9 @@ + BuildableName = "BinarySearchTests.xctest" + BlueprintName = "BinarySearchTests" + ReferencedContainer = "container:BinarySearchTests.xcodeproj"> @@ -74,9 +74,9 @@ + BuildableName = "BinarySearchTests.xctest" + BlueprintName = "BinarySearchTests" + ReferencedContainer = "container:BinarySearchTests.xcodeproj">