Swift map(_:) extension for Set() ?
The issue with Set.map(_:)
is the same with Dictionary.map(_:)
, and they didn't implement it in the Swift
module (standard library) because there's actually no correct way to implement it. The reason is: map
ping isn't just enumerating (which you can do with any SequenceType
in a for-in
), but it's transforming (with the argument closure) each value into another. So you would expect that the result would have the all the transform(element)
in it, but guess what, if the values are the same they collapse (for Dictionary
s only the keys behave this way).
e.g. (with the proposed implementation)
Set([1, 2, 3, 4, 5]).map { 1 }.count == 1
That's also why Swift.map
returns an Array
and not the same type of SequenceType
/CollectionType
passed as the source
argument.
To digress slightly, Dictionary
(as said before) has the same issue on the key values (which is basically a Set
), so any map
that works either on the Set<K>
or the Set<(K, V)>
doesn't ensure a consistent mapping, but one that changes the values only would be ok. It wouldn't be a true map
though since Dictionary
s are collections of tuples, not on value only. So there could be something similar to mapValues
which would be correct, but still not a true map
on the collection.
TL;DR
map
is really useful but be careful on what you're actually doing, because you can't really do a map
from Set
to Set
.
Update: Quite a lot changed with Swift 2 and 3. The generic
placeholder of Set
is now Element
instead of T
, and all
collections have a map()
method which returns an array.
There were also good arguments given about the problems of a Set -> Set
mapping (such as different elements mapping to the same result).
On the other hand, there may be a use-case for such a mapping,
so here is an update for Swift 3 (now using a different name).
extension Set {
func setmap<U>(transform: (Element) -> U) -> Set<U> {
return Set<U>(self.lazy.map(transform))
}
}
Example:
let numberSet = Set(1...11)
let divideSet = numberSet.setmap { $0 / 2 }
print(divideSet) // [5, 0, 2, 4, 1, 3]
(Old answer:) You were almost there. For some reason, the generic type of the returned set must be specified explicitly:
extension Set {
func map<U>(transform: (T) -> U) -> Set<U> {
return Set<U>(Swift.map(self, transform))
}
}
Example:
let numberSet = Set(1...11)
let divideSet = numberSet.map { $0 / 2 }
println(divideSet) // [5, 0, 2, 4, 1, 3]
The resulting set has less elements because the integer division
$0 / 2
truncates the quotient, e.g. both 4/2 and 5/2 map to
the same element 2. This does not happen with floating point division:
let floatdivideSet = numberSet.map { Double($0) / 2.0 }
println(floatdivideSet) // [4.0, 5.0, 4.5, 5.5, 2.0, 3.0, 3.5, 2.5, 1.5, 1.0, 0.5]
Another possible implementation is
extension Set {
func map<U>(transform: (T) -> U) -> Set<U> {
return Set<U>(lazy(self).map(transform))
}
}
Here lazy(self)
returns a LazyForwardCollection
which has
a map()
method and that returns a LazyForwardCollection
again.
The advantage might be that no intermediate array is created.