iosswiftalgorithmfibonacciiterator-protocol

How to detect the first run of an IteratorProtocol in swift?


Trying to detect the first run of an Iterator protocol. In the example below I'm trying to start printing Fibonacci series from Zero but it starts from One:

class FibIterator : IteratorProtocol {
var (a, b) = (0, 1)

func next() -> Int? {
    (a, b) = (b, a + b)
    return a
}
}

let fibs = AnySequence{FibIterator()}

print(Array(fibs.prefix(10)))

What modifications can be made to the above code to detect the first run?


Solution

  • To answer your verbatim question: You can add a boolean variable firstRun to detect the first call of the next() method:

    class FibIterator : IteratorProtocol {
        var firstRun = true
        var (a, b) = (0, 1)
    
        func next() -> Int? {
            if firstRun {
                firstRun = false
                return 0
            }
            (a, b) = (b, a + b)
            return a
        }
    }
    
    let fibs = AnySequence { FibIterator() }
    print(Array(fibs.prefix(10))) // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
    

    But there are more elegant solutions for this problem. You can “defer” the update of a and b to be done after returning the current value:

    class FibIterator : IteratorProtocol {
        var (a, b) = (0, 1)
    
        func next() -> Int? {
            defer { (a, b) = (b, a + b) }
            return a
        }
    }
    
    let fibs = AnySequence { FibIterator() }
    print(Array(fibs.prefix(10))) // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
    

    or – perhaps simpler – change the initial values (using the fact that the Fibonacci numbers are defined for negative indices as well):

    class FibIterator : IteratorProtocol {
        var (a, b) = (1, 0)  // (Fib(-1), Fib(0))
    
        func next() -> Int? {
            (a, b) = (b, a + b)
            return a
        }
    }
    
    let fibs = AnySequence { FibIterator() }
    print(Array(fibs.prefix(10))) // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
    

    Note that if you declare conformance to the Sequence protocol then you don't need the AnySequence wrapper (there is a default implementation of makeIterator() for types conforming to IteratorProtocol). Also value types are generally preferred, so – unless the reference semantics is needed – you can make it a struct:

    struct FibSequence : Sequence, IteratorProtocol {
        var (a, b) = (1, 0)  // (Fib(-1), Fib(0))
    
        mutating func next() -> Int? {
            (a, b) = (b, a + b)
            return a
        }
    }
    
    let fibs = FibSequence()