I have a list of animals:
let animals = ["bear", "dog", "cat"]
And some ways to transform that list:
typealias Transform = (String) -> [String]
let containsA: Transform = { $0.contains("a") ? [$0] : [] }
let plural: Transform = { [$0 + "s"] }
let double: Transform = { [$0, $0] }
As a slight aside, these are analogous to filter (outputs 0 or 1 element), map (exactly 1 element) and flatmap (more than 1 element) respectively but defined in a uniform way so that they can be handled consistently.
I want to create a lazy iterator which applies an array of these transforms to the list of animals:
extension Array where Element == String {
func transform(_ transforms: [Transform]) -> AnySequence<String> {
return AnySequence<String> { () -> AnyIterator<String> in
var iterator = self
.lazy
.flatMap(transforms[0])
.flatMap(transforms[1])
.flatMap(transforms[2])
.makeIterator()
return AnyIterator {
return iterator.next()
}
}
}
}
which means I can lazily do:
let transformed = animals.transform([containsA, plural, double])
and to check the result:
print(Array(transformed))
I'm pleased with how succinct this is but clearly:
.flatMap(transforms[0])
.flatMap(transforms[1])
.flatMap(transforms[2])
is an issue as it means the transform function will only work with an array of 3 transforms.
Edit: I tried:
var lazyCollection = self.lazy
for transform in transforms {
lazyCollection = lazyCollection.flatMap(transform) //Error
}
var iterator = lazyCollection.makeIterator()
but on the marked row I get error:
Cannot assign value of type 'LazyCollection< FlattenCollection< LazyMapCollection< Array< String>, [String]>>>' to type 'LazyCollection< Array< String>>'
which I understand because each time around the loop another flatmap is being added, so the type is changing.
How can I make the transform function work with an array of any number of transforms?
One WET solution for a limited number of transforms would be (but YUK!)
switch transforms.count {
case 1:
var iterator = self
.lazy
.flatMap(transforms[0])
.makeIterator()
return AnyIterator {
return iterator.next()
}
case 2:
var iterator = self
.lazy
.flatMap(transforms[0])
.flatMap(transforms[1])
.makeIterator()
return AnyIterator {
return iterator.next()
}
case 3:
var iterator = self
.lazy
.flatMap(transforms[0])
.flatMap(transforms[1])
.flatMap(transforms[2])
.makeIterator()
return AnyIterator {
return iterator.next()
}
default:
fatalError(" Too many transforms!")
}
Whole code:
let animals = ["bear", "dog", "cat"]
typealias Transform = (String) -> [String]
let containsA: Transform = { $0.contains("a") ? [$0] : [] }
let plural: Transform = { [$0 + "s"] }
let double: Transform = { [$0, $0] }
extension Array where Element == String {
func transform(_ transforms: [Transform]) -> AnySequence<String> {
return AnySequence<String> { () -> AnyIterator<String> in
var iterator = self
.lazy
.flatMap(transforms[0])
.flatMap(transforms[1])
.flatMap(transforms[2])
.makeIterator()
return AnyIterator {
return iterator.next()
}
}
}
}
let transformed = animals.transform([containsA, plural, double])
print(Array(transformed))
You can apply the transformations recursively if you define the method on the Sequence
protocol (instead of Array
). Also the constraint where Element == String
is not needed if the transformations parameter is defined as an array of (Element) -> [Element]
.
extension Sequence {
func transform(_ transforms: [(Element) -> [Element]]) -> AnySequence<Element> {
if transforms.isEmpty {
return AnySequence(self)
} else {
return lazy.flatMap(transforms[0]).transform(Array(transforms[1...]))
}
}
}
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