Skip to content

Commit 5c93227

Browse files
Merge pull request #747 from apple/swiftmain_search-algorithm
[swift/main] Add a string-specific search algorithm
2 parents e1926b6 + 884fc8d commit 5c93227

File tree

6 files changed

+367
-34
lines changed

6 files changed

+367
-34
lines changed

Sources/_StringProcessing/Algorithms/Algorithms/FirstRange.swift

+41-5
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,23 @@ extension Collection {
2121
}
2222

2323
// MARK: Fixed pattern algorithms
24+
extension Substring {
25+
func _firstRangeSubstring(
26+
of other: Substring
27+
) -> Range<String.Index>? {
28+
var searcher = SubstringSearcher(text: self, pattern: other)
29+
return searcher.next()
30+
}
31+
}
2432

2533
extension Collection where Element: Equatable {
34+
func _firstRangeGeneric<C: Collection>(
35+
of other: C
36+
) -> Range<Index>? where C.Element == Element {
37+
let searcher = ZSearcher<SubSequence>(pattern: Array(other), by: ==)
38+
return searcher.search(self[...], in: startIndex..<endIndex)
39+
}
40+
2641
/// Finds and returns the range of the first occurrence of a given collection
2742
/// within this collection.
2843
///
@@ -33,9 +48,19 @@ extension Collection where Element: Equatable {
3348
public func firstRange<C: Collection>(
3449
of other: C
3550
) -> Range<Index>? where C.Element == Element {
36-
// TODO: Use a more efficient search algorithm
37-
let searcher = ZSearcher<SubSequence>(pattern: Array(other), by: ==)
38-
return searcher.search(self[...], in: startIndex..<endIndex)
51+
switch (self, other) {
52+
case (let str as String, let other as String):
53+
return str[...]._firstRangeSubstring(of: other[...]) as! Range<Index>?
54+
case (let str as Substring, let other as String):
55+
return str._firstRangeSubstring(of: other[...]) as! Range<Index>?
56+
case (let str as String, let other as Substring):
57+
return str[...]._firstRangeSubstring(of: other) as! Range<Index>?
58+
case (let str as Substring, let other as Substring):
59+
return str._firstRangeSubstring(of: other) as! Range<Index>?
60+
61+
default:
62+
return _firstRangeGeneric(of: other)
63+
}
3964
}
4065
}
4166

@@ -50,8 +75,19 @@ extension BidirectionalCollection where Element: Comparable {
5075
public func firstRange<C: Collection>(
5176
of other: C
5277
) -> Range<Index>? where C.Element == Element {
53-
let searcher = ZSearcher<SubSequence>(pattern: Array(other), by: ==)
54-
return searcher.search(self[...], in: startIndex..<endIndex)
78+
switch (self, other) {
79+
case (let str as String, let other as String):
80+
return str[...]._firstRangeSubstring(of: other[...]) as! Range<Index>?
81+
case (let str as Substring, let other as String):
82+
return str._firstRangeSubstring(of: other[...]) as! Range<Index>?
83+
case (let str as String, let other as Substring):
84+
return str[...]._firstRangeSubstring(of: other) as! Range<Index>?
85+
case (let str as Substring, let other as Substring):
86+
return str._firstRangeSubstring(of: other) as! Range<Index>?
87+
88+
default:
89+
return _firstRangeGeneric(of: other)
90+
}
5591
}
5692
}
5793

Sources/_StringProcessing/Algorithms/Algorithms/Ranges.swift

+14-2
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ extension Collection where Element: Equatable {
135135
) -> RangesCollection<ZSearcher<Self>> where C.Element == Element {
136136
_ranges(of: ZSearcher(pattern: Array(other), by: ==))
137137
}
138-
138+
139139
// FIXME: Return `some Collection<Range<Index>>` for SE-0346
140140
/// Finds and returns the ranges of the all occurrences of a given sequence
141141
/// within the collection.
@@ -146,7 +146,19 @@ extension Collection where Element: Equatable {
146146
public func ranges<C: Collection>(
147147
of other: C
148148
) -> [Range<Index>] where C.Element == Element {
149-
Array(_ranges(of: other))
149+
switch (self, other) {
150+
case (let str as String, let other as String):
151+
return Array(SubstringSearcher(text: str[...], pattern: other[...])) as! [Range<Index>]
152+
case (let str as Substring, let other as String):
153+
return Array(SubstringSearcher(text: str, pattern: other[...])) as! [Range<Index>]
154+
case (let str as String, let other as Substring):
155+
return Array(SubstringSearcher(text: str[...], pattern: other)) as! [Range<Index>]
156+
case (let str as Substring, let other as Substring):
157+
return Array(SubstringSearcher(text: str, pattern: other)) as! [Range<Index>]
158+
159+
default:
160+
return Array(_ranges(of: other))
161+
}
150162
}
151163
}
152164

Sources/_StringProcessing/Algorithms/Algorithms/Replace.swift

+60-17
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,32 @@
1111

1212
// MARK: `CollectionSearcher` algorithms
1313

14+
extension Substring {
15+
func _replacingSubstring(
16+
_ other: Substring,
17+
with replacement: Substring,
18+
maxReplacements: Int = .max
19+
) -> String {
20+
precondition(maxReplacements >= 0)
21+
22+
var result = String()
23+
var index = startIndex
24+
25+
var rangeIterator = SubstringSearcher(text: self, pattern: other)
26+
var replacementCount = 0
27+
while replacementCount < maxReplacements, let range = rangeIterator.next() {
28+
result.append(contentsOf: self[index..<range.lowerBound])
29+
result.append(contentsOf: replacement)
30+
index = range.upperBound
31+
32+
replacementCount += 1
33+
}
34+
35+
result.append(contentsOf: self[index...])
36+
return result
37+
}
38+
}
39+
1440
extension RangeReplaceableCollection {
1541
func _replacing<Ranges: Collection, Replacement: Collection>(
1642
_ ranges: Ranges,
@@ -35,19 +61,6 @@ extension RangeReplaceableCollection {
3561
result.append(contentsOf: self[index...])
3662
return result
3763
}
38-
39-
mutating func _replace<
40-
Ranges: Collection, Replacement: Collection
41-
>(
42-
_ ranges: Ranges,
43-
with replacement: Replacement,
44-
maxReplacements: Int = .max
45-
) where Ranges.Element == Range<Index>, Replacement.Element == Element {
46-
self = _replacing(
47-
ranges,
48-
with: replacement,
49-
maxReplacements: maxReplacements)
50-
}
5164
}
5265

5366
// MARK: Fixed pattern algorithms
@@ -70,10 +83,40 @@ extension RangeReplaceableCollection where Element: Equatable {
7083
subrange: Range<Index>,
7184
maxReplacements: Int = .max
7285
) -> Self where C.Element == Element, Replacement.Element == Element {
73-
_replacing(
74-
self[subrange]._ranges(of: other),
75-
with: replacement,
76-
maxReplacements: maxReplacements)
86+
switch (self, other, replacement) {
87+
case (let str as String, let other as String, let repl as String):
88+
return str[...]._replacingSubstring(other[...], with: repl[...],
89+
maxReplacements: maxReplacements) as! Self
90+
case (let str as Substring, let other as Substring, let repl as Substring):
91+
return str._replacingSubstring(other, with: repl,
92+
maxReplacements: maxReplacements)[...] as! Self
93+
94+
case (let str as Substring, let other as String, let repl as String):
95+
return str[...]._replacingSubstring(other[...], with: repl[...],
96+
maxReplacements: maxReplacements)[...] as! Self
97+
case (let str as String, let other as Substring, let repl as String):
98+
return str[...]._replacingSubstring(other[...], with: repl[...],
99+
maxReplacements: maxReplacements) as! Self
100+
case (let str as String, let other as String, let repl as Substring):
101+
return str[...]._replacingSubstring(other[...], with: repl[...],
102+
maxReplacements: maxReplacements) as! Self
103+
104+
case (let str as String, let other as Substring, let repl as Substring):
105+
return str[...]._replacingSubstring(other[...], with: repl[...],
106+
maxReplacements: maxReplacements) as! Self
107+
case (let str as Substring, let other as String, let repl as Substring):
108+
return str[...]._replacingSubstring(other[...], with: repl[...],
109+
maxReplacements: maxReplacements)[...] as! Self
110+
case (let str as Substring, let other as Substring, let repl as String):
111+
return str[...]._replacingSubstring(other[...], with: repl[...],
112+
maxReplacements: maxReplacements)[...] as! Self
113+
114+
default:
115+
return _replacing(
116+
self[subrange]._ranges(of: other),
117+
with: replacement,
118+
maxReplacements: maxReplacements)
119+
}
77120
}
78121

79122
/// Returns a new collection in which all occurrences of a target sequence

Sources/_StringProcessing/Algorithms/Algorithms/Split.swift

+20-8
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,22 @@ extension Collection where Element: Equatable {
148148
maxSplits: Int = .max,
149149
omittingEmptySubsequences: Bool = true
150150
) -> [SubSequence] where C.Element == Element {
151-
Array(split(
152-
by: ZSearcher(pattern: Array(separator), by: ==),
153-
maxSplits: maxSplits,
154-
omittingEmptySubsequences: omittingEmptySubsequences))
151+
switch (self, separator) {
152+
case (let str as String, let sep as String):
153+
return str[...]._split(separator: sep, maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences) as! [SubSequence]
154+
case (let str as String, let sep as Substring):
155+
return str[...]._split(separator: sep, maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences) as! [SubSequence]
156+
case (let str as Substring, let sep as String):
157+
return str._split(separator: sep, maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences) as! [SubSequence]
158+
case (let str as Substring, let sep as Substring):
159+
return str._split(separator: sep, maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences) as! [SubSequence]
160+
161+
default:
162+
return Array(split(
163+
by: ZSearcher(pattern: Array(separator), by: ==),
164+
maxSplits: maxSplits,
165+
omittingEmptySubsequences: omittingEmptySubsequences))
166+
}
155167
}
156168
}
157169

@@ -174,8 +186,8 @@ extension StringProtocol where SubSequence == Substring {
174186
maxSplits: Int = .max,
175187
omittingEmptySubsequences: Bool = true
176188
) -> [Substring] {
177-
Array(split(
178-
by: ZSearcher(pattern: Array(separator), by: ==),
189+
Array(self[...].split(
190+
by: SubstringSearcher(text: "" as Substring, pattern: separator[...]),
179191
maxSplits: maxSplits,
180192
omittingEmptySubsequences: omittingEmptySubsequences))
181193
}
@@ -187,8 +199,8 @@ extension StringProtocol where SubSequence == Substring {
187199
maxSplits: Int = .max,
188200
omittingEmptySubsequences: Bool = true
189201
) -> [Substring] {
190-
Array(split(
191-
by: ZSearcher(pattern: Array(separator), by: ==),
202+
Array(self[...].split(
203+
by: SubstringSearcher(text: "" as Substring, pattern: separator[...]),
192204
maxSplits: maxSplits,
193205
omittingEmptySubsequences: omittingEmptySubsequences))
194206
}

0 commit comments

Comments
 (0)