My algorithm needs to loop up from or down to zero in an unsigned integer type, and might stop the loop on a condition. To avoid duplication, I want to have only one version of the content of the loop. Ideally, that would look like the following:

func useRanges(_ max: UInt, _ up: Bool, _ stop: UInt) {
    let range = up ? (0 ... max) : (0 ... max).reversed()
    var result = ""
    for index in range {
        result += "\(index)"
        if index == stop { break }
    }
    print("\(#function)(\(max), \(up), \(stop)) = \(result)")
}

useRanges(5, true , 6) // should print 012345
useRanges(5, false, 6) // should print 543210
useRanges(5, true , 3) // should print 0123
useRanges(5, false, 3) // should print 543

Unfortunately, the Swift 4.2 compiler will not accept this, claiming mismatching types 'ClosedRange<UInt>' and 'ReversedCollection<(ClosedRange<UInt>)>'.

Is there another elegant and efficient way of expressing a reversible count from or to zero in an unsigned type?

Obviously I simplified the content of the loop for this example. You may paste the code above and below into a playground to try yourself.

The following variants work, but are ugly or consume too many resources. I also experimented with foreach loops and protocols, but could not find anything elegant that works.

/// elegant but slow because of array creation and reversal
func useArrays(_ max: UInt, _ up: Bool, _ stop: UInt) {
    var array = Array(0 ... max)
    if !up { array.reverse() }
    var result = ""
    for index in array {
        result += "\(index)"
        if index == stop { break }
    }
    print("\(#function)(\(max), \(up), \(stop)) = \(result)")
}

useArrays(5, true , 6) // prints 012345
useArrays(5, false, 6) // prints 543210
useArrays(5, true , 3) // prints 0123
useArrays(5, false, 3) // prints 543

/// works, but ugly, as strides do no include the "to" value
func useStrides(_ max: UInt, _ up: Bool, _ stop: UInt) {
    let str = up ? stride(from: 1, to: max + 2, by: 1) : stride(from: max + 1, to: 0, by: -1)
    var result = ""
    for incrementedIndex in str {
        let index = incrementedIndex - 1
        result += "\(index)"
        if index == stop { break }
    }
    print("\(#function)(\(max), \(up), \(stop)) = \(result)")
}

useStrides(5, true, 6)  // prints 012345
useStrides(5, false, 6) // prints 543210
useStrides(5, true, 3)  // prints 0123
useStrides(5, false, 3) // prints 543

/// works, but duplicates code
func useSeparateRanges(_ max: UInt, _ up: Bool, _ stop: UInt) {
    var result = ""
    if up {
        for index in 0 ... max {
            result += "\(index)"
            if index == stop { break }
        }
    } else {
        for index in (0 ... max).reversed() {
            result += "\(index)"
            if index == stop { break }
        }
    }
    print("\(#function)(\(max), \(up), \(stop)) = \(result)")
}

useSeparateRanges(5, true , 6) // prints 012345
useSeparateRanges(5, false, 6) // prints 543210
useSeparateRanges(5, true , 3) // prints 0123
useSeparateRanges(5, false, 3) // prints 543

/// works, with ugly type conversions
func useSignedStrides(_ max: UInt, _ up: Bool, _ stop: UInt) {
    let str = up ? stride(from: 0, to: Int(max) + 1, by: 1) : stride(from: Int(max), to: -1, by: -1)
    var result = ""
    for index in str {
        result += "\(UInt(index))"
        if index == stop { break }
    }
    print("\(#function)(\(max), \(up), \(stop)) = \(result)")
}

useSignedStrides(5, true , 6) // prints 012345
useSignedStrides(5, false, 6) // prints 543210
useSignedStrides(5, true , 3) // prints 0123
useSignedStrides(5, false, 3) // prints 543

/// works, with ugly loop breaking
func useWhile(_ max: UInt, _ up: Bool, _ stop: UInt) {
    var index = up ? 0 : max
    var result = ""
    while index != max + 1 {
        result += "\(index)"
        if index == stop { break }
        if up { index += 1 }
        else if index == 0 { break }
        else { index -= 1 }
    }
    print("\(#function)(\(max), \(up), \(stop)) = \(result)")
}

useWhile(5, true,  6) // prints 012345
useWhile(5, false, 6) // prints 543210
useWhile(5, true , 3) // prints 0123
useWhile(5, false, 3) // prints 543

/// works, but with ugly side effect in inner function
func useSideEffect(_ max: UInt, _ up: Bool, _ stop: UInt) {
    var result = ""
    
    func testWithSideEffect(_ index: UInt) -> Bool {
        result += "\(index)"
        return index == stop
    }
    
    if up {
        for index in 0 ... max {
            if testWithSideEffect(index) { break }
        }
    } else {
        for index in (0 ... max).reversed() {
            if testWithSideEffect(index) { break }
        }
    }
    print("\(#function)(\(max), \(up), \(stop)) = \(result)")
}

useSideEffect(5, true,  6) // prints 012345
useSideEffect(5, false, 6) // prints 543210
useSideEffect(5, true , 3) // prints 0123
useSideEffect(5, false, 3) // prints 543

The simplest way currently is probably to directly address the type mismatch by erasing the types

let range = up ? AnySequence((0 ... max)) : AnySequence((0 ... max).reversed())

so range now has type AnySequence<UInt>. Or use one of the Any*Collection type erasers if you need collection methods and not just sequence methods.

There is also a version of the stride function that includes the upper bound: stride(from:through:by:). It looks like this does what you want:

let range = up ? stride(from: 0, through: max, by: 1) : stride(from: max, through: 0, by: -1)

Test code:

func useStrideThrough(_ max: UInt, _ up: Bool, _ stop: UInt) {
    let range = up ? stride(from: 0, through: max, by: 1) : stride(from: max, through: 0, by: -1)
    var result = ""
    for index in range {
        result += "\(index)"
        if index == stop { break }
    }
    print("\(#function)(\(max), \(up), \(stop)) = \(result)")
}

useStrideThrough(5, true , 6) // 012345
useStrideThrough(5, false, 6) // 543210
useStrideThrough(5, true , 3) // 0123
useStrideThrough(5, false, 3) // 543
4 Likes

Elegant and fast, that's what I was looking for! I noticed stride's brother, but didn't figure out the difference. Thanks a lot!

Elegant and faster than Array, but slower than Ole's stride through solution below. Thanks a lot!