Skip to content

Commit aec3405

Browse files
committed
Upgrade binary search code and test to Swift 3.0
1 parent 116fb5d commit aec3405

File tree

7 files changed

+207
-155
lines changed

7 files changed

+207
-155
lines changed

Binary Search/BinarySearch.playground/Contents.swift

+11-48
Original file line numberDiff line numberDiff line change
@@ -4,53 +4,16 @@
44
let numbers = [11, 59, 3, 2, 53, 17, 31, 7, 19, 67, 47, 13, 37, 61, 29, 43, 5, 41, 23]
55

66
// Binary search requires that the array is sorted from low to high
7-
let sorted = numbers.sort()
7+
let sorted = numbers.sorted()
88

9-
/*
10-
The recursive version of binary search.
11-
*/
12-
func binarySearch<T: Comparable>(a: [T], key: T, range: Range<Int>) -> Int? {
13-
if range.startIndex >= range.endIndex {
14-
return nil
15-
} else {
16-
let midIndex = range.startIndex + (range.endIndex - range.startIndex) / 2
17-
if a[midIndex] > key {
18-
return binarySearch(a, key: key, range: range.startIndex ..< midIndex)
19-
} else if a[midIndex] < key {
20-
return binarySearch(a, key: key, range: midIndex + 1 ..< range.endIndex)
21-
} else {
22-
return midIndex
23-
}
24-
}
25-
}
9+
// Using recursive solution
10+
binarySearch(a: sorted, key: 2, range: 0 ..< sorted.count) // gives 0
11+
binarySearch(a: sorted, key: 67, range: 0 ..< sorted.count) // gives 18
12+
binarySearch(a: sorted, key: 43, range: 0 ..< sorted.count) // gives 13
13+
binarySearch(a: sorted, key: 42, range: 0 ..< sorted.count) // nil
2614

27-
binarySearch(sorted, key: 2, range: 0 ..< sorted.count) // gives 0
28-
binarySearch(sorted, key: 67, range: 0 ..< sorted.count) // gives 18
29-
binarySearch(sorted, key: 43, range: 0 ..< sorted.count) // gives 13
30-
binarySearch(sorted, key: 42, range: 0 ..< sorted.count) // nil
31-
32-
/*
33-
The iterative version of binary search.
34-
35-
Notice how similar these functions are. The difference is that this one
36-
uses a while loop, while the other calls itself recursively.
37-
*/
38-
func binarySearch<T: Comparable>(a: [T], key: T) -> Int? {
39-
var range = 0..<a.count
40-
while range.startIndex < range.endIndex {
41-
let midIndex = range.startIndex + (range.endIndex - range.startIndex) / 2
42-
if a[midIndex] == key {
43-
return midIndex
44-
} else if a[midIndex] < key {
45-
range.startIndex = midIndex + 1
46-
} else {
47-
range.endIndex = midIndex
48-
}
49-
}
50-
return nil
51-
}
52-
53-
binarySearch(sorted, key: 2) // gives 0
54-
binarySearch(sorted, key: 67) // gives 18
55-
binarySearch(sorted, key: 43) // gives 13
56-
binarySearch(sorted, key: 42) // nil
15+
// Using iterative solution
16+
binarySearch(a: sorted, key: 2) // gives 0
17+
binarySearch(a: sorted, key: 67) // gives 18
18+
binarySearch(a: sorted, key: 43) // gives 13
19+
binarySearch(a: sorted, key: 42) // nil
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
Binary Search
3+
4+
Recursively splits the array in half until the value is found.
5+
6+
If there is more than one occurrence of the search key in the array, then
7+
there is no guarantee which one it finds.
8+
9+
Note: The array must be sorted!
10+
**/
11+
12+
import Foundation
13+
14+
// The recursive version of binary search.
15+
16+
public func binarySearch<T: Comparable>(a: [T], key: T, range: Range<Int>) -> Int? {
17+
if range.lowerBound >= range.upperBound {
18+
return nil
19+
} else {
20+
let midIndex = range.lowerBound + (range.upperBound - range.lowerBound) / 2
21+
if a[midIndex] > key {
22+
return binarySearch(a: a, key: key, range: range.lowerBound ..< midIndex)
23+
} else if a[midIndex] < key {
24+
return binarySearch(a: a, key: key, range: midIndex + 1 ..< range.upperBound)
25+
} else {
26+
return midIndex
27+
}
28+
}
29+
}
30+
31+
/**
32+
The iterative version of binary search.
33+
34+
Notice how similar these functions are. The difference is that this one
35+
uses a while loop, while the other calls itself recursively.
36+
**/
37+
38+
public func binarySearch<T: Comparable>(a: [T], key: T) -> Int? {
39+
var lowerBound = 0
40+
var upperBound = a.count
41+
while lowerBound < upperBound {
42+
let midIndex = lowerBound + (upperBound - lowerBound) / 2
43+
if a[midIndex] == key {
44+
return midIndex
45+
} else if a[midIndex] < key {
46+
lowerBound = midIndex + 1
47+
} else {
48+
upperBound = midIndex
49+
}
50+
}
51+
return nil
52+
}

Binary Search/BinarySearch.swift

+46-18
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,52 @@
1-
/*
2-
Binary Search
1+
/**
2+
Binary Search
3+
4+
Recursively splits the array in half until the value is found.
5+
6+
If there is more than one occurrence of the search key in the array, then
7+
there is no guarantee which one it finds.
8+
9+
Note: The array must be sorted!
10+
**/
311

4-
Recursively splits the array in half until the value is found.
12+
import Foundation
513

6-
If there is more than one occurrence of the search key in the array, then
7-
there is no guarantee which one it finds.
14+
// The recursive version of binary search.
815

9-
Note: The array must be sorted!
10-
*/
11-
func binarySearch<T: Comparable>(a: [T], key: T) -> Int? {
12-
var range = 0..<a.count
13-
while range.startIndex < range.endIndex {
14-
let midIndex = range.startIndex + (range.endIndex - range.startIndex) / 2
15-
if a[midIndex] == key {
16-
return midIndex
17-
} else if a[midIndex] < key {
18-
range.startIndex = midIndex + 1
16+
public func binarySearch<T: Comparable>(_ a: [T], key: T, range: Range<Int>) -> Int? {
17+
if range.lowerBound >= range.upperBound {
18+
return nil
1919
} else {
20-
range.endIndex = midIndex
20+
let midIndex = range.lowerBound + (range.upperBound - range.lowerBound) / 2
21+
if a[midIndex] > key {
22+
return binarySearch(a, key: key, range: range.lowerBound ..< midIndex)
23+
} else if a[midIndex] < key {
24+
return binarySearch(a, key: key, range: midIndex + 1 ..< range.upperBound)
25+
} else {
26+
return midIndex
27+
}
2128
}
22-
}
23-
return nil
29+
}
30+
31+
/**
32+
The iterative version of binary search.
33+
34+
Notice how similar these functions are. The difference is that this one
35+
uses a while loop, while the other calls itself recursively.
36+
**/
37+
38+
public func binarySearch<T: Comparable>(_ a: [T], key: T) -> Int? {
39+
var lowerBound = 0
40+
var upperBound = a.count
41+
while lowerBound < upperBound {
42+
let midIndex = lowerBound + (upperBound - lowerBound) / 2
43+
if a[midIndex] == key {
44+
return midIndex
45+
} else if a[midIndex] < key {
46+
lowerBound = midIndex + 1
47+
} else {
48+
upperBound = midIndex
49+
}
50+
}
51+
return nil
2452
}

Binary Search/README.markdown

+66-65
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ The built-in `indexOf()` function performs a [linear search](../Linear Search/).
1616

1717
```swift
1818
func linearSearch<T: Equatable>(a: [T], _ key: T) -> Int? {
19-
for i in 0 ..< a.count {
20-
if a[i] == key {
21-
return i
22-
}
23-
}
24-
return nil
19+
for i in 0 ..< a.count {
20+
if a[i] == key {
21+
return i
22+
}
23+
}
24+
return nil
2525
}
2626
```
2727

@@ -58,27 +58,27 @@ Here is a recursive implementation of binary search in Swift:
5858

5959
```swift
6060
func binarySearch<T: Comparable>(a: [T], key: T, range: Range<Int>) -> Int? {
61-
if range.startIndex >= range.endIndex {
62-
// If we get here, then the search key is not present in the array.
63-
return nil
64-
65-
} else {
66-
// Calculate where to split the array.
67-
let midIndex = range.startIndex + (range.endIndex - range.startIndex) / 2
68-
69-
// Is the search key in the left half?
70-
if a[midIndex] > key {
71-
return binarySearch(a, key: key, range: range.startIndex ..< midIndex)
72-
73-
// Is the search key in the right half?
74-
} else if a[midIndex] < key {
75-
return binarySearch(a, key: key, range: midIndex + 1 ..< range.endIndex)
76-
77-
// If we get here, then we've found the search key!
78-
} else {
79-
return midIndex
80-
}
81-
}
61+
if range.lowerBound >= range.upperBound {
62+
// If we get here, then the search key is not present in the array.
63+
return nil
64+
65+
} else {
66+
// Calculate where to split the array.
67+
let midIndex = range.lowerBound + (range.upperBound - range.lowerBound) / 2
68+
69+
// Is the search key in the left half?
70+
if a[midIndex] > key {
71+
return binarySearch(a, key: key, range: range.lowerBound ..< midIndex)
72+
73+
// Is the search key in the right half?
74+
} else if a[midIndex] < key {
75+
return binarySearch(a, key: key, range: midIndex + 1 ..< range.upperBound)
76+
77+
// If we get here, then we've found the search key!
78+
} else {
79+
return midIndex
80+
}
81+
}
8282
}
8383
```
8484

@@ -94,84 +94,84 @@ Note that the `numbers` array is sorted. The binary search algorithm does not wo
9494

9595
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.
9696

97-
> **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.
97+
> **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.
9898
9999
## Stepping through the example
100100

101101
It might be useful to look at how the algorithm works in detail.
102102

103103
The array from the above example consists of 19 numbers and looks like this when sorted:
104104

105-
[ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67 ]
105+
[ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67 ]
106106

107107
We're trying to determine if the number `43` is in this array.
108108

109109
To split the array in half, we need to know the index of the object in the middle. That's determined by this line:
110110

111111
```swift
112-
let midIndex = range.startIndex + (range.endIndex - range.startIndex) / 2
112+
let midIndex = range.lowerBound + (range.upperBound - range.lowerBound) / 2
113113
```
114114

115-
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.
115+
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.
116116

117117
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.
118118

119-
[ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67 ]
120-
*
119+
[ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67 ]
120+
*
121121

122122
Now binary search will determine which half to use. The relevant section from the code is:
123123

124124
```swift
125-
if a[midIndex] > key {
126-
// use left half
127-
} else if a[midIndex] < key {
128-
// use right half
129-
} else {
130-
return midIndex
131-
}
125+
if a[midIndex] > key {
126+
// use left half
127+
} else if a[midIndex] < key {
128+
// use right half
129+
} else {
130+
return midIndex
131+
}
132132
```
133133

134134
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).
135135

136-
Now we can simply repeat the binary search, but on the array interval from `midIndex + 1` to `range.endIndex`:
136+
Now we can simply repeat the binary search, but on the array interval from `midIndex + 1` to `range.upperBound`:
137137

138-
[ x, x, x, x, x, x, x, x, x, x | 31, 37, 41, 43, 47, 53, 59, 61, 67 ]
138+
[ x, x, x, x, x, x, x, x, x, x | 31, 37, 41, 43, 47, 53, 59, 61, 67 ]
139139

140140
Since we no longer need to concern ourselves with the left half of the array, I've marked that with `x`'s. From now on we'll only look at the right half, which starts at array index 10.
141141

142142
We calculate the index of the new middle element: `midIndex = 10 + (19 - 10)/2 = 14`, and split the array down the middle again.
143143

144-
[ x, x, x, x, x, x, x, x, x, x | 31, 37, 41, 43, 47, 53, 59, 61, 67 ]
145-
*
144+
[ x, x, x, x, x, x, x, x, x, x | 31, 37, 41, 43, 47, 53, 59, 61, 67 ]
145+
*
146146

147147
As you can see, `a[14]` is indeed the middle element of the array's right half.
148148

149149
Is the search key greater or smaller than `a[14]`? It's smaller because `43 < 47`. This time we're taking the left half and ignore the larger numbers on the right:
150150

151-
[ x, x, x, x, x, x, x, x, x, x | 31, 37, 41, 43 | x, x, x, x, x ]
151+
[ x, x, x, x, x, x, x, x, x, x | 31, 37, 41, 43 | x, x, x, x, x ]
152152

153153
The new `midIndex` is here:
154154

155-
[ x, x, x, x, x, x, x, x, x, x | 31, 37, 41, 43 | x, x, x, x, x ]
156-
*
155+
[ x, x, x, x, x, x, x, x, x, x | 31, 37, 41, 43 | x, x, x, x, x ]
156+
*
157157

158158
The search key is greater than `37`, so continue with the right side:
159159

160-
[ x, x, x, x, x, x, x, x, x, x | x, x | 41, 43 | x, x, x, x, x ]
161-
*
160+
[ x, x, x, x, x, x, x, x, x, x | x, x | 41, 43 | x, x, x, x, x ]
161+
*
162162

163163
Again, the search key is greater, so split once more and take the right side:
164164

165-
[ x, x, x, x, x, x, x, x, x, x | x, x | x | 43 | x, x, x, x, x ]
166-
*
165+
[ x, x, x, x, x, x, x, x, x, x | x, x | x | 43 | x, x, x, x, x ]
166+
*
167167

168168
And now we're done. The search key equals the array element we're looking at, so we've finally found what we were searching for: number `43` is at array index `13`. w00t!
169169

170170
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.
171171

172-
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`.
172+
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`.
173173

174-
> **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.
174+
> **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.
175175
176176
## Iterative vs recursive
177177

@@ -181,18 +181,19 @@ Here is an iterative implementation of binary search in Swift:
181181

182182
```swift
183183
func binarySearch<T: Comparable>(a: [T], key: T) -> Int? {
184-
var range = 0..<a.count
185-
while range.startIndex < range.endIndex {
186-
let midIndex = range.startIndex + (range.endIndex - range.startIndex) / 2
187-
if a[midIndex] == key {
188-
return midIndex
189-
} else if a[midIndex] < key {
190-
range.startIndex = midIndex + 1
191-
} else {
192-
range.endIndex = midIndex
193-
}
194-
}
195-
return nil
184+
var lowerBound = 0
185+
var upperBound = a.count
186+
while lowerBound < upperBound {
187+
let midIndex = lowerBound + (upperBound - lowerBound) / 2
188+
if a[midIndex] == key {
189+
return midIndex
190+
} else if a[midIndex] < key {
191+
lowerBound = midIndex + 1
192+
} else {
193+
upperBound = midIndex
194+
}
195+
}
196+
return nil
196197
}
197198
```
198199

0 commit comments

Comments
 (0)