Search code examples
swiftstringswift-string

Getting "String.Index" while enumerating swift string


Currently we iterate string as below:

let greeting = "Hello"
for (intIndex, char) in greeting.enumerated() {
    let currentIndex = greeting.index(greeting.startIndex, offsetBy: intIndex)
    let indexAfterCurrentIndex = greeting.index(after: currentIndex)
    print(greeting[indexAfterCurrentIndex...])
}

I feel writing below code is redundant.

let currentIndex = greeting.index(greeting.startIndex, offsetBy: intIndex)

Is there other way to get directly "String.Index" while iterating?

Something like this

let greeting = "Hello"
for (stringIndex, char) in greeting.enumeratedXXX() {
    let indexAfterCurrentIndex = greeting.index(after: stringIndex)
    print(greeting[indexAfterCurrentIndex...])
}

Solution

  • There is no built-in functionality for this. You could wrap this in a custom iterator, but then you only encapsulate the same kind of computation in a different place, so that's not an answer :)

    Code Complexity

    However, you can improve performance of your current code:

    greeting.index(greeting.startIndex, offsetBy: intIndex)
    
    • This will calculate the index from startIndex to the resulting index for every loop iteration.
    • The index calculation with index(_:offsetBy:) is really just another loop itself, where it +1s each index. There's no O(1) way to "compute" the index; it is found out by a loop in O(n)

    So your own outer loop is linear with O(n) for n iterations, one for every character.

    Then computing the index with an inner loop means there are 1+2+3+4+5+6+...n = (n^2 + n)/2 iterations, where n is the intIndex in this case.

    That means the algorithm has a complexity of *handwaiving* roundabout O(n + n^2). The quadratic part is problematic!

    Better approach

    You can get the complexity down to 2 operations per iteration, or O(2n). Just keep the previously computed index in memory and +1 yourself, avoiding a recomputation from scratch.

    Here's the code:

    let greeting = "Hello"
    var index = greeting.startIndex
    for char in greeting {
        let indexAfterCurrentIndex = greeting.index(after: index)
        print(greeting[indexAfterCurrentIndex...])
        index = indexAfterCurrentIndex
    }
    

    Still not a simple and built-in solution, but you can just as well wrap this more efficient algorithm and off you go!

    extension String {
        func forEachCharacterWithIndex(iterator: (String.Index, Character) -> Void) {
            var currIndex = self.startIndex
            for char in self {
                iterator(currIndex, char)
                currIndex = self.index(after: currIndex)
            }
        }
    }
    
    let greeting = "Hello"
    greeting.forEachCharacterWithIndex { (index, char) in
        let indexAfterCurrentIndex = greeting.index(after: index)
        print(greeting[indexAfterCurrentIndex...])
    }