Here are two versions of map, the first one representing the current implementation, slightly simplified. I've tested some expensive transforms with a class and struct and the difference in speed seems negligible. I have two questions:
- Is the head-on
reserve -> append expected to be faster than initializing the contiguous array with a lazy map wrapper (_copyToContiguousArray)?
- If not, does
map intendedly stick to the head-on approach?
extension Sequence {
@inlinable func map2<T>(
_ transform: (Element) -> T
) -> [T] {
let initialCapacity = underestimatedCount
var result = ContiguousArray<T>()
result.reserveCapacity(initialCapacity)
var iterator = self.makeIterator()
for _ in 0..<initialCapacity {
let next = iterator.next()!
result.append(transform(next))
}
while let element = iterator.next() {
result.append(transform(element))
}
return Array(result)
}
}
extension Sequence {
@inlinable func map1<T>(
_ transform: @escaping (Element) -> T
) -> [T] {
let result = ContiguousArray(lazy.map(transform))
return Array(result)
}
}
jrose
(Jordan Rose)
2
I suspect that this is mostly just because the current implementation of Sequence's map predates lazy. Sequence.map also takes a non-escaping closure, so you'll need a withoutActuallyEscaping somewhere in there.
Put up a pull request and let's benchmark it!
1 Like
@jrose I just realized I can't use the lazy map with the real map because it doesn't accept throwing closures
. Perhaps that's the reason.
1 Like
The ability to specify the throwing context would be very useful here.
@inlinable
func map<T>(
_ transform: (Element) throws -> T
) rethrows -> [T] {
return withoutActuallyEscaping(transform) { escaping in
return Array(
ContiguousArray(
self.lazy.map { elt in try(map) escaping(elt) }
) ~~~
)
)
}
}