Swift map(_:) extension for Set() ?

纵饮孤独 提交于 2019-11-28 09:55:19

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.

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: mapping 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 Dictionarys 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 Dictionarys 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.

As of Swift 2.0 the CollectionType protocol (implemented by Set) has a map method.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!