In Flatten to get all child controls of certain type in a UIView, methods were discussed to recursively flatten a tree-like structure in Swift, resulting in an array of all elements.
Motivated by that thread, I have written a flatten function which creates a sequence instead. This can be an advantage if the resulting elements are to be post-processed (e.g. filtered), because they can be created "lazily" or "on-demand", instead of creating an array with all resulting elements first.
func sequentialFlatten<S : SequenceType>
(seq : S, children : (S.Generator.Element) -> S) -> SequenceOf<S.Generator.Element>
{
return SequenceOf {
() -> GeneratorOf<S.Generator.Element> in
// Current generator, or `nil` if all sequences are exhausted:
var gen : S.Generator? = seq.generate()
// Queue of generators for sequences which still have to be enumerated:
var queue : [S.Generator] = []
return GeneratorOf {
while gen != nil {
if let next = gen!.next() {
queue.append(children(next).generate())
return next
}
// Current generator is empty, try to deque next one:
gen = queue.first // `nil` if queue is empty
if (gen != nil) {
queue.removeAtIndex(0)
}
}
return nil
}
}
}
The function arguments are an initial sequence, and a closure which
transforms any sequence element into a new sequence (which may be
empty). One can view seq
as a set of root nodes in a tree
or forest, and children
as the mapping from each node to its children.
(Swift has a related flapMap()
function which takes a sequence and a
transformation as argument, but that does not work recursively, and
returns an array and not a sequence.)
Example 1:
let someView : UIView = ...
let views = sequentialFlatten([someView], { $0.subviews as! [UIView] })
let labels = lazy(views).filter( { $0 is UILabel } ).map( { $0 as! UILabel } )
creates a sequence of all views in the view hierarchy of someView
,
and then extracts all labels from that sequence. (Note that no intermediate array of all views is created.)
Example 2:
let seq = sequentialFlatten([0], { n in n < 50 ? [ 2*n+1, 2*n+2] : []})
for x in seq { println(x) }
is an unconventional method to print the numbers 0 ... 100.
I wrote this function mainly for my own educational purpose, to get more familiar with Swift sequences and generators. All feedback is welcome, for example:
- Naming (function and variables).
- Swifty-ness: things which should be done different in Swift.
- Implementation: can the same be achieved easier, perhaps using available functions from the Swift standard library?
- Can
sequentialFlatten()
be implemented in a way that it calls itself recursively?