winstonp
(Winston)
1
Learning to use Swift, PATs and Generics. Regardless of performance, I would like to make it so in my code, I can perform string indexing like Python:
let text = "hello"
print(text[0..<2]) // "he"
I am trying to implement it as such:
import Foundation
extension String {
subscript (bounds: RangeExpression) -> String {
...
}
}
I've tried a lot of things, like:
-
subscript (bounds: RangeExpression<Int>, but this is not allowed in Swift 5.6
- define another protocol
IntRange: RangeExpression, but I did not figure out how to set its associated type
What is the right approach to this? Thanks!
tera
2
Will this work for you?
let text = "hello"
print(text[0..<2]) // "he"
print(text[0...2]) // "hel"
print(text[1...]) // "ello"
print(text[..<2]) // "he"
print(text[...2]) // "hel"
extension String {
subscript(bounds: Range<Int>) -> String {
String(self[self + bounds.lowerBound ..< self + bounds.upperBound])
}
subscript(bounds: ClosedRange<Int>) -> String {
String(self[self + bounds.lowerBound ... self + bounds.upperBound])
}
subscript(bounds: PartialRangeFrom<Int>) -> String {
String(self[self + bounds.lowerBound ..< endIndex])
}
subscript(bounds: PartialRangeUpTo<Int>) -> String {
String(self[startIndex ..< self + bounds.upperBound])
}
subscript(bounds: PartialRangeThrough<Int>) -> String {
String(self[startIndex ... self + bounds.upperBound])
}
static func + (string: Self, offset: Int) -> String.Index {
string.index(at: offset)
}
func index(at offset: Int) -> String.Index {
index(startIndex, offsetBy: offset)
}
}
BTW, you may return Substring (and only convert to String on the use side if needed) although if performance doesn't matter then returning String is fine.
1 Like
winstonp
(Winston)
3
Awesome, thanks! Out of curiosity, what exactly is the purpose of RangeExpression then?
AlexanderM
(Alexander Momchilov)
5
It's very intentionally omitted.
While the ergonomics aren't great as far as peoples usual expectations, indexing strings is actually a surprisingly uncommon operation in real world code (i.e. not a "shuffle the characters of a string" style code challenge). Most algorithms are better expressed in terms of higher level concepts like slicing, joining, taking prefixes, dropping prefixes, etc.
7 Likes
tera
6
Remember that index(_: String.Index, offsetBy:) is O(n) operation. If you do it in a loop for all characters in the string - you have a quadratic algorithm.
We can have it in a form:
text[slow: 0...2]
"slow" should discourage people from using it in real world apps.
I am not sure
. Will leave for others to comment upon.
3 Likes
RangeExpression serves as an abstraction over all the range types in @tera’s example code.
instead of providing overloads for Range, ClosedRange, PartialRangeFrom, PartialRangeUpTo, and PartialRangeThrough, you could just provide one generic RangeExpression method.
1 Like
tera
8
Thank you, indeed:
let text = "hello"
print(text[slow: 0..<2]) // "he"
print(text[slow: 0...2]) // "hel"
print(text[slow: 1...]) // "ello"
print(text[slow: ..<2]) // "he"
print(text[slow: ...2]) // "hel"
extension String {
subscript<T: RangeExpression>(slow bounds: T) -> String where T.Bound == Int {
let range = bounds.relative(to: 0 ..< .max)
let left = index(startIndex, offsetBy: range.lowerBound)
let right = range.upperBound == .max ? endIndex : index(startIndex, offsetBy: range.upperBound)
return String(self[left ..< right])
}
}
1 Like