Is there nice and easy way to iterate through Range<String.Index>?

So for-in and .forEach() do not work on Range<String.Index> and I can see why: it's because it needs the original string to move:

let s = "xxxxxxx12345yyyyy"

let a = s.index(s.startIndex, offsetBy: 7)
let b = s.index(s.endIndex, offsetBy: -5)
let r = a ..< b

// how to iterate through r?

// for-in and .forEach() do not work. I see why: because the original string is needed to move
//for i in r {    // Protocol 'Sequence' requires that 'String.Index' conform to 'Strideable'
//    print(i)
//}
//
//r.forEach {     // Referencing instance method 'forEach' on 'Range' requires that 'String.Index' conform to 'Strideable'
//    print($0)
//}

// this works, but it's clumsy
var i = a
while i < b {
    print(s[i])
    i = s.index(after: i)
}


// does something like this already exist?
struct StringIndexRangeIterator: Sequence, IteratorProtocol {
    private let string: String
    private var at: String.Index
    private let end: String.Index

    init(_ s: String, range: Range<String.Index>) {
        string = s
        at = range.lowerBound
        end = range.upperBound
    }

    mutating func next() -> String.Index? {
        if at >= end {
            return nil
        } else {
            defer { at = string.index(after: at) }
            return at
        }
    }
}

extension String {
    func iterator(of range: Range<String.Index>) -> StringIndexRangeIterator {
        StringIndexRangeIterator(self, range: range)
    }
}

// now it's nice and easy to iterate
for i in s.iterator(of: r) {
    print(s[i])
}

I hope this helps but might not be what you want.

let s = "xxxxxxx12345yyyyy"

let a = s.index(s.startIndex, offsetBy: 7)

let b = s.index(s.endIndex, offsetBy: -5)

let r = s[a ..< b]

for each in r {
    print(each)
}

Yes:

let s = "xxxxxxx12345yyyyy"

let a = s.index(s.startIndex, offsetBy: 7)
let b = s.index(s.endIndex, offsetBy: -5)
let r = a ..< b

// or just s[r] if you don't need the index itself, as @masters3d said above.
for i in s.indices[r] {
    print("index \(i) has char \(s[i]) ")
}
2 Likes

I would have gone with s[r].indices rather than s.indices[r]. In String's case they're going to work, but it's not required that Self.Index and Self.Indices.Index are the same type, or that they line up, whereas Self.Index and Self.SubSequence.Index are guaranteed to be interchangeable.

2 Likes

:+1: perfect! I'm going with @jrose s[r].indices

@masters3d : I need to scan through the indices, not the String slice.

I noticed that (with Xcode 12.5) this

extension Collection where Self.Index == Self.Indices.Index {
}

compiles without any warning, as implied by your statement, but this:

struct S<T: Collection> where T.Index == T.Indices.Index {
}

results in a warning

"Redundant same-type constraint `T.Index` == `T.Indices.Index`"

Why is this?

1 Like

Oops, I’m incorrect! I was looking under Indices for the constraint, but it’s under Index instead. (Along with a bunch of other redundant constraints.) I do wonder why the extension and the struct have different behavior here; that seems like a bug.

Filed SR-14917

1 Like