Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 800
alex Avatar asked Feb 05 '23 22:02

alex


2 Answers

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)
}
like image 126
Martin R Avatar answered Mar 06 '23 23:03

Martin R


For completeness: an alternative to the while loop approach is using an AnyIterator:

let num = 1000

var i = 5
for i in AnyIterator<Int>({
    return i <= num ? { defer { i *= 5 }; return i }() : nil
}) {
    // note that we choose to shadow the external i variable name,
    // such that any access to i within this loop will only refer
    // to the loop local immutable variable i.
    print(i)

    // e.g. i += 1 not legal, i refers to a constant here!

} /* 5 
     25 
     125 
     625 */

This method suffers from the same drawback as the while loop in that the loop "external" i variable persists outside and after the scope of the loop block. This external i variable is not, however, the i variable that is accessible within the loop body, as we let the loop body variable i shadow the external one, limiting access to i within the body to the immutable, temporary (loop scope local) one.

like image 22
dfrib Avatar answered Mar 07 '23 00:03

dfrib