Description
One of the most common uses of compactMap
is to just flatten the nil
s out of a sequence without transforming its elements, i.e. x.lazy.compactMap { $0 }
.
A compacted()
convenience method for this use case would be useful because, similar to joined()
in the Standard Library, it wouldn't have to capture an escaping closure and (by the current conventions) it would be able to be lazy by default, i.e. x.compacted() // => Compacted<[Int?], Int>
A new Compacted
sequence should also be able to conditionally conform to Collection
and BidirectionalCollection
, and it should precompute on initialization in order to provide an O(1) startIndex
.
Here's a sketch of the Sequence
conformance:
struct Compacted<Base: Sequence, Element>: Sequence
where Base.Element == Element?
{
let base: Base
struct Iterator: IteratorProtocol {
var base: Base.Iterator
mutating func next() -> Element? {
while let wrapped = base.next() {
if let some = wrapped {
return some
} else {
// skip nil
}
}
return nil
}
}
func makeIterator() -> Iterator {
return Iterator(base: base.makeIterator())
}
}
extension Sequence {
func compacted<Unwrapped>() -> Compacted<Self, Unwrapped> where Element == Unwrapped? {
Compacted(base: self)
}
}
var tests: [[Int?]] = [
[],
[0],
[nil],
[0, nil],
[nil, 0],
[0, nil, 1, nil, 2, nil],
[0, 1, 2, nil, nil, nil],
[nil, nil, nil, 0, 1, 2],
]
for test in tests {
assert(test.compacted().elementsEqual(test.compactMap({ $0 })))
}
// let x: Array<Int> = [1, 2, 3]
// _ = x.compacted() // error: instance method 'compacted()' requires the types 'Int' and 'Unwrapped?' be equivalent
Thanks to @natecook1000 for coming up with how to make the types work out!