Iām not an expert in the field, but my understanding is that the two-way algorithm is held in high regard.
ā¢ ā¢ ā¢
If this functionality were ever added to Swift, I suspect it would be written as a method on Collection, with a signature like so:
extension Collection where Element: Equatable {
func firstRange<C: Collection>(of target: C) -> Range<Index>?
where C.Element == Element
{
// Insert implementation here
}
}
For optimizability, it would probably also be a protocol requirement of Collection, so that eg. BidirectionalCollection could provide its own implementation.
I found that @SDGGiesbrecht's solution is nice.
As for the pitfalls, if I keep using value of type String.Index for the as: argument, will it be all good? (Integer value cannot be passed as argument type String.Index, right?)
var s = "This also is a string"
var sep = "is"
var i1 = s.firstIndex(of: sep, at: s.startIndex)
if var i1 = i1 {
var i2 = s.firstIndex(of: sep, at: s.index(after: i1))
if var i2 = i2 {
print("'\(s)'")
print("'\(s[i1...i2])'")
for _ in sep {
i1 = s.index(after: i1)
}
i2 = s.index(before: i2)
print("'\(s[i1...i2])'")
print("'\(s[i1...i2].trimmingCharacters(in: .whitespacesAndNewlines))'")
}
}
Yes, that will work, and it does not stumble into any of the repeated integer conversion pitfalls I was talking about.
(Caveat: At this point I am assuming you can guarantee sep will never be "", in which case I donāt know off the top of my head what will happen when it reaches range(of:) in the extension method.)
Three parts of it might still be doing slightly more work than necessary.
Twice you have used this pattern (with i1 and with i2):
var x = y()
if var x = x {
// ...
Those two var declarations create two separate variables, even though one shadows the other. (The compiler is probably even warning you that the first one is never changed and could be switched to a let.) You can compress that pattern directly into this:
if var x = y() {
// ...
That way you are only storing one variable.
The innermost loop...
for _ in sep {
i1 = s.index(after: i1)
}
...could be reduced to...
i1 = s.index(i1, offsetBy: sep.count)
(count must be doing a similar loop of some form under the hood, but it might have inside information allowing it to do so without the overhead of dispatching to the index(after:) method in each iteration.)
The conversion to an open range...
i2 = s.index(before: i2)
print("'\(s[i1...i2])'")
...could be simplified by just using a closed range directly:
If we're doing a code review I'll point out that one of Swift's design principles is Fluency. range(of: of) isn't very fluent IMO. Same with [at...]. I recommend you add additional variable labels here. Something like
But anyways, thereās a whole series of operations youād want to do before or after a certain point in a string (capitalizing, lower casing, sorting, searching, replacing, etc.). Thereās no point in bloating each API with after: Index parameters.
Instead, you can compose orthogonal components like slicing (to pick which part or act on) and a normal API like firstIndex(of:)