4
\$\begingroup\$

Since the deprecation of C-style for loops in Swift 2.2 (and the removal in Swift 3), several questions about the possible replacements were asked on Stack Overflow. In most cases, a for .. in loop is the appropriate replacement:

for i in 1 ... 5 { /* ... */ }
for j in 10.stride(through: 0, by: -2) { /* ... */ }
for elem in array.reverse() { /* ... */ }

and similar. However, there are cases where this pattern cannot be used:

  • The stride (advance) is not constant.
  • The terminating condition is "dynamic" (depends on the loop variable), as in Swift replacement for C's for-loop.
  • The loop variable is not a strideable type (e.g. a linked list).

In all these cases, the C-style for loop can be replaced by a while or repeat/while statement:

var iterationVariable = "initial value"
while "condition" {
    //
    //  do something
    //

    iterationVariable = "compute next value"
}

However, this has some disadvantages (and I cannot formulate it better than @nhgrif did in Swift replacement for C's for-loop):

And the biggest problem with the while loop which I'd like reviewed is that the divisor variable has a scope larger than I'd like, and the update statement doesn't come until the very end of the while loop (a problem that becomes more complex if your loop has any continue statements in it anywhere that allow iterations to break out early and jump to the next iteration).

For my own educational purposes, I wrote the following general iteration functions which can be used as a C-style for loop replacement in such cases (requires Swift 2.2/Xcode 7.3):

func iterate<T>(from from : T, while condition : T -> Bool, next : T -> T) -> AnySequence<T> {

    var current = from
    let condition = condition
    let next = next

    return AnySequence { 
        return AnyGenerator {
            if !condition(current) {
                return nil
            }
            defer {
                current = next(current)
            }
            return current
        }
    }
}

func iterate<T>(from from : T, until condition : T -> Bool, next : T -> T) -> AnySequence<T> {

    var current = from
    let condition = condition
    let next = next
    var done = false

    return AnySequence {
        return AnyGenerator {
            if done {
                return nil
            } else if condition(current) {
                done = true
                return current
            } else {
                defer {
                    current = next(current)
                }
                return current
            }
        }
    }
}

A simple usage example is

for i in iterate(from: 1, while: { $0 * $0 < 100 } , next: { 2 * $0 }) {
    print(i, terminator: ", ")
}
// Output: 1, 2, 4, 8,

In contrast to the while loop, the scope of the loop variable is exactly the loop body, and the syntax is remotely similar to the C-style for loop. But in contrast to the C-style for loop, the loop variable is a constant within the body.

The return value of the iterate() functions is a SequenceType, so you can create an array from it directly. The following example computes the "3n+1 sequence" for a given integer:

func collatz(n : Int) -> Int {
    return n % 2 == 0 ? n / 2 : 3 * n + 1
}

func collatzValues(n : Int) -> [Int]{
    return Array(iterate(from: n, until: { $0 == 1 } , next: collatz))
}

print(collatzValues(13)) // [13, 40, 20, 10, 5, 16, 8, 4, 2, 1]

And finally an example where you iterate over a linked list of network interfaces (compare How to get Ip address in swift):

var ifaddr : UnsafeMutablePointer<ifaddrs> = nil
if getifaddrs(&ifaddr) == 0 {
    for ptr in iterate(from: ifaddr, while: { $0 != nil }, next: { $0.memory.ifa_next }) {
        let addr = ptr.memory.ifa_addr.memory
        // ...
        // Do something with this sockaddr 
        // ...
    }
    freeifaddrs(ifaddr)
}

To compile this example,

#include <ifaddrs.h>

must be added to the bridging header file.

What I would like is a review on all aspects of the two iterate() functions (the examples were just added to demonstrate the usage), including but of course not limited to:

  • naming of function, parameters, variables,
  • Swifty-ness of the code, possible simplifications,
  • possible performance improvements (iterating over the sequences created by the iterate() functions is much slower than the equivalent while loop).

In addition, any thoughts on the usefulness of those functions are welcome.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.