Skip to content

[6.0] Add a string-specific search algorithm #748

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 41 additions & 5 deletions Sources/_StringProcessing/Algorithms/Algorithms/FirstRange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,23 @@ extension Collection {
}

// MARK: Fixed pattern algorithms
extension Substring {
func _firstRangeSubstring(
of other: Substring
) -> Range<String.Index>? {
var searcher = SubstringSearcher(text: self, pattern: other)
return searcher.next()
}
}

extension Collection where Element: Equatable {
func _firstRangeGeneric<C: Collection>(
of other: C
) -> Range<Index>? where C.Element == Element {
let searcher = ZSearcher<SubSequence>(pattern: Array(other), by: ==)
return searcher.search(self[...], in: startIndex..<endIndex)
}

/// Finds and returns the range of the first occurrence of a given collection
/// within this collection.
///
Expand All @@ -33,9 +48,19 @@ extension Collection where Element: Equatable {
public func firstRange<C: Collection>(
of other: C
) -> Range<Index>? where C.Element == Element {
// TODO: Use a more efficient search algorithm
let searcher = ZSearcher<SubSequence>(pattern: Array(other), by: ==)
return searcher.search(self[...], in: startIndex..<endIndex)
switch (self, other) {
case (let str as String, let other as String):
return str[...]._firstRangeSubstring(of: other[...]) as! Range<Index>?
case (let str as Substring, let other as String):
return str._firstRangeSubstring(of: other[...]) as! Range<Index>?
case (let str as String, let other as Substring):
return str[...]._firstRangeSubstring(of: other) as! Range<Index>?
case (let str as Substring, let other as Substring):
return str._firstRangeSubstring(of: other) as! Range<Index>?

default:
return _firstRangeGeneric(of: other)
}
}
}

Expand All @@ -50,8 +75,19 @@ extension BidirectionalCollection where Element: Comparable {
public func firstRange<C: Collection>(
of other: C
) -> Range<Index>? where C.Element == Element {
let searcher = ZSearcher<SubSequence>(pattern: Array(other), by: ==)
return searcher.search(self[...], in: startIndex..<endIndex)
switch (self, other) {
case (let str as String, let other as String):
return str[...]._firstRangeSubstring(of: other[...]) as! Range<Index>?
case (let str as Substring, let other as String):
return str._firstRangeSubstring(of: other[...]) as! Range<Index>?
case (let str as String, let other as Substring):
return str[...]._firstRangeSubstring(of: other) as! Range<Index>?
case (let str as Substring, let other as Substring):
return str._firstRangeSubstring(of: other) as! Range<Index>?

default:
return _firstRangeGeneric(of: other)
}
}
}

Expand Down
16 changes: 14 additions & 2 deletions Sources/_StringProcessing/Algorithms/Algorithms/Ranges.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ extension Collection where Element: Equatable {
) -> RangesCollection<ZSearcher<Self>> where C.Element == Element {
_ranges(of: ZSearcher(pattern: Array(other), by: ==))
}

// FIXME: Return `some Collection<Range<Index>>` for SE-0346
/// Finds and returns the ranges of the all occurrences of a given sequence
/// within the collection.
Expand All @@ -146,7 +146,19 @@ extension Collection where Element: Equatable {
public func ranges<C: Collection>(
of other: C
) -> [Range<Index>] where C.Element == Element {
Array(_ranges(of: other))
switch (self, other) {
case (let str as String, let other as String):
return Array(SubstringSearcher(text: str[...], pattern: other[...])) as! [Range<Index>]
case (let str as Substring, let other as String):
return Array(SubstringSearcher(text: str, pattern: other[...])) as! [Range<Index>]
case (let str as String, let other as Substring):
return Array(SubstringSearcher(text: str[...], pattern: other)) as! [Range<Index>]
case (let str as Substring, let other as Substring):
return Array(SubstringSearcher(text: str, pattern: other)) as! [Range<Index>]

default:
return Array(_ranges(of: other))
}
}
}

Expand Down
77 changes: 60 additions & 17 deletions Sources/_StringProcessing/Algorithms/Algorithms/Replace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,32 @@

// MARK: `CollectionSearcher` algorithms

extension Substring {
func _replacingSubstring(
_ other: Substring,
with replacement: Substring,
maxReplacements: Int = .max
) -> String {
precondition(maxReplacements >= 0)

var result = String()
var index = startIndex

var rangeIterator = SubstringSearcher(text: self, pattern: other)
var replacementCount = 0
while replacementCount < maxReplacements, let range = rangeIterator.next() {
result.append(contentsOf: self[index..<range.lowerBound])
result.append(contentsOf: replacement)
index = range.upperBound

replacementCount += 1
}

result.append(contentsOf: self[index...])
return result
}
}

extension RangeReplaceableCollection {
func _replacing<Ranges: Collection, Replacement: Collection>(
_ ranges: Ranges,
Expand All @@ -35,19 +61,6 @@ extension RangeReplaceableCollection {
result.append(contentsOf: self[index...])
return result
}

mutating func _replace<
Ranges: Collection, Replacement: Collection
>(
_ ranges: Ranges,
with replacement: Replacement,
maxReplacements: Int = .max
) where Ranges.Element == Range<Index>, Replacement.Element == Element {
self = _replacing(
ranges,
with: replacement,
maxReplacements: maxReplacements)
}
}

// MARK: Fixed pattern algorithms
Expand All @@ -70,10 +83,40 @@ extension RangeReplaceableCollection where Element: Equatable {
subrange: Range<Index>,
maxReplacements: Int = .max
) -> Self where C.Element == Element, Replacement.Element == Element {
_replacing(
self[subrange]._ranges(of: other),
with: replacement,
maxReplacements: maxReplacements)
switch (self, other, replacement) {
case (let str as String, let other as String, let repl as String):
return str[...]._replacingSubstring(other[...], with: repl[...],
maxReplacements: maxReplacements) as! Self
case (let str as Substring, let other as Substring, let repl as Substring):
return str._replacingSubstring(other, with: repl,
maxReplacements: maxReplacements)[...] as! Self

case (let str as Substring, let other as String, let repl as String):
return str[...]._replacingSubstring(other[...], with: repl[...],
maxReplacements: maxReplacements)[...] as! Self
case (let str as String, let other as Substring, let repl as String):
return str[...]._replacingSubstring(other[...], with: repl[...],
maxReplacements: maxReplacements) as! Self
case (let str as String, let other as String, let repl as Substring):
return str[...]._replacingSubstring(other[...], with: repl[...],
maxReplacements: maxReplacements) as! Self

case (let str as String, let other as Substring, let repl as Substring):
return str[...]._replacingSubstring(other[...], with: repl[...],
maxReplacements: maxReplacements) as! Self
case (let str as Substring, let other as String, let repl as Substring):
return str[...]._replacingSubstring(other[...], with: repl[...],
maxReplacements: maxReplacements)[...] as! Self
case (let str as Substring, let other as Substring, let repl as String):
return str[...]._replacingSubstring(other[...], with: repl[...],
maxReplacements: maxReplacements)[...] as! Self

default:
return _replacing(
self[subrange]._ranges(of: other),
with: replacement,
maxReplacements: maxReplacements)
}
}

/// Returns a new collection in which all occurrences of a target sequence
Expand Down
28 changes: 20 additions & 8 deletions Sources/_StringProcessing/Algorithms/Algorithms/Split.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,22 @@ extension Collection where Element: Equatable {
maxSplits: Int = .max,
omittingEmptySubsequences: Bool = true
) -> [SubSequence] where C.Element == Element {
Array(split(
by: ZSearcher(pattern: Array(separator), by: ==),
maxSplits: maxSplits,
omittingEmptySubsequences: omittingEmptySubsequences))
switch (self, separator) {
case (let str as String, let sep as String):
return str[...]._split(separator: sep, maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences) as! [SubSequence]
case (let str as String, let sep as Substring):
return str[...]._split(separator: sep, maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences) as! [SubSequence]
case (let str as Substring, let sep as String):
return str._split(separator: sep, maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences) as! [SubSequence]
case (let str as Substring, let sep as Substring):
return str._split(separator: sep, maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences) as! [SubSequence]

default:
return Array(split(
by: ZSearcher(pattern: Array(separator), by: ==),
maxSplits: maxSplits,
omittingEmptySubsequences: omittingEmptySubsequences))
}
}
}

Expand All @@ -174,8 +186,8 @@ extension StringProtocol where SubSequence == Substring {
maxSplits: Int = .max,
omittingEmptySubsequences: Bool = true
) -> [Substring] {
Array(split(
by: ZSearcher(pattern: Array(separator), by: ==),
Array(self[...].split(
by: SubstringSearcher(text: "" as Substring, pattern: separator[...]),
maxSplits: maxSplits,
omittingEmptySubsequences: omittingEmptySubsequences))
}
Expand All @@ -187,8 +199,8 @@ extension StringProtocol where SubSequence == Substring {
maxSplits: Int = .max,
omittingEmptySubsequences: Bool = true
) -> [Substring] {
Array(split(
by: ZSearcher(pattern: Array(separator), by: ==),
Array(self[...].split(
by: SubstringSearcher(text: "" as Substring, pattern: separator[...]),
maxSplits: maxSplits,
omittingEmptySubsequences: omittingEmptySubsequences))
}
Expand Down
Loading