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 protocol
s, 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