alex alex - 1 month ago 12
Swift Question

Express for loops in swift with dynamic range

...or how can I use the index inside the for loop condition

Hey people
Since we're left with no c style for loops in swift 3 I can't seem to find a way to express a bit more complex for loops so maybe you can help me out.

If I were to write this

for(int i=5; num/i > 0; i*=5)


in swift 3 how would I do that?

The closes I came by was:

for i in stride(from: 5, through: num, by: 5) where num/i > 0


but this will of course iterate 5 chunks at a time instead if i being: 5, 25, 125 etc.

Any ideas?

Thanks

Answer

Using a helper function (originally defined at Converting a C-style for loop that uses division for the step to Swift 3)

public func sequence<T>(first: T, while condition: @escaping (T)-> Bool, next: @escaping (T) -> T) -> UnfoldSequence<T, T> {
    let nextState = { (state: inout T) -> T? in
        // Return `nil` if condition is no longer satisfied:
        guard condition(state) else { return nil }
        // Update current value _after_ returning from this call:
        defer { state = next(state) }
        // Return current value:
        return state
    }
    return sequence(state: first, next: nextState)
}

you can write the loop as

let num = 1000
for i in sequence(first: 5, while: { num/$0 > 0 }, next: { $0 * 5 }) {
    print(i)
}

A simpler solution would be a while-loop:

var i = 5
while num/i > 0 {
    print(i)
    i *= 5
}

but the advantage of the first solution is that the scope of the loop variable is limited to the loop body, and that the loop variable is a constant.

Swift 3.1 will provide a prefix(while:) method for sequences, and then the helper function is no longer necessary:

let num = 1000
for i in sequence(first: 5, next: { $0 * 5 }).prefix(while: { num/$0 > 0 }) {
    print(i)
}

All of above solutions are "equivalent" to the given C loop. However, they all can crash if num is close to Int.max and $0 * 5 overflows. If that is an issue then you have to check if $0 * 5 fits in the integer range before doing the multiplication.

Actually that makes the loop simpler – at least if we assume that num >= 5 so that the loop is executed at least once:

for i in sequence(first: 5, next: { $0 <= num/5  ? $0 * 5 : nil }) {
    print(i)
}