4

...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

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
alex
  • 957
  • 2
  • 9
  • 16

2 Answers2

6

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)
}
Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • As a horrible alternative (horrible even disregarding lack of `Double` to `Int` bounds checks) to the help function above, until Swift 3.1, one could also use `sequence(first: 5, next: { $0 * 5 }).prefix(Int(log(Double(num))/log(5)))` ;) – dfrib Oct 16 '16 at 14:40
  • @dfri: ... and possible rounding errors. – I really think that the upcoming `prefix(while:)` and `drop(while:)` will solve this and similar problems nicely. – Martin R Oct 16 '16 at 14:41
  • Indeed. Jokes aside, `sequence(first: 5, next: { num/$0 > 0 ? $0 * 5 : nil }).dropLast()` should be a valid alternative, making use of the `Optional`-return of the `next` closure in `sequence(first: T, next: T -> T?)`. – dfrib Oct 16 '16 at 14:46
  • @dfri: Actually in this particular case it should be `for i in sequence(first: 5, next: { $0 <= num/5 ? $0 * 5 : nil })` in order to safely avoid an integer overflow. – Martin R Oct 16 '16 at 15:04
  • The problem with that variation is that (as I noted you've already written as a disadvantage in your linked answer) that we'll always visit at least one element, even if it breaks the invariant (which of course is not an issue if `num >= 5`). But do we really have an integer overflow issue with the `dropLast()` variation, that is not present in the other cases? The last integer calculated for any given `num` will be `5` times the magnitude of the last used value of `i`. But this value is also present e.g. in `state` in the helper above, for the last `nil`-returning pass. – dfrib Oct 16 '16 at 15:12
  • (... unless the ternary operator always evaluates both possible states, but I would assume that it, much like an if-else block, only the one its conditionally directed to) – dfrib Oct 16 '16 at 15:14
  • @dfri: You are right that my initially proposed solutions suffer from the same problem in this use-case – as does your "dropLast" version from above comment, e.g. for `num = Int.max`. – Martin R Oct 16 '16 at 15:18
  • Indeed, the corner case will be present for all variations that make use of `$0 * 5` as a termination check. Just wanted to make sure the `dropLast()` didn't suffer from the `25` time extra magnitude that my initial `AnyIterator` solution had (which would be the case if the ternary operator `conditional ? a : b` always evaluated `a` even for `conditional = false`, but I believe this is not the case. Deleted my prior comment I started after the ninja-edit of your previous comment w.r.t. the conditional operator :). Anyway, thanks for the feedback! – dfrib Oct 16 '16 at 15:33
2

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.

dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Note that this updates the loop variable once too often (i.e. even when returning `nil`). That could cause an integer overflow in this application, and might be problematic in other use cases (such as traversing a linked list). – Martin R Oct 16 '16 at 13:55
  • @MartinR Ah yes I noted the extra increase in my answer, but I didn't realize the possible dangers it implicated. Updated with a version at least analogous to the `while` loop, w.r.t. the value of the external `i` after loop completion. Thanks! – dfrib Oct 16 '16 at 14:05