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.
An Iterator is an object that can be used to loop through collections, like ArrayList and HashSet. It is called an "iterator" because "iterating" is the technical term for looping. To use an Iterator, you must import it from the java.util package.
An iterator is an object (like a pointer) that points to an element inside the container. We can use iterators to move through the contents of the container. They can be visualized as something similar to a pointer pointing to some location and we can access the content at that particular location using them.
The Sequence protocol provides default implementations for many common operations that depend on sequential access to a sequence's values. For clearer, more concise code, the example above could use the array's contains(_:) method, which every sequence inherits from Sequence , instead of iterating manually: if bugs.
An iterator is an object that allows you to step through the contents of another object, by providing convenient operations for getting the first element, testing when you are done, and getting the next element if you are not. In C, we try to design iterators to have operations that fit well in the top of a for loop.
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
}
}
This can be generalized to arbitrarily nested arrays:
func chain(_ segments: Any...) -> AnyIterator<Int> {
var stack: [(Any, Int)] = [(segments, 0)]
return AnyIterator<Int> {
while let (next, idx) = stack.popLast() {
switch next {
case let value as Int:
return value
case let segments as [Any]:
if idx < segments.count {
stack.append((segments, idx + 1))
stack.append((segments[idx], 0))
}
default:
return nil
}
}
return nil
}
}
The still-to-be-processed arrays are on the stack together with their current index. The arrays themselves are not modified, so that the copy is cheap.
Example:
let G = chain([1, 2, [3]], 4, [5, 6, [], 7])
while let g = G.next() {
print(g)
}
// 1 2 3 4 5 6 7
See also Implementing recursive generator for simple tree structure in Swift for more approaches to recursively enumerate a tree-like structure.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With