taylor swift taylor swift - 14 days ago 6
Swift Question

Explain Swift Iterators

There’s very little up-to-date guidance on how to make generators in Swift (or iterators as they’re apparently called in Swift), especially if you are new to the language. Why are there so many generator types like

AnyIterator
and
UnfoldSequence
? Why doesn’t the following code, which should yield from a sequence of either individual
Int
s or Arrays of
Int
s, work?

func chain(_ segments: Any...) -> AnyIterator<Int>{
return AnyIterator<Int> {
for segment in segments {
switch segment {
case let segment as Int:
return segment
case let segment as [Int]:
for i in segment {
return i
}
default:
return nil
}
}
return nil
}
}

let G = chain(array1, 42, array2)
while let g = G.next() {
print(g)
}


The way I understand it,
AnyIterator
is supposed to take the closure in the
{}
s and turn it into the
.next()
method in the returned generator, but it doesn’t seem to be working. Or should I be using
UnfoldSequence
like in this question instead. I’m very confused.

Answer

Yes, the next() method of AnyIterator calls the given closure. And in your code, that closure returns the same first element on each call, because it does not remember what elements have been returned already.

If Swift had a yield statement like Python or C# then things would be easier: you could yield segment or yield i and are done.

But – unfortunately? – Swift has no yield statement, which means that the closure must explicitly manage some state in order to resume the iteration with the next element on each call.

One possibility would be to maintain two indices, one for the current segment and one for the current element within a segment if that is an array:

func chain(_ segments: Any...) -> AnyIterator<Int> {
    var currentSegment = 0 // index of current segment
    var currentElement = 0 // index of current element within current segment
    return AnyIterator<Int> {
        while currentSegment < segments.count {
            let next = segments[currentSegment]
            switch next {
            case let value as Int:
                currentSegment += 1
                return value
            case let segment as [Int]:
                if currentElement < segment.count {
                    let val = segment[currentElement]
                    currentElement += 1
                    return val
                }
                currentSegment += 1
                currentElement = 0
            default:
                return nil
            }
        }
        return nil
    }
}

Another method would be to keep a stack of items to process or to use recursive generators, as discussed in Implementing recursive generator for simple tree structure in Swift.

Here is a simple stack-based implementation:

func chain(_ segments: Any...) -> AnyIterator<Int> {
    var stack = segments
    return AnyIterator<Int> {
        while let next = stack.first {
            stack.remove(at: 0)
            switch next {
            case let value as Int:
                return value
            case let segment as [Any]:
                stack.insert(contentsOf: segment, at: 0)
            default:
                return nil
            }
        }
        return nil
    }
}

which would work even with nested arrays:

let G = chain([1, 2, [3]], 4, [5, 6, [], 7])
while let g = G.next() {
    print(g)
}
// 1 2 3 4 5 6 7

This can probably be further improved, but I hope it helps to get the idea.

Comments