Skip to content

Commit c3cad8c

Browse files
committed
Updated README: updated algorithms and credits.
1 parent 36fba8a commit c3cad8c

File tree

1 file changed

+59
-33
lines changed

1 file changed

+59
-33
lines changed

Boyer-Moore/README.markdown

+59-33
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ extension String {
8787
// pattern, we can skip ahead by the full pattern length. However, if
8888
// the character *is* present in the pattern, there may be a match up
8989
// ahead and we can't skip as far.
90-
i = self.index(i, offsetBy: skipTable[c] ?? patternLength)
90+
i = index(i, offsetBy: skipTable[c] ?? patternLength, limitedBy: endIndex) ?? endIndex
9191
}
9292
}
9393
return nil
@@ -158,40 +158,66 @@ Here's an implementation of the Boyer-Moore-Horspool algorithm:
158158
```swift
159159
extension String {
160160
func indexOf(pattern: String) -> String.Index? {
161-
let patternLength = pattern.characters.count
162-
assert(patternLength > 0)
163-
assert(patternLength <= self.characters.count)
161+
// Cache the length of the search pattern because we're going to
162+
// use it a few times and it's expensive to calculate.
163+
let patternLength = pattern.characters.count
164+
assert(patternLength > 0)
165+
assert(patternLength <= self.characters.count)
164166

165-
var skipTable = [Character: Int]()
166-
for (i, c) in pattern.characters.enumerated() {
167-
skipTable[c] = patternLength - i - 1
168-
}
167+
// Make the skip table. This table determines how far we skip ahead
168+
// when a character from the pattern is found.
169+
var skipTable = [Character: Int]()
170+
for (i, c) in pattern.characters.enumerated() {
171+
skipTable[c] = patternLength - i - 1
172+
}
169173

170-
let p = pattern.index(before: pattern.endIndex)
171-
let lastChar = pattern[p]
172-
var i = self.index(startIndex, offsetBy: patternLength - 1)
173-
174-
func backwards() -> String.Index? {
175-
var q = p
176-
var j = i
177-
while q > pattern.startIndex {
178-
j = index(before: j)
179-
q = index(before: q)
180-
if self[j] != pattern[q] { return nil }
181-
}
182-
return j
183-
}
174+
// This points at the last character in the pattern.
175+
let p = pattern.index(before: pattern.endIndex)
176+
let lastChar = pattern[p]
184177

185-
while i < self.endIndex {
186-
let c = self[i]
187-
if c == lastChar {
188-
if let k = backwards() { return k }
189-
i = index(after: i)
190-
} else {
191-
i = index(i, offsetBy: skipTable[c] ?? patternLength)
192-
}
193-
}
194-
return nil
178+
// The pattern is scanned right-to-left, so skip ahead in the string by
179+
// the length of the pattern. (Minus 1 because startIndex already points
180+
// at the first character in the source string.)
181+
var i = self.index(startIndex, offsetBy: patternLength - 1)
182+
183+
// This is a helper function that steps backwards through both strings
184+
// until we find a character that doesn’t match, or until we’ve reached
185+
// the beginning of the pattern.
186+
func backwards() -> String.Index? {
187+
var q = p
188+
var j = i
189+
while q > pattern.startIndex {
190+
j = index(before: j)
191+
q = index(before: q)
192+
if self[j] != pattern[q] { return nil }
193+
}
194+
return j
195+
}
196+
197+
// The main loop. Keep going until the end of the string is reached.
198+
while i < self.endIndex {
199+
let c = self[i]
200+
201+
// Does the current character match the last character from the pattern?
202+
if c == lastChar {
203+
204+
// There is a possible match. Do a brute-force search backwards.
205+
if let k = backwards() { return k }
206+
207+
// Ensure to jump at least one character (this is needed because the first
208+
// character is in the skipTable, and `skipTable[lastChar] = 0`)
209+
let jumpOffset = max(skipTable[c] ?? patternLength, 1)
210+
i = index(i, offsetBy: jumpOffset, limitedBy: endIndex) ?? endIndex
211+
} else {
212+
// The characters are not equal, so skip ahead. The amount to skip is
213+
// determined by the skip table. If the character is not present in the
214+
// pattern, we can skip ahead by the full pattern length. However, if
215+
// the character *is* present in the pattern, there may be a match up
216+
// ahead and we can't skip as far.
217+
i = index(i, offsetBy: skipTable[c] ?? patternLength, limitedBy: endIndex) ?? endIndex
218+
}
219+
}
220+
return nil
195221
}
196222
}
197223
```
@@ -200,4 +226,4 @@ In practice, the Horspool version of the algorithm tends to perform a little bet
200226

201227
Credits: This code is based on the paper: [R. N. Horspool (1980). "Practical fast searching in strings". Software - Practice & Experience 10 (6): 501–506.](http://www.cin.br/~paguso/courses/if767/bib/Horspool_1980.pdf)
202228

203-
_Written for Swift Algorithm Club by Matthijs Hollemans, updated by Andreas Neusüß_
229+
_Written for Swift Algorithm Club by Matthijs Hollemans, updated by Andreas Neusüß_, [Matías Mazzei](https://github.com/mmazzei).

0 commit comments

Comments
 (0)