-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added
longestPrefx(where:)
on RandomAccessCollection
.
- Loading branch information
1 parent
7cbcc2d
commit 1c03b69
Showing
3 changed files
with
84 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// Created by Wade Tregaskis on 2024-03-11. | ||
|
||
extension RandomAccessCollection { | ||
/// A combination of finding the longest prefix for some condition and transforming that prefix. | ||
/// | ||
/// Combining the transformation with the prefix search is an efficiency improvement for some use-cases, as it avoids duplicate work in having to repeat the transformation on the winning prefix. | ||
/// | ||
/// The transformation step is technically optional - you can return the closure's argument (the candidate prefix) to degenerate this into simply returning the longest matching prefix. | ||
/// | ||
/// - Parameter transform: The transform to apply, returning a value if successful or nil otherwise. This _must_ have a single transition point (where it goes from returning values to returning nil, for increasingly long prefixes), else the result is undefined. | ||
/// - Returns: The result of a transformation closure on the longest prefix for which the transform succeeds, or nil if there is no [non-empty] prefix for which this is the case. | ||
func longestPrefix<T>(where transform: (SubSequence) throws -> T?) rethrows -> T? { | ||
var lowerBound = self.startIndex | ||
var lowerBoundResult: T? = nil | ||
var upperBound = self.endIndex | ||
|
||
var currentGuess = self.index(lowerBound, offsetBy: self.distance(from: lowerBound, to: upperBound) / 2) | ||
|
||
while lowerBound != upperBound { | ||
let prefix = self[..<currentGuess] | ||
|
||
if let result = try transform(prefix) { | ||
let distance = self.distance(from: currentGuess, to: upperBound) | ||
|
||
guard 0 < distance else { | ||
return result | ||
} | ||
|
||
lowerBound = currentGuess | ||
lowerBoundResult = result | ||
self.formIndex(¤tGuess, offsetBy: (distance + 1) / 2) | ||
} else { | ||
let distance = self.distance(from: lowerBound, to: currentGuess) | ||
|
||
guard 0 < distance else { | ||
return lowerBoundResult | ||
} | ||
|
||
upperBound = self.index(before: currentGuess) | ||
self.formIndex(¤tGuess, offsetBy: -((distance + 1) / 2)) | ||
} | ||
} | ||
|
||
return lowerBoundResult | ||
} | ||
} |
34 changes: 34 additions & 0 deletions
34
Tests/FoundationExtensionsTests/RandomAccessCollectionTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// Created by Wade Tregaskis on 2024-03-11. | ||
|
||
import XCTest | ||
@testable import FoundationExtensions | ||
|
||
|
||
final class RandomAccessCollectionTests: XCTestCase { | ||
func testLongestPrefix() throws { | ||
XCTAssertNil([].longestPrefix(where: { _ in true })) | ||
|
||
for array in [[1], | ||
[1, 1], | ||
[1, 1, 2], | ||
[1, 1, 2, 3, 5, 8, 13]] { | ||
XCTAssertEqual(array.longestPrefix(where: { $0 }), | ||
array[...]) | ||
XCTAssertNil(array.longestPrefix(where: { _ in nil })) | ||
|
||
for length in 1..<array.count { | ||
let targetPrefix = array[0..<length] | ||
|
||
XCTAssertEqual(array.longestPrefix(where: { targetPrefix.starts(with: $0) ? $0 : nil }), | ||
targetPrefix) | ||
XCTAssertEqual(array.longestPrefix(where: { targetPrefix.starts(with: $0) ? length : nil }), | ||
length) | ||
|
||
XCTAssertEqual(array.longestPrefix(where: { $0.count <= length ? $0 : nil }), | ||
targetPrefix) | ||
XCTAssertEqual(array.longestPrefix(where: { $0.count <= length ? length : nil }), | ||
length) | ||
} | ||
} | ||
} | ||
} |