Loop up from or down to zero in an unsigned integer type

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!