0

I find using String.Index really needs a lot of code in Swift, especially when it comes to methods of Range Swift doesn't have. Like in above code where I don't want an open range (both bounds exclusive). So I wonder if I can extend String or Range or something to simplify it. In the following code, I already know that startIndex and endIndex are of type String.Index, and startIndex..<endIndex is of type Range<String.Index>. But when I extend String.Index, I'd like to define a method like static func >.< (lhs: String.Index, rhs: String.Index) -> Range<String.Index>, but I failed because there's no method to step String.Index up or down.

let startIndex = str.index(str.firstIndex(of: "[") ?? str.startIndex, offsetBy: 1)
let endIndex = str.firstIndex(of: "]") ?? str.startIndex
let subStr = str[startIndex..<endIndex]

I want to define operators like below. To clarify them using interval notation, if native method a...b is equivalent to [a, b] and a..<b is equivalent to [a, b), what is the equivalent of (a, b) and (a,b].

let startIndex = str.firstIndex(of: "[") ?? str.startIndex
let endIndex = str.firstIndex(of: "]") ?? str.startIndex
let subStr1 = str[startIndex...endIndex]    // [a, b]
let subStr3 = str[startIndex..<endIndex]    // [a, b)
let subStr2 = str[startIndex>.<endIndex]    // (a, b)
let subStr4 = str[startIndex>..endIndex]    // (a, b]
Yiming Designer
  • 423
  • 3
  • 10
  • "there's no method to step String.Index up or down" Yes, there is. – matt May 15 '22 at 00:44
  • 1
    There is a good reason why strings are not subscriptable by ranges in the way you suggest. And string indexes have no meaning outside of the _particular_ string to which they are related, so you can't manipulate them in the abstract. The real question here is what _practical_ problem you're having. – matt May 15 '22 at 01:02
  • 1
    @matt For example, for String `"[10:02.11]"`, I can get the index of `"["`, `"]"` and `":"`. I want to get the result of `"10"` and `"02.11"`. So I think open range (both bounds exclusive) is a good way. But Swift doesn't have this. – Yiming Designer May 15 '22 at 01:27
  • But that's hardly a practical example. If I knew that I had substrings delimited by `"["` and `":"` and `"]"` I wouldn't use ranges to obtain them at all. I'd fetch them both from the original string in a single regular expression. – matt May 15 '22 at 02:47

1 Answers1

-1

Edit: See the second code example instead! Please disregard the first block; I didn't read the question as carefully as I should have, so it is not a relevant solution.

Edit 2: Also look at the first comment below this answer for an example of a major caveat of the first example. It does not work correctly for strings which contain emojis. Credit to Leo Dabus for this discovery.


This code might do what you're looking for, but I can't guarantee its reliability in all cases.

What it does is take a regular half-open range of integers (of the form a..<b) and a specific string (because, as matt mentioned in his comment, Swift string indices don't pertain to the class String, only to some particular string), and returns an open range of indices for that particular string.

In effect, it just adds 1 to the lower bound to change it from inclusive to exclusive.

import Foundation

// Returns a Range of 'String.Index'es where both upper and lower bounds are exclusive.
extension Range {
    // Here, the upper bound of the range was already exclusive, so don't subtract 1 from it.  Only add 1 to the lower bound.
    static func OpenRange(string: String, range: Range<Int>) -> Range<String.Index> {
        return Range<String.Index>(uncheckedBounds: (String.Index(utf16Offset: range.lowerBound + 1, in: string), String.Index(utf16Offset: range.upperBound, in: string)))
    }
}

let theString = "abcdefg"
let endIndex = theString.index(theString.startIndex, offsetBy: 5)
let range: Range<String.Index> = theString.startIndex..<endIndex

let openRange = Range<String.Index>.OpenRange(string: theString, range: 0..<5)
print(theString[range]) // prints 'abcde'
print(theString[openRange]) // prints 'bcde'

Reference: https://www.avanderlee.com/swift/ranges-explained/

The example I had above doesn't really fit the specific case you were asking about though, as I was starting from code I already had and was trying to 'adapt' it to this situation. My bad! I believe this is a better alternative:

// Returns a Range of 'String.Index'es between two specified characters in the string (as exclusive bounds)
extension Range {
    // Here, the upper bound of the range was already exclusive, so don't subtract 1 from it.  Only add 1 to the lower bound.
    static func OpenRange(string: String, from: String, to: String) -> Range<String.Index> {
        // The 'firstIndex' method expects a Character, not a String, so you need a type cast here
        let lowerInclusive = string.firstIndex(of: Character(from))!
        let lowerExclusive = string.index(lowerInclusive, offsetBy: 1)
        let upperExclusive = string.firstIndex(of: Character(to))!
        return Range<String.Index>(uncheckedBounds: (lowerExclusive, upperExclusive))
    }
}

let theString = "[10:02.11]"
let openRange1 = Range<String.Index>.OpenRange(string: theString, from: "[", to: ":")
let openRange2 = Range<String.Index>.OpenRange(string: theString, from: ":", to: "]")

print(theString[openRange1]) // prints '10'
print(theString[openRange2]) // prints '02.11'
Quack E. Duck
  • 594
  • 1
  • 4
  • 20
  • 1
    This is totally wrong. Try `let theString = "‍‍‍abc"` and `let openRange = Range.OpenRange(string: theString, range: 0..<6)`. Then `print(theString[openRange])` will result in `"‍‍"` – Leo Dabus May 15 '22 at 02:21
  • @LeoDabus Interesting! I just saw your comment after editing my post. I'm a complete beginner at Swift string index related functionality, and to me at least it seems very unintuitive compared to any other programming language I'm familiar with. Of course it's necessary to support all the extra character types Swift allows (like the emojis in your example) but it is a steep learning curve. – Quack E. Duck May 15 '22 at 02:26
  • 2
    Note that it is Swift naming convention to name your methods starting with a lowercase letter. Btw Looks like you are looking for something like [Swift Get string between 2 strings in a string](https://stackoverflow.com/a/52521742/2303865) – Leo Dabus May 15 '22 at 02:30
  • 2
    that would allow you to write something like `theString.substring(from: "[", to: ":")` // "10" and `theString.substring(from: ":", to: "]")` // "02.11" – Leo Dabus May 15 '22 at 02:34
  • 1
    @LeoDabus yes, that looks like it does exactly what OP is looking for, and I will be using your approach in the future as well. – Quack E. Duck May 15 '22 at 02:34